• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 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 package com.android.settings.applications;
17 
18 import android.app.AppGlobals;
19 import android.app.AppOpsManager;
20 import android.app.AppOpsManager.PackageOps;
21 import android.content.Context;
22 import android.content.pm.IPackageManager;
23 import android.content.pm.PackageInfo;
24 import android.content.pm.PackageManager;
25 import android.os.RemoteException;
26 import android.os.UserHandle;
27 import android.os.UserManager;
28 import android.util.ArrayMap;
29 import android.util.Log;
30 import android.util.SparseArray;
31 
32 import androidx.annotation.VisibleForTesting;
33 
34 import com.android.settingslib.applications.ApplicationsState;
35 import com.android.settingslib.applications.ApplicationsState.AppEntry;
36 
37 import java.util.Arrays;
38 import java.util.Collection;
39 import java.util.HashSet;
40 import java.util.List;
41 import java.util.Set;
42 
43 /*
44  * Connects app ops info to the ApplicationsState. Makes use of AppOpsManager to
45  * determine further permission level.
46  */
47 public abstract class AppStateAppOpsBridge extends AppStateBaseBridge {
48 
49     private static final String TAG = "AppStateAppOpsBridge";
50 
51     private final IPackageManager mIPackageManager;
52     private final UserManager mUserManager;
53     private final List<UserHandle> mProfiles;
54     private final AppOpsManager mAppOpsManager;
55     private final Context mContext;
56     private final int[] mAppOpsOpCodes;
57     private final String[] mPermissions;
58 
AppStateAppOpsBridge(Context context, ApplicationsState appState, Callback callback, int appOpsOpCode, String[] permissions)59     public AppStateAppOpsBridge(Context context, ApplicationsState appState, Callback callback,
60             int appOpsOpCode, String[] permissions) {
61         this(context, appState, callback, appOpsOpCode, permissions,
62                 AppGlobals.getPackageManager());
63     }
64 
65     @VisibleForTesting
AppStateAppOpsBridge(Context context, ApplicationsState appState, Callback callback, int appOpsOpCode, String[] permissions, IPackageManager packageManager)66     AppStateAppOpsBridge(Context context, ApplicationsState appState, Callback callback,
67             int appOpsOpCode, String[] permissions, IPackageManager packageManager) {
68         super(appState, callback);
69         mContext = context;
70         mIPackageManager = packageManager;
71         mUserManager = UserManager.get(context);
72         mProfiles = mUserManager.getUserProfiles();
73         mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
74         mAppOpsOpCodes = new int[] {appOpsOpCode};
75         mPermissions = permissions;
76     }
77 
isThisUserAProfileOfCurrentUser(final int userId)78     private boolean isThisUserAProfileOfCurrentUser(final int userId) {
79         final int profilesMax = mProfiles.size();
80         for (int i = 0; i < profilesMax; i++) {
81             if (mProfiles.get(i).getIdentifier() == userId) {
82                 return true;
83             }
84         }
85         return false;
86     }
87 
updateExtraInfo(AppEntry app, String pkg, int uid)88     protected abstract void updateExtraInfo(AppEntry app, String pkg, int uid);
89 
doesAnyPermissionMatch(String permissionToMatch, String[] permissions)90     private boolean doesAnyPermissionMatch(String permissionToMatch, String[] permissions) {
91         for (String permission : permissions) {
92             if (permissionToMatch.equals(permission)) {
93                 return true;
94             }
95         }
96         return false;
97     }
98 
getPermissionInfo(String pkg, int uid)99     public PermissionState getPermissionInfo(String pkg, int uid) {
100         PermissionState permissionState = new PermissionState(pkg, new UserHandle(UserHandle
101                 .getUserId(uid)));
102         try {
103             permissionState.packageInfo = mIPackageManager.getPackageInfo(pkg,
104                     PackageManager.GET_PERMISSIONS | PackageManager.MATCH_ANY_USER,
105                     permissionState.userHandle.getIdentifier());
106             if (permissionState.packageInfo != null) {
107                 // Check static permission state (whatever that is declared in package manifest)
108                 String[] requestedPermissions = permissionState.packageInfo.requestedPermissions;
109                 int[] permissionFlags = permissionState.packageInfo.requestedPermissionsFlags;
110                 if (requestedPermissions != null) {
111                     for (int i = 0; i < requestedPermissions.length; i++) {
112                         if (doesAnyPermissionMatch(requestedPermissions[i], mPermissions)) {
113                             permissionState.permissionDeclared = true;
114                             if ((permissionFlags[i] & PackageInfo.REQUESTED_PERMISSION_GRANTED)
115                                     != 0) {
116                                 permissionState.staticPermissionGranted = true;
117                                 break;
118                             }
119                         }
120                     }
121                 }
122             }
123             // Check app op state.
124             List<PackageOps> ops = mAppOpsManager.getOpsForPackage(uid, pkg, mAppOpsOpCodes);
125             if (ops != null && ops.size() > 0 && ops.get(0).getOps().size() > 0) {
126                 permissionState.appOpMode = ops.get(0).getOps().get(0).getMode();
127             }
128         } catch (RemoteException e) {
129             Log.w(TAG, "PackageManager is dead. Can't get package info " + pkg, e);
130         }
131         return permissionState;
132     }
133 
134     @Override
loadAllExtraInfo()135     protected void loadAllExtraInfo() {
136         SparseArray<ArrayMap<String, PermissionState>> entries = getEntries();
137 
138         // Load state info.
139         loadPermissionsStates(entries);
140         loadAppOpsStates(entries);
141 
142         // Map states to application info.
143         List<AppEntry> apps = mAppSession.getAllApps();
144         final int N = apps.size();
145         for (int i = 0; i < N; i++) {
146             AppEntry app = apps.get(i);
147             int userId = UserHandle.getUserId(app.info.uid);
148             ArrayMap<String, PermissionState> userMap = entries.get(userId);
149             app.extraInfo = userMap != null ? userMap.get(app.info.packageName) : null;
150         }
151     }
152 
153     /*
154      * Gets a sparse array that describes every user on the device and all the associated packages
155      * of each user, together with the packages available for that user.
156      */
getEntries()157     private SparseArray<ArrayMap<String, PermissionState>> getEntries() {
158         try {
159             Set<String> packagesSet = new HashSet<>();
160             for (String permission : mPermissions) {
161                 String[] pkgs = mIPackageManager.getAppOpPermissionPackages(permission);
162                 if (pkgs != null) {
163                     packagesSet.addAll(Arrays.asList(pkgs));
164                 }
165             }
166 
167             if (packagesSet.isEmpty()) {
168                 // No packages are requesting permission as specified by mPermissions.
169                 return null;
170             }
171 
172             // Create a sparse array that maps profileIds to an ArrayMap that maps package names to
173             // an associated PermissionState object
174             SparseArray<ArrayMap<String, PermissionState>> entries = new SparseArray<>();
175             for (final UserHandle profile : mProfiles) {
176                 final ArrayMap<String, PermissionState> entriesForProfile = new ArrayMap<>();
177                 final int profileId = profile.getIdentifier();
178                 entries.put(profileId, entriesForProfile);
179                 for (final String packageName : packagesSet) {
180                     final boolean isAvailable = mIPackageManager.isPackageAvailable(packageName,
181                             profileId);
182                     if (!shouldIgnorePackage(packageName) && isAvailable) {
183                         final PermissionState newEntry = new PermissionState(packageName, profile);
184                         entriesForProfile.put(packageName, newEntry);
185                     }
186                 }
187             }
188 
189             return entries;
190         } catch (RemoteException e) {
191             Log.w(TAG, "PackageManager is dead. Can't get list of packages requesting "
192                     + mPermissions[0], e);
193             return null;
194         }
195     }
196 
197     /*
198      * This method will set the packageInfo and staticPermissionGranted field of the associated
199      * PermissionState, which describes a particular package.
200      */
loadPermissionsStates(SparseArray<ArrayMap<String, PermissionState>> entries)201     private void loadPermissionsStates(SparseArray<ArrayMap<String, PermissionState>> entries) {
202         // Load the packages that have been granted the permission specified in mPermission.
203         if (entries == null) {
204             return;
205         }
206 
207         try {
208             for (final UserHandle profile : mProfiles) {
209                 final int profileId = profile.getIdentifier();
210                 final ArrayMap<String, PermissionState> entriesForProfile = entries.get(profileId);
211                 if (entriesForProfile == null) {
212                     continue;
213                 }
214                 @SuppressWarnings("unchecked") final List<PackageInfo> packageInfos =
215                         mIPackageManager
216                                 .getPackagesHoldingPermissions(mPermissions, 0,
217                                         profileId).getList();
218                 final int packageInfoCount = packageInfos != null ? packageInfos.size() : 0;
219                 for (int i = 0; i < packageInfoCount; i++) {
220                     final PackageInfo packageInfo = packageInfos.get(i);
221                     final PermissionState pe = entriesForProfile.get(packageInfo.packageName);
222                     if (pe != null) {
223                         pe.packageInfo = packageInfo;
224                         pe.staticPermissionGranted = true;
225                     }
226                 }
227             }
228         } catch (RemoteException e) {
229             Log.w(TAG, "PackageManager is dead. Can't get list of packages granted "
230                     + mPermissions, e);
231             return;
232         }
233     }
234 
235     /*
236      * This method will set the appOpMode field of the associated PermissionState, which describes
237      * a particular package.
238      */
loadAppOpsStates(SparseArray<ArrayMap<String, PermissionState>> entries)239     private void loadAppOpsStates(SparseArray<ArrayMap<String, PermissionState>> entries) {
240         // Find out which packages have been granted permission from AppOps.
241         final List<AppOpsManager.PackageOps> packageOps = mAppOpsManager.getPackagesForOps(
242                 mAppOpsOpCodes);
243         final int packageOpsCount = packageOps != null ? packageOps.size() : 0;
244         for (int i = 0; i < packageOpsCount; i++) {
245             final AppOpsManager.PackageOps packageOp = packageOps.get(i);
246             final int userId = UserHandle.getUserId(packageOp.getUid());
247             if (!isThisUserAProfileOfCurrentUser(userId)) {
248                 // This AppOp does not belong to any of this user's profiles.
249                 continue;
250             }
251 
252             final ArrayMap<String, PermissionState> entriesForProfile = entries.get(userId);
253             if (entriesForProfile == null) {
254                 continue;
255             }
256             final PermissionState pe = entriesForProfile.get(packageOp.getPackageName());
257             if (pe == null) {
258                 Log.w(TAG, "AppOp permission exists for package " + packageOp.getPackageName()
259                         + " of user " + userId + " but package doesn't exist or did not request "
260                         + mPermissions + " access");
261                 continue;
262             }
263 
264             if (packageOp.getOps().size() < 1) {
265                 Log.w(TAG, "No AppOps permission exists for package " + packageOp.getPackageName());
266                 continue;
267             }
268             pe.appOpMode = packageOp.getOps().get(0).getMode();
269         }
270     }
271 
272     /*
273      * Check for packages that should be ignored for further processing
274      */
shouldIgnorePackage(String packageName)275     private boolean shouldIgnorePackage(String packageName) {
276         return packageName.equals("android") || packageName.equals(mContext.getPackageName());
277     }
278 
getNumPackagesDeclaredPermission()279     public int getNumPackagesDeclaredPermission() {
280         SparseArray<ArrayMap<String, PermissionState>> entries = getEntries();
281         if (entries == null) {
282             return 0;
283         }
284         final ArrayMap<String, PermissionState> entriesForProfile = entries.get(mUserManager
285                 .getUserHandle());
286         if (entriesForProfile == null) {
287             return 0;
288         }
289         return entriesForProfile.size();
290     }
291 
getNumPackagesAllowedByAppOps()292     public int getNumPackagesAllowedByAppOps() {
293         SparseArray<ArrayMap<String, PermissionState>> entries = getEntries();
294         if (entries == null) {
295             return 0;
296         }
297         loadPermissionsStates(entries);
298         loadAppOpsStates(entries);
299         final ArrayMap<String, PermissionState> entriesForProfile = entries.get(mUserManager
300                 .getUserHandle());
301         if (entriesForProfile == null) {
302             return 0;
303         }
304         Collection<PermissionState> permStates = entriesForProfile.values();
305         int result = 0;
306         for (PermissionState permState : permStates) {
307             if (permState.isPermissible()) {
308                 result++;
309             }
310         }
311         return result;
312     }
313 
314     public static class PermissionState {
315         public final String packageName;
316         public final UserHandle userHandle;
317         public PackageInfo packageInfo;
318         public boolean staticPermissionGranted;
319         public boolean permissionDeclared;
320         public int appOpMode;
321 
PermissionState(String packageName, UserHandle userHandle)322         public PermissionState(String packageName, UserHandle userHandle) {
323             this.packageName = packageName;
324             this.appOpMode = AppOpsManager.MODE_DEFAULT;
325             this.userHandle = userHandle;
326         }
327 
isPermissible()328         public boolean isPermissible() {
329             // defining the default behavior as permissible as long as the package requested this
330             // permission (this means pre-M gets approval during install time; M apps gets approval
331             // during runtime.
332             if (appOpMode == AppOpsManager.MODE_DEFAULT) {
333                 return staticPermissionGranted;
334             }
335             return appOpMode == AppOpsManager.MODE_ALLOWED;
336         }
337     }
338 }
339