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 com.android.phone; 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.os.Build; 28 import android.os.UserHandle; 29 import android.os.UserManager; 30 import android.provider.Settings; 31 32 import java.util.List; 33 34 /** 35 * Helper for performing location access checks. 36 */ 37 final class LocationAccessPolicy { 38 LocationAccessPolicy()39 private LocationAccessPolicy() { 40 /* do nothing - hide ctor */ 41 } 42 43 /** 44 * API to determine if the caller has permissions to get cell location. 45 * 46 * @param pkgName Package name of the application requesting access 47 * @param uid The uid of the package 48 * @param message Message to add to the exception if no location permission 49 * @return boolean true or false if permissions is granted 50 */ canAccessCellLocation(@onNull Context context, @NonNull String pkgName, int uid, String message)51 static boolean canAccessCellLocation(@NonNull Context context, @NonNull String pkgName, 52 int uid, String message) throws SecurityException { 53 context.getSystemService(AppOpsManager.class).checkPackage(uid, pkgName); 54 // We always require the location permission and also require the 55 // location mode to be on for non-legacy apps. Legacy apps are 56 // required to be in the foreground to at least mitigate the case 57 // where a legacy app the user is not using tracks their location. 58 59 // Grating ACCESS_FINE_LOCATION to an app automatically grants it ACCESS_COARSE_LOCATION. 60 context.enforceCallingOrSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION, message); 61 final int opCode = AppOpsManager.permissionToOpCode( 62 Manifest.permission.ACCESS_COARSE_LOCATION); 63 if (opCode != AppOpsManager.OP_NONE && context.getSystemService(AppOpsManager.class) 64 .noteOp(opCode, uid, pkgName) != AppOpsManager.MODE_ALLOWED) { 65 return false; 66 } 67 if (!isLocationModeEnabled(context, UserHandle.getUserId(uid)) 68 && !isLegacyForeground(context, pkgName)) { 69 return false; 70 } 71 // If the user or profile is current, permission is granted. 72 // Otherwise, uid must have INTERACT_ACROSS_USERS_FULL permission. 73 return isCurrentProfile(context, uid) || checkInteractAcrossUsersFull(context); 74 } 75 isLocationModeEnabled(@onNull Context context, @UserIdInt int userId)76 private static boolean isLocationModeEnabled(@NonNull Context context, @UserIdInt int userId) { 77 return Settings.Secure.getIntForUser(context.getContentResolver(), 78 Settings.Secure.LOCATION_MODE, Settings.Secure.LOCATION_MODE_OFF, userId) 79 != Settings.Secure.LOCATION_MODE_OFF; 80 } 81 isLegacyForeground(@onNull Context context, @NonNull String pkgName)82 private static boolean isLegacyForeground(@NonNull Context context, @NonNull String pkgName) { 83 return isLegacyVersion(context, pkgName) && isForegroundApp(context, pkgName); 84 } 85 isLegacyVersion(@onNull Context context, @NonNull String pkgName)86 private static boolean isLegacyVersion(@NonNull Context context, @NonNull String pkgName) { 87 try { 88 if (context.getPackageManager().getApplicationInfo(pkgName, 0) 89 .targetSdkVersion <= Build.VERSION_CODES.O) { 90 return true; 91 } 92 } catch (PackageManager.NameNotFoundException e) { 93 // In case of exception, assume known app (more strict checking) 94 // Note: This case will never happen since checkPackage is 95 // called to verify validity before checking app's version. 96 } 97 return false; 98 } 99 isForegroundApp(@onNull Context context, @NonNull String pkgName)100 private static boolean isForegroundApp(@NonNull Context context, @NonNull String pkgName) { 101 final ActivityManager am = context.getSystemService(ActivityManager.class); 102 final List<ActivityManager.RunningTaskInfo> tasks = am.getRunningTasks(1); 103 if (!tasks.isEmpty()) { 104 return pkgName.equals(tasks.get(0).topActivity.getPackageName()); 105 } 106 return false; 107 } 108 checkInteractAcrossUsersFull(@onNull Context context)109 private static boolean checkInteractAcrossUsersFull(@NonNull Context context) { 110 return context.checkCallingOrSelfPermission( 111 android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) 112 == PackageManager.PERMISSION_GRANTED; 113 } 114 isCurrentProfile(@onNull Context context, int uid)115 private static boolean isCurrentProfile(@NonNull Context context, int uid) { 116 final int currentUser = ActivityManager.getCurrentUser(); 117 final int callingUserId = UserHandle.getUserId(uid); 118 if (callingUserId == currentUser) { 119 return true; 120 } else { 121 List<UserInfo> userProfiles = context.getSystemService( 122 UserManager.class).getProfiles(currentUser); 123 for (UserInfo user: userProfiles) { 124 if (user.id == callingUserId) { 125 return true; 126 } 127 } 128 } 129 return false; 130 } 131 } 132