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