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.server.audio; 18 19 import static android.Manifest.permission.CALL_AUDIO_INTERCEPTION; 20 import static android.Manifest.permission.MODIFY_AUDIO_ROUTING; 21 import static android.Manifest.permission.MODIFY_PHONE_STATE; 22 import static android.Manifest.permission.RECORD_AUDIO; 23 24 import android.annotation.Nullable; 25 import android.os.RemoteException; 26 import android.os.UserHandle; 27 import android.util.ArraySet; 28 import android.util.IntArray; 29 30 import com.android.internal.annotations.GuardedBy; 31 import com.android.media.permission.INativePermissionController; 32 import com.android.media.permission.PermissionEnum; 33 import com.android.media.permission.UidPackageState; 34 import com.android.server.pm.pkg.PackageState; 35 36 import java.util.Arrays; 37 import java.util.Collection; 38 import java.util.Collections; 39 import java.util.HashMap; 40 import java.util.List; 41 import java.util.Map; 42 import java.util.Objects; 43 import java.util.Set; 44 import java.util.function.BiPredicate; 45 import java.util.function.Supplier; 46 import java.util.stream.Collector; 47 import java.util.stream.Collectors; 48 49 /** Responsible for synchronizing system server permission state to the native audioserver. */ 50 public class AudioServerPermissionProvider { 51 52 static final String[] MONITORED_PERMS = new String[PermissionEnum.ENUM_SIZE]; 53 54 static { 55 MONITORED_PERMS[PermissionEnum.MODIFY_AUDIO_ROUTING] = MODIFY_AUDIO_ROUTING; 56 MONITORED_PERMS[PermissionEnum.MODIFY_PHONE_STATE] = MODIFY_PHONE_STATE; 57 MONITORED_PERMS[PermissionEnum.RECORD_AUDIO] = RECORD_AUDIO; 58 MONITORED_PERMS[PermissionEnum.CALL_AUDIO_INTERCEPTION] = CALL_AUDIO_INTERCEPTION; 59 } 60 61 private final Object mLock = new Object(); 62 private final Supplier<int[]> mUserIdSupplier; 63 private final BiPredicate<Integer, String> mPermissionPredicate; 64 65 @GuardedBy("mLock") 66 private INativePermissionController mDest; 67 68 @GuardedBy("mLock") 69 private final Map<Integer, Set<String>> mPackageMap; 70 // Values are sorted 71 @GuardedBy("mLock") 72 private final int[][] mPermMap = new int[PermissionEnum.ENUM_SIZE][]; 73 74 @GuardedBy("mLock") 75 private boolean mIsUpdateDeferred = true; 76 77 /** 78 * @param appInfos - PackageState for all apps on the device, used to populate init state 79 */ AudioServerPermissionProvider( Collection<PackageState> appInfos, BiPredicate<Integer, String> permissionPredicate, Supplier<int[]> userIdSupplier)80 public AudioServerPermissionProvider( 81 Collection<PackageState> appInfos, 82 BiPredicate<Integer, String> permissionPredicate, 83 Supplier<int[]> userIdSupplier) { 84 for (int i = 0; i < PermissionEnum.ENUM_SIZE; i++) { 85 Objects.requireNonNull(MONITORED_PERMS[i]); 86 } 87 mUserIdSupplier = userIdSupplier; 88 mPermissionPredicate = permissionPredicate; 89 // Initialize the package state 90 mPackageMap = generatePackageMappings(appInfos); 91 } 92 93 /** 94 * Called whenever audioserver starts (or started before us) 95 * 96 * @param pc - The permission controller interface from audioserver, which we push updates to 97 */ onServiceStart(@ullable INativePermissionController pc)98 public void onServiceStart(@Nullable INativePermissionController pc) { 99 if (pc == null) return; 100 synchronized (mLock) { 101 mDest = pc; 102 resetNativePackageState(); 103 try { 104 for (byte i = 0; i < PermissionEnum.ENUM_SIZE; i++) { 105 if (mIsUpdateDeferred) { 106 mPermMap[i] = getUidsHoldingPerm(MONITORED_PERMS[i]); 107 } 108 mDest.populatePermissionState(i, mPermMap[i]); 109 } 110 mIsUpdateDeferred = false; 111 } catch (RemoteException e) { 112 // We will re-init the state when the service comes back up 113 mDest = null; 114 } 115 } 116 } 117 118 /** 119 * Called when a package is added or removed 120 * 121 * @param uid - uid of modified package (only app-id matters) 122 * @param packageName - the (new) packageName 123 * @param isRemove - true if the package is being removed, false if it is being added 124 */ onModifyPackageState(int uid, String packageName, boolean isRemove)125 public void onModifyPackageState(int uid, String packageName, boolean isRemove) { 126 // No point in maintaining package mappings for uids of different users 127 uid = UserHandle.getAppId(uid); 128 synchronized (mLock) { 129 // Update state 130 Set<String> packages; 131 if (!isRemove) { 132 packages = mPackageMap.computeIfAbsent(uid, unused -> new ArraySet(1)); 133 packages.add(packageName); 134 } else { 135 packages = mPackageMap.get(uid); 136 if (packages != null) { 137 packages.remove(packageName); 138 if (packages.isEmpty()) mPackageMap.remove(uid); 139 } 140 } 141 // Push state to destination 142 if (mDest == null) { 143 return; 144 } 145 var state = new UidPackageState(); 146 state.uid = uid; 147 state.packageNames = packages != null ? List.copyOf(packages) : Collections.emptyList(); 148 try { 149 mDest.updatePackagesForUid(state); 150 } catch (RemoteException e) { 151 // We will re-init the state when the service comes back up 152 mDest = null; 153 } 154 } 155 } 156 157 /** Called whenever any package/permission changes occur which invalidate uids holding perms */ onPermissionStateChanged()158 public void onPermissionStateChanged() { 159 synchronized (mLock) { 160 if (mDest == null) { 161 mIsUpdateDeferred = true; 162 return; 163 } 164 try { 165 for (byte i = 0; i < PermissionEnum.ENUM_SIZE; i++) { 166 var newPerms = getUidsHoldingPerm(MONITORED_PERMS[i]); 167 if (!Arrays.equals(newPerms, mPermMap[i])) { 168 mPermMap[i] = newPerms; 169 mDest.populatePermissionState(i, newPerms); 170 } 171 } 172 } catch (RemoteException e) { 173 // We will re-init the state when the service comes back up 174 mDest = null; 175 // We didn't necessarily finish 176 mIsUpdateDeferred = true; 177 } 178 } 179 } 180 181 /** Called when full syncing package state to audioserver. */ 182 @GuardedBy("mLock") resetNativePackageState()183 private void resetNativePackageState() { 184 if (mDest == null) return; 185 List<UidPackageState> states = 186 mPackageMap.entrySet().stream() 187 .map( 188 entry -> { 189 UidPackageState state = new UidPackageState(); 190 state.uid = entry.getKey(); 191 state.packageNames = List.copyOf(entry.getValue()); 192 return state; 193 }) 194 .toList(); 195 try { 196 mDest.populatePackagesForUids(states); 197 } catch (RemoteException e) { 198 // We will re-init the state when the service comes back up 199 mDest = null; 200 } 201 } 202 203 @GuardedBy("mLock") 204 /** Return all uids (not app-ids) which currently hold a given permission. Not app-op aware */ getUidsHoldingPerm(String perm)205 private int[] getUidsHoldingPerm(String perm) { 206 IntArray acc = new IntArray(); 207 for (int userId : mUserIdSupplier.get()) { 208 for (int appId : mPackageMap.keySet()) { 209 int uid = UserHandle.getUid(userId, appId); 210 if (mPermissionPredicate.test(uid, perm)) { 211 acc.add(uid); 212 } 213 } 214 } 215 var unwrapped = acc.toArray(); 216 Arrays.sort(unwrapped); 217 return unwrapped; 218 } 219 220 /** 221 * Aggregation operation on all package states list: groups by states by app-id and merges the 222 * packageName for each state into an ArraySet. 223 */ generatePackageMappings( Collection<PackageState> appInfos)224 private static Map<Integer, Set<String>> generatePackageMappings( 225 Collection<PackageState> appInfos) { 226 Collector<PackageState, Object, Set<String>> reducer = 227 Collectors.mapping( 228 (PackageState p) -> p.getPackageName(), 229 Collectors.toCollection(() -> new ArraySet(1))); 230 231 return appInfos.stream() 232 .collect( 233 Collectors.groupingBy( 234 /* predicate */ (PackageState p) -> p.getAppId(), 235 /* factory */ HashMap::new, 236 /* downstream collector */ reducer)); 237 } 238 } 239