1 /*
2  * Copyright (C) 2024 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.car.settings.privacy;
18 
19 import android.Manifest;
20 import android.content.Context;
21 import android.content.pm.PackageInfo;
22 import android.content.pm.PackageManager;
23 import android.content.pm.PackageManager.NameNotFoundException;
24 import android.content.pm.PermissionInfo;
25 import android.location.LocationManager;
26 import android.os.Build;
27 import android.os.UserHandle;
28 import android.util.ArrayMap;
29 
30 import androidx.annotation.Nullable;
31 import androidx.core.util.Preconditions;
32 
33 import com.android.car.settings.R;
34 import com.android.car.settings.common.Logger;
35 import com.android.net.module.util.CollectionUtils;
36 
37 import com.google.common.collect.ImmutableListMultimap;
38 
39 import java.util.ArrayList;
40 import java.util.List;
41 
42 /**
43  * Utility class for applications permissions. This logic is extracted from {@link
44  * com.android.permissioncontroller.permission.data.AppPermGroupUiInfoLiveData}.
45  */
46 public final class PermissionUtils {
47     private static final Logger LOG = new Logger(PermissionUtils.class);
48     private static final ImmutableListMultimap<String, String> PERMISSION_GROUP_TO_PERMISSIONS =
49             ImmutableListMultimap.<String, String>builder()
50                     .putAll(Manifest.permission_group.CAMERA,
51                             Manifest.permission.CAMERA)
52                     .putAll(Manifest.permission_group.LOCATION,
53                             Manifest.permission.ACCESS_FINE_LOCATION,
54                             Manifest.permission.ACCESS_COARSE_LOCATION,
55                             Manifest.permission.ACCESS_BACKGROUND_LOCATION)
56                     .putAll(Manifest.permission_group.MICROPHONE,
57                             Manifest.permission.RECORD_AUDIO)
58                     .build();
59 
PermissionUtils()60     private PermissionUtils() {}
61 
62     private static class PermissionState {
63         public int mPermissionFlags;
64         public PermissionInfo mPermissionInfo;
65         public boolean mGranted;
66 
PermissionState(int permissionFlags, PermissionInfo permInfo, boolean granted)67         PermissionState(int permissionFlags, PermissionInfo permInfo, boolean granted) {
68             mPermissionFlags = permissionFlags;
69             mPermissionInfo = permInfo;
70             mGranted = granted;
71         }
72     }
73 
74     private static final Object sLock = new Object();
75     private static final ArrayMap<UserHandle, Context> sUserContexts = new ArrayMap<>();
76 
77     /**
78      * TODO(b/339236027): Remove extra logic in Android W
79      *
80      * Creates and caches a PackageContext for the requested user, or returns the previously cached
81      * value. The package of the PackageContext is the application's package.
82      *
83      * Note: All this logic is directly from PermissionController. Ideally, we would've exposed a
84      * SystemApi to be used in CarSettings instead, but the API deadline passed. This logic is used
85      * below in {@link #getSpecialLocationState(Context, String, String, UserHandle)}. It appears
86      * there's a scenario where the locationManager from user context and app context may not align.
87      *
88      * @param context The context of the currently running application
89      * @param user The desired user for the context
90      *
91      * @return The generated or cached Context for the requested user
92      *
93      * @throws RuntimeException If the app has no package name attached, which should never happen
94      */
getUserContext(Context context, UserHandle user)95     private static Context getUserContext(Context context, UserHandle user) {
96         synchronized (sLock) {
97             if (!sUserContexts.containsKey(user)) {
98                 sUserContexts.put(user, context.getApplicationContext()
99                         .createContextAsUser(user, 0));
100             }
101             return Preconditions.checkNotNull(sUserContexts.get(user));
102         }
103     }
104 
105     /**
106      * Returns a list of the packages holding a permission from the specified permission group.
107      *
108      * @param context current context.
109      * @param permissionGroup permission group for filtering the packages.
110      * @param userHandle current user handle.
111      * @param showSystem whether to return system packages.
112      *
113      * @return List of packages.
114      */
getPackagesWithPermissionGroup(Context context, String permissionGroup, UserHandle userHandle, boolean showSystem)115     public static List<PackageInfo> getPackagesWithPermissionGroup(Context context,
116             String permissionGroup, UserHandle userHandle, boolean showSystem) {
117         ArrayMap<String, PackageInfo> packageNameToInfo = new ArrayMap<>();
118         List<String> permissionNames = PERMISSION_GROUP_TO_PERMISSIONS.get(permissionGroup);
119         for (String permissionName: permissionNames) {
120             List<PackageInfo> packages = context.getPackageManager().getPackagesHoldingPermissions(
121                     new String[]{permissionName}, PackageManager.GET_PERMISSIONS);
122 
123             for (PackageInfo packageInfo : packages) {
124                 // Ignore duplicate packages
125                 if (packageNameToInfo.containsKey(packageInfo.packageName)) {
126                     continue;
127                 }
128                 PermissionState permState =
129                         getPermissionState(context, packageInfo, permissionName, userHandle);
130                 if (permState == null) {
131                     continue;
132                 }
133 
134                 boolean isUserSensitive = ((permState.mGranted && (permState.mPermissionFlags
135                         & PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED) != 0)
136                         || (!permState.mGranted && (permState.mPermissionFlags
137                         & PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED) != 0));
138                 // If an app is not user sensitive, then it is considered a system app,
139                 // and hidden in the UI by default.
140                 if (!showSystem && !isUserSensitive) {
141                     continue;
142                 }
143                 packageNameToInfo.put(packageInfo.packageName, packageInfo);
144             }
145         }
146         return new ArrayList<>(packageNameToInfo.values());
147     }
148 
getPermissionState(Context context, PackageInfo packageInfo, String permissionName, UserHandle userHandle)149     private static PermissionState getPermissionState(Context context, PackageInfo packageInfo,
150             String permissionName, UserHandle userHandle) {
151         int permissionFlags = context.getPackageManager().getPermissionFlags(permissionName,
152                 packageInfo.packageName, userHandle);
153         String[] requestedPermissions = packageInfo.requestedPermissions;
154         if (requestedPermissions == null) {
155             return null;
156         }
157         int requestedPermissionsIndex = CollectionUtils.indexOf(requestedPermissions,
158                 permissionName);
159         int[] requestedPermissionsFlags = packageInfo.requestedPermissionsFlags;
160         PermissionInfo permInfo = null;
161         try {
162             permInfo = context.getPackageManager().getPermissionInfo(permissionName, 0);
163         } catch (NameNotFoundException ex) {
164             LOG.e("Failed to get application info for " + packageInfo.packageName);
165             return null;
166         }
167 
168         if (requestedPermissionsIndex == -1) {
169             return null;
170         }
171 
172         boolean hasPreRuntime = false;
173         boolean hasInstantPerm = false;
174 
175         if ((permInfo.getProtectionFlags() & PermissionInfo.PROTECTION_FLAG_RUNTIME_ONLY) == 0) {
176             hasPreRuntime = true;
177         }
178 
179         if ((permInfo.getProtectionFlags() & PermissionInfo.PROTECTION_FLAG_INSTANT) != 0) {
180             hasInstantPerm = true;
181         }
182 
183         boolean supportsRuntime = packageInfo.applicationInfo.targetSdkVersion
184                 >= Build.VERSION_CODES.M;
185         boolean isGrantingAllowed = (!packageInfo.applicationInfo.isInstantApp() || hasInstantPerm)
186                 && (supportsRuntime || hasPreRuntime);
187         if (!isGrantingAllowed) {
188             return null;
189         }
190 
191         boolean granted = (((requestedPermissionsFlags[requestedPermissionsIndex]
192                 & PackageInfo.REQUESTED_PERMISSION_GRANTED) != 0)
193                 && ((permissionFlags & PackageManager.FLAG_PERMISSION_REVOKED_COMPAT) == 0));
194 
195         return new PermissionState(permissionFlags, permInfo, granted);
196     }
197 
198      /**
199      * Returns the grant status of all permissions in a group for a specific package.
200      *
201      * @param context current context.
202      * @param packageInfo package for which to check the grant status
203      * @param permissionGroup permission group for filtering the packages.
204      * @param userHandle current user handle.
205      *
206      * @return resource for permission grant status.
207      */
getPermissionGroupGrantStatus(Context context, PackageInfo packageInfo, String permissionGroup, UserHandle userHandle)208     public static int getPermissionGroupGrantStatus(Context context, PackageInfo packageInfo,
209             String permissionGroup, UserHandle userHandle) {
210         ArrayMap<String, PermissionState> permissionNameToState = new ArrayMap<>();
211         List<String> permissionNames = PERMISSION_GROUP_TO_PERMISSIONS.get(permissionGroup);
212         for (String permissionName : permissionNames) {
213             PermissionState permState =
214                     getPermissionState(context, packageInfo, permissionName, userHandle);
215             if (permState != null) {
216                 permissionNameToState.put(permissionName, permState);
217             }
218         }
219 
220         Boolean specialLocationState = getSpecialLocationState(context, packageInfo.packageName,
221                 permissionGroup, userHandle);
222         boolean supportsRuntime = packageInfo.applicationInfo.targetSdkVersion
223                 >= Build.VERSION_CODES.M;
224         boolean hasPermWithBackground = false;
225         boolean anyAllowed = false;
226         boolean isUserFixed = false;
227         boolean containsOneTimePerm = false;
228         boolean containsGrantedNonOneTimePerm = false;
229         for (int i = 0; i < permissionNameToState.size(); i++) {
230             PermissionState permState = permissionNameToState.valueAt(i);
231             if ((permState.mPermissionInfo.backgroundPermission != null)) {
232                 hasPermWithBackground = true;
233                 PermissionState backgroundPermState = permissionNameToState.get(
234                         permState.mPermissionInfo.backgroundPermission);
235                 if (backgroundPermState != null && backgroundPermState.mGranted
236                         && (backgroundPermState.mPermissionFlags
237                                 & PackageManager.FLAG_PERMISSION_ONE_TIME) == 0
238                         && !Boolean.FALSE.equals(specialLocationState)) {
239                     return R.string.permission_grant_always;
240                 }
241             }
242 
243             if ((permState.mPermissionFlags & PackageManager.FLAG_PERMISSION_ONE_TIME) != 0) {
244                 containsOneTimePerm = true;
245             }
246 
247             if ((permState.mPermissionFlags & PackageManager.FLAG_PERMISSION_ONE_TIME) == 0
248                     && permState.mGranted) {
249                 containsGrantedNonOneTimePerm = true;
250             }
251 
252             if (specialLocationState != null) {
253                 anyAllowed = specialLocationState;
254             } else if (permState.mGranted
255                     || (supportsRuntime
256                             && (permState.mPermissionFlags
257                                     & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) != 0)) {
258                 anyAllowed = true;
259             }
260 
261             isUserFixed = isUserFixed
262                     || (permState.mPermissionFlags
263                             & PackageManager.FLAG_PERMISSION_USER_FIXED) != 0;
264         }
265 
266         // isOneTime indicates whether all granted permissions in permission states are one-time
267         // permissions
268         boolean isOneTime = containsOneTimePerm && !containsGrantedNonOneTimePerm;
269         boolean shouldShowAsForegroundGroup =
270                 Manifest.permission_group.CAMERA.equals(permissionGroup)
271                 || Manifest.permission_group.MICROPHONE.equals(permissionGroup);
272 
273         if (anyAllowed) {
274             if (isOneTime) {
275                 return R.string.permission_grant_ask;
276             } else if (hasPermWithBackground || shouldShowAsForegroundGroup) {
277                 return R.string.permission_grant_in_use;
278             } else {
279                 return R.string.permission_grant_allowed;
280             }
281         }
282         if (isUserFixed) {
283             return R.string.permission_grant_never;
284         }
285         if (isOneTime) {
286             return R.string.permission_grant_ask;
287         }
288         return R.string.permission_grant_never;
289     }
290 
291     @Nullable
getSpecialLocationState(Context appContext, String packageName, String permissionGroup, UserHandle userHandle)292     private static Boolean getSpecialLocationState(Context appContext, String packageName,
293             String permissionGroup, UserHandle userHandle) {
294         if (!Manifest.permission_group.LOCATION.equals(permissionGroup)) {
295             return null;
296         }
297 
298         LocationManager appLocationManager = appContext.getSystemService(LocationManager.class);
299         if (!appLocationManager.isProviderPackage(packageName)
300                 && !packageName.equals(appLocationManager.getExtraLocationControllerPackage())) {
301             return null;
302         }
303 
304         Context userContext = getUserContext(appContext, userHandle);
305         LocationManager userLocationManager = userContext.getSystemService(LocationManager.class);
306         if (userLocationManager.isProviderPackage(packageName)) {
307             return userLocationManager.isLocationEnabled();
308         }
309 
310         // The permission of the extra location controller package is determined by the
311         // status of the controller package itself.
312         if (packageName.equals(userLocationManager.getExtraLocationControllerPackage())) {
313             try {
314                 return userLocationManager.isExtraLocationControllerPackageEnabled();
315             } catch (Exception e) {
316                 return false;
317             }
318         }
319 
320         return null;
321     }
322 }
323