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