blob: 6f2a5eccde4a86fbbeb6710e83c61fd419d28503 [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
Svet Ganove4ae9412017-09-28 12:45:30 -070048 * @param message Message to add to the exception if no location permission
Svetoslav Ganov4a9d4482017-06-20 19:53:35 -070049 * @return boolean true or false if permissions is granted
50 */
51 static boolean canAccessCellLocation(@NonNull Context context, @NonNull String pkgName,
Svet Ganove4ae9412017-09-28 12:45:30 -070052 int uid, String message) throws SecurityException {
Svetoslav Ganov4a9d4482017-06-20 19:53:35 -070053 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.
Svet Ganove4ae9412017-09-28 12:45:30 -070058
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)) {
Svetoslav Ganov4a9d4482017-06-20 19:53:35 -070069 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
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
82 private static boolean isLegacyForeground(@NonNull Context context, @NonNull String pkgName) {
83 return isLegacyVersion(context, pkgName) && isForegroundApp(context, pkgName);
84 }
85
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
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
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
Svetoslav Ganov4a9d4482017-06-20 19:53:35 -0700115 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}