1 /* 2 * Copyright 2019 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.applications.specialaccess; 18 19 import android.app.AppGlobals; 20 import android.app.AppOpsManager; 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.SparseArray; 30 31 import androidx.annotation.VisibleForTesting; 32 33 import com.android.car.settings.common.Logger; 34 import com.android.internal.util.ArrayUtils; 35 import com.android.settingslib.applications.ApplicationsState.AppEntry; 36 37 import java.util.List; 38 import java.util.Map; 39 40 /** 41 * Bridges {@link AppOpsManager} app operation permission information into {@link 42 * AppEntry#extraInfo} as {@link PermissionState} objects. 43 */ 44 public class AppStateAppOpsBridge implements AppEntryListManager.ExtraInfoBridge { 45 46 private static final Logger LOG = new Logger(AppStateAppOpsBridge.class); 47 48 private final Context mContext; 49 private final IPackageManager mIPackageManager; 50 private final List<UserHandle> mProfiles; 51 private final AppOpsManager mAppOpsManager; 52 private final int mAppOpsOpCode; 53 private final String mPermission; 54 55 /** 56 * Constructor. 57 * 58 * @param appOpsOpCode the {@link AppOpsManager} op code constant to fetch information for. 59 * @param permission the {@link android.Manifest.permission} required to perform the 60 * operation. 61 */ AppStateAppOpsBridge(Context context, int appOpsOpCode, String permission)62 public AppStateAppOpsBridge(Context context, int appOpsOpCode, String permission) { 63 this(context, appOpsOpCode, permission, AppGlobals.getPackageManager()); 64 } 65 66 @VisibleForTesting AppStateAppOpsBridge(Context context, int appOpsOpCode, String permission, IPackageManager packageManager)67 AppStateAppOpsBridge(Context context, int appOpsOpCode, String permission, 68 IPackageManager packageManager) { 69 mContext = context; 70 mIPackageManager = packageManager; 71 mProfiles = UserManager.get(context).getUserProfiles(); 72 mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); 73 mAppOpsOpCode = appOpsOpCode; 74 mPermission = permission; 75 } 76 77 @Override loadExtraInfo(List<AppEntry> entries)78 public void loadExtraInfo(List<AppEntry> entries) { 79 SparseArray<Map<String, PermissionState>> packageToStatesMapByProfileId = 80 getPackageToStateMapsByProfileId(); 81 loadAppOpModes(packageToStatesMapByProfileId); 82 83 for (AppEntry entry : entries) { 84 Map<String, PermissionState> packageStatesMap = packageToStatesMapByProfileId.get( 85 UserHandle.getUserId(entry.info.uid)); 86 entry.extraInfo = (packageStatesMap != null) ? packageStatesMap.get( 87 entry.info.packageName) : null; 88 } 89 } 90 getPackageToStateMapsByProfileId()91 private SparseArray<Map<String, PermissionState>> getPackageToStateMapsByProfileId() { 92 SparseArray<Map<String, PermissionState>> entries = new SparseArray<>(); 93 try { 94 for (UserHandle profile : mProfiles) { 95 int profileId = profile.getIdentifier(); 96 List<PackageInfo> packageInfos = getPackageInfos(profileId); 97 Map<String, PermissionState> entriesForProfile = new ArrayMap<>(); 98 entries.put(profileId, entriesForProfile); 99 for (PackageInfo packageInfo : packageInfos) { 100 boolean isAvailable = mIPackageManager.isPackageAvailable( 101 packageInfo.packageName, 102 profileId); 103 if (shouldIgnorePackage(packageInfo) || !isAvailable) { 104 LOG.d("Ignoring " + packageInfo.packageName + " isAvailable=" 105 + isAvailable); 106 continue; 107 } 108 PermissionState newEntry = new PermissionState(); 109 newEntry.mRequestedPermissions = packageInfo.requestedPermissions; 110 entriesForProfile.put(packageInfo.packageName, newEntry); 111 } 112 } 113 } catch (RemoteException e) { 114 LOG.w("PackageManager is dead. Can't get list of packages requesting " 115 + mPermission, e); 116 } 117 return entries; 118 } 119 120 @SuppressWarnings("unchecked") // safe by specification. getPackageInfos(int profileId)121 private List<PackageInfo> getPackageInfos(int profileId) throws RemoteException { 122 return mIPackageManager.getPackagesHoldingPermissions(new String[]{mPermission}, 123 PackageManager.GET_PERMISSIONS, profileId).getList(); 124 } 125 shouldIgnorePackage(PackageInfo packageInfo)126 private boolean shouldIgnorePackage(PackageInfo packageInfo) { 127 return packageInfo.packageName.equals("android") 128 || packageInfo.packageName.equals(mContext.getPackageName()) 129 || !ArrayUtils.contains(packageInfo.requestedPermissions, mPermission); 130 } 131 132 /** Sets the {@link PermissionState#mAppOpMode} field. */ loadAppOpModes( SparseArray<Map<String, PermissionState>> packageToStateMapsByProfileId)133 private void loadAppOpModes( 134 SparseArray<Map<String, PermissionState>> packageToStateMapsByProfileId) { 135 // Find out which packages have been granted permission from AppOps. 136 List<AppOpsManager.PackageOps> packageOps = mAppOpsManager.getPackagesForOps( 137 new int[]{mAppOpsOpCode}); 138 if (packageOps == null) { 139 return; 140 } 141 for (AppOpsManager.PackageOps packageOp : packageOps) { 142 int userId = UserHandle.getUserId(packageOp.getUid()); 143 Map<String, PermissionState> packageStateMap = packageToStateMapsByProfileId.get( 144 userId); 145 if (packageStateMap == null) { 146 // Profile is not for the current user. 147 continue; 148 } 149 PermissionState permissionState = packageStateMap.get(packageOp.getPackageName()); 150 if (permissionState == null) { 151 LOG.w("AppOp permission exists for package " + packageOp.getPackageName() 152 + " of user " + userId + " but package doesn't exist or did not request " 153 + mPermission + " access"); 154 continue; 155 } 156 if (packageOp.getOps().size() < 1) { 157 LOG.w("No AppOps permission exists for package " + packageOp.getPackageName()); 158 continue; 159 } 160 permissionState.mAppOpMode = packageOp.getOps().get(0).getMode(); 161 } 162 } 163 164 /** 165 * Data class for use in {@link AppEntry#extraInfo} which indicates whether 166 * the app operation used to construct the data bridge is permitted for the associated 167 * application. 168 */ 169 public static class PermissionState { 170 private String[] mRequestedPermissions; 171 private int mAppOpMode = AppOpsManager.MODE_DEFAULT; 172 173 /** Returns {@code true} if the entry's application is allowed to perform the operation. */ isPermissible()174 public boolean isPermissible() { 175 // Default behavior is permissible as long as the package requested this permission. 176 if (mAppOpMode == AppOpsManager.MODE_DEFAULT) { 177 return true; 178 } 179 return mAppOpMode == AppOpsManager.MODE_ALLOWED; 180 } 181 182 /** Returns the permissions requested by the entry's application. */ getRequestedPermissions()183 public String[] getRequestedPermissions() { 184 return mRequestedPermissions; 185 } 186 } 187 } 188