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