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.ACCESS_ULTRASOUND; 20 import static android.Manifest.permission.BLUETOOTH_CONNECT; 21 import static android.Manifest.permission.CALL_AUDIO_INTERCEPTION; 22 import static android.Manifest.permission.CAPTURE_AUDIO_HOTWORD; 23 import static android.Manifest.permission.CAPTURE_AUDIO_OUTPUT; 24 import static android.Manifest.permission.CAPTURE_MEDIA_OUTPUT; 25 import static android.Manifest.permission.CAPTURE_TUNER_AUDIO_INPUT; 26 import static android.Manifest.permission.CAPTURE_VOICE_COMMUNICATION_OUTPUT; 27 import static android.Manifest.permission.BYPASS_CONCURRENT_RECORD_AUDIO_RESTRICTION; 28 import static android.Manifest.permission.MODIFY_AUDIO_ROUTING; 29 import static android.Manifest.permission.MODIFY_AUDIO_SETTINGS; 30 import static android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED; 31 import static android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS; 32 import static android.Manifest.permission.MODIFY_PHONE_STATE; 33 import static android.Manifest.permission.RECORD_AUDIO; 34 import static android.Manifest.permission.WRITE_SECURE_SETTINGS; 35 36 import android.annotation.Nullable; 37 import android.os.RemoteException; 38 import android.os.Trace; 39 import android.os.UserHandle; 40 import android.util.ArraySet; 41 import android.util.IntArray; 42 import android.util.Log; 43 44 import com.android.internal.annotations.GuardedBy; 45 import com.android.media.permission.INativePermissionController; 46 import com.android.media.permission.PermissionEnum; 47 import com.android.media.permission.UidPackageState; 48 import com.android.server.pm.pkg.PackageState; 49 50 import java.util.Arrays; 51 import java.util.Collection; 52 import java.util.Collections; 53 import java.util.HashMap; 54 import java.util.List; 55 import java.util.Map; 56 import java.util.Objects; 57 import java.util.Set; 58 import java.util.function.BiPredicate; 59 import java.util.function.Supplier; 60 import java.util.stream.Collector; 61 import java.util.stream.Collectors; 62 63 /** Responsible for synchronizing system server permission state to the native audioserver. */ 64 public class AudioServerPermissionProvider { 65 66 static final String TAG = "AudioServerPermissionProvider"; 67 68 static final String[] MONITORED_PERMS = new String[PermissionEnum.ENUM_SIZE]; 69 70 static final byte[] HDS_PERMS = new byte[] {PermissionEnum.CAPTURE_AUDIO_HOTWORD, 71 PermissionEnum.CAPTURE_AUDIO_OUTPUT, PermissionEnum.RECORD_AUDIO}; 72 73 static { 74 MONITORED_PERMS[PermissionEnum.RECORD_AUDIO] = RECORD_AUDIO; 75 MONITORED_PERMS[PermissionEnum.MODIFY_AUDIO_ROUTING] = MODIFY_AUDIO_ROUTING; 76 MONITORED_PERMS[PermissionEnum.MODIFY_AUDIO_SETTINGS] = MODIFY_AUDIO_SETTINGS; 77 MONITORED_PERMS[PermissionEnum.MODIFY_PHONE_STATE] = MODIFY_PHONE_STATE; 78 MONITORED_PERMS[PermissionEnum.MODIFY_DEFAULT_AUDIO_EFFECTS] = MODIFY_DEFAULT_AUDIO_EFFECTS; 79 MONITORED_PERMS[PermissionEnum.WRITE_SECURE_SETTINGS] = WRITE_SECURE_SETTINGS; 80 MONITORED_PERMS[PermissionEnum.CALL_AUDIO_INTERCEPTION] = CALL_AUDIO_INTERCEPTION; 81 MONITORED_PERMS[PermissionEnum.ACCESS_ULTRASOUND] = ACCESS_ULTRASOUND; 82 MONITORED_PERMS[PermissionEnum.CAPTURE_AUDIO_OUTPUT] = CAPTURE_AUDIO_OUTPUT; 83 MONITORED_PERMS[PermissionEnum.CAPTURE_MEDIA_OUTPUT] = CAPTURE_MEDIA_OUTPUT; 84 MONITORED_PERMS[PermissionEnum.CAPTURE_AUDIO_HOTWORD] = CAPTURE_AUDIO_HOTWORD; 85 MONITORED_PERMS[PermissionEnum.CAPTURE_TUNER_AUDIO_INPUT] = CAPTURE_TUNER_AUDIO_INPUT; 86 MONITORED_PERMS[PermissionEnum.CAPTURE_VOICE_COMMUNICATION_OUTPUT] = 87 CAPTURE_VOICE_COMMUNICATION_OUTPUT; 88 MONITORED_PERMS[PermissionEnum.BLUETOOTH_CONNECT] = BLUETOOTH_CONNECT; 89 MONITORED_PERMS[PermissionEnum.BYPASS_CONCURRENT_RECORD_AUDIO_RESTRICTION] = 90 BYPASS_CONCURRENT_RECORD_AUDIO_RESTRICTION; 91 MONITORED_PERMS[PermissionEnum.MODIFY_AUDIO_SETTINGS_PRIVILEGED] = 92 MODIFY_AUDIO_SETTINGS_PRIVILEGED; 93 } 94 95 private final Object mLock = new Object(); 96 private final Supplier<int[]> mUserIdSupplier; 97 private final BiPredicate<Integer, String> mPermissionPredicate; 98 99 @GuardedBy("mLock") 100 private INativePermissionController mDest; 101 102 @GuardedBy("mLock") 103 private final Map<Integer, Set<String>> mPackageMap; 104 105 // Values are sorted 106 @GuardedBy("mLock") 107 private final int[][] mPermMap = new int[PermissionEnum.ENUM_SIZE][]; 108 109 @GuardedBy("mLock") 110 private boolean mIsUpdateDeferred = true; 111 112 @GuardedBy("mLock") 113 private int mHdsUid = -1; 114 115 /** 116 * @param appInfos - PackageState for all apps on the device, used to populate init state 117 */ AudioServerPermissionProvider( Collection<PackageState> appInfos, BiPredicate<Integer, String> permissionPredicate, Supplier<int[]> userIdSupplier)118 public AudioServerPermissionProvider( 119 Collection<PackageState> appInfos, 120 BiPredicate<Integer, String> permissionPredicate, 121 Supplier<int[]> userIdSupplier) { 122 for (int i = 0; i < PermissionEnum.ENUM_SIZE; i++) { 123 Objects.requireNonNull(MONITORED_PERMS[i]); 124 } 125 mUserIdSupplier = userIdSupplier; 126 mPermissionPredicate = permissionPredicate; 127 // Initialize the package state 128 mPackageMap = generatePackageMappings(appInfos); 129 } 130 131 /** 132 * Called whenever audioserver starts (or started before us) 133 * 134 * @param pc - The permission controller interface from audioserver, which we push updates to 135 */ onServiceStart(@ullable INativePermissionController pc)136 public void onServiceStart(@Nullable INativePermissionController pc) { 137 if (pc == null) return; 138 synchronized (mLock) { 139 mDest = pc; 140 resetNativePackageState(); 141 try { 142 for (byte i = 0; i < PermissionEnum.ENUM_SIZE; i++) { 143 if (mIsUpdateDeferred) { 144 mPermMap[i] = getUidsHoldingPerm(i); 145 } 146 mDest.populatePermissionState(i, mPermMap[i]); 147 } 148 mIsUpdateDeferred = false; 149 } catch (RemoteException e) { 150 // We will re-init the state when the service comes back up 151 mDest = null; 152 } 153 } 154 } 155 156 /** 157 * Called when a package is added or removed 158 * 159 * @param uid - uid of modified package (only app-id matters) 160 * @param packageName - the (new) packageName 161 * @param isRemove - true if the package is being removed, false if it is being added 162 */ onModifyPackageState(int uid, String packageName, boolean isRemove)163 public void onModifyPackageState(int uid, String packageName, boolean isRemove) { 164 // No point in maintaining package mappings for uids of different users 165 uid = UserHandle.getAppId(uid); 166 synchronized (mLock) { 167 // Update state 168 Set<String> packages; 169 if (!isRemove) { 170 packages = mPackageMap.computeIfAbsent(uid, unused -> new ArraySet(1)); 171 packages.add(packageName); 172 } else { 173 packages = mPackageMap.get(uid); 174 if (packages != null) { 175 packages.remove(packageName); 176 if (packages.isEmpty()) mPackageMap.remove(uid); 177 } 178 } 179 // Push state to destination 180 if (mDest == null) { 181 return; 182 } 183 var state = new UidPackageState(); 184 state.uid = uid; 185 state.packageNames = packages != null ? List.copyOf(packages) : Collections.emptyList(); 186 try { 187 mDest.updatePackagesForUid(state); 188 } catch (RemoteException e) { 189 // We will re-init the state when the service comes back up 190 mDest = null; 191 } 192 } 193 } 194 195 /** Called whenever any package/permission changes occur which invalidate uids holding perms */ onPermissionStateChanged()196 public void onPermissionStateChanged() { 197 synchronized (mLock) { 198 if (mDest == null) { 199 mIsUpdateDeferred = true; 200 return; 201 } 202 Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "audioserver_permission_update"); 203 try { 204 for (byte i = 0; i < PermissionEnum.ENUM_SIZE; i++) { 205 var newPerms = getUidsHoldingPerm(i); 206 if (!Arrays.equals(newPerms, mPermMap[i])) { 207 mPermMap[i] = newPerms; 208 mDest.populatePermissionState(i, newPerms); 209 } 210 } 211 } catch (RemoteException e) { 212 // We will re-init the state when the service comes back up 213 mDest = null; 214 // We didn't necessarily finish 215 mIsUpdateDeferred = true; 216 } finally { 217 Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER); 218 } 219 } 220 } 221 setIsolatedServiceUid(int uid, int owningUid)222 public void setIsolatedServiceUid(int uid, int owningUid) { 223 synchronized (mLock) { 224 if (mHdsUid == uid) return; 225 var packageNameSet = mPackageMap.get(UserHandle.getAppId(owningUid)); 226 if (packageNameSet != null) { 227 var packageName = packageNameSet.iterator().next(); 228 onModifyPackageState(uid, packageName, /* isRemove= */ false); 229 } else { 230 Log.wtf(TAG, "setIsolatedService owning uid not found"); 231 } 232 // permissions 233 mHdsUid = uid; 234 if (mDest == null) { 235 mIsUpdateDeferred = true; 236 return; 237 } 238 try { 239 for (byte perm : HDS_PERMS) { 240 int[] newPerms = new int[mPermMap[perm].length + 1]; 241 System.arraycopy(mPermMap[perm], 0, newPerms, 0, mPermMap[perm].length); 242 newPerms[newPerms.length - 1] = mHdsUid; 243 Arrays.sort(newPerms); 244 mPermMap[perm] = newPerms; 245 mDest.populatePermissionState(perm, newPerms); 246 } 247 } catch (RemoteException e) { 248 // We will re-init the state when the service comes back up 249 mDest = null; 250 // We didn't necessarily finish 251 mIsUpdateDeferred = true; 252 } 253 } 254 } 255 clearIsolatedServiceUid(int uid)256 public void clearIsolatedServiceUid(int uid) { 257 synchronized (mLock) { 258 var packageNameSet = mPackageMap.get(UserHandle.getAppId(uid)); 259 if (mHdsUid != uid) { 260 Log.wtf(TAG, 261 "Unexpected isolated service uid cleared: " + uid + packageNameSet 262 + ", expected " + mHdsUid); 263 return; 264 } 265 if (packageNameSet != null) { 266 var packageName = packageNameSet.iterator().next(); 267 onModifyPackageState(uid, packageName, /* isRemove= */ true); 268 } else { 269 Log.wtf(TAG, "clearIsolatedService uid not found"); 270 } 271 // permissions 272 if (mDest == null) { 273 mIsUpdateDeferred = true; 274 return; 275 } 276 try { 277 for (byte perm : HDS_PERMS) { 278 int[] newPerms = new int[mPermMap[perm].length - 1]; 279 int ind = Arrays.binarySearch(mPermMap[perm], uid); 280 if (ind < 0) continue; 281 System.arraycopy(mPermMap[perm], 0, newPerms, 0, ind); 282 System.arraycopy(mPermMap[perm], ind + 1, newPerms, ind, 283 mPermMap[perm].length - ind - 1); 284 mPermMap[perm] = newPerms; 285 mDest.populatePermissionState(perm, newPerms); 286 } 287 } catch (RemoteException e) { 288 // We will re-init the state when the service comes back up 289 mDest = null; 290 // We didn't necessarily finish 291 mIsUpdateDeferred = true; 292 } 293 mHdsUid = -1; 294 } 295 } 296 isSpecialHdsPermission(int perm)297 private boolean isSpecialHdsPermission(int perm) { 298 for (var hdsPerm : HDS_PERMS) { 299 if (perm == hdsPerm) return true; 300 } 301 return false; 302 } 303 304 /** Called when full syncing package state to audioserver. */ 305 @GuardedBy("mLock") resetNativePackageState()306 private void resetNativePackageState() { 307 if (mDest == null) return; 308 List<UidPackageState> states = 309 mPackageMap.entrySet().stream() 310 .map( 311 entry -> { 312 UidPackageState state = new UidPackageState(); 313 state.uid = entry.getKey(); 314 state.packageNames = List.copyOf(entry.getValue()); 315 return state; 316 }) 317 .toList(); 318 try { 319 mDest.populatePackagesForUids(states); 320 } catch (RemoteException e) { 321 // We will re-init the state when the service comes back up 322 mDest = null; 323 } 324 } 325 326 @GuardedBy("mLock") 327 /** Return all uids (not app-ids) which currently hold a given permission. Not app-op aware */ getUidsHoldingPerm(int perm)328 private int[] getUidsHoldingPerm(int perm) { 329 IntArray acc = new IntArray(); 330 for (int userId : mUserIdSupplier.get()) { 331 for (int appId : mPackageMap.keySet()) { 332 int uid = UserHandle.getUid(userId, appId); 333 if (mPermissionPredicate.test(uid, MONITORED_PERMS[perm])) { 334 acc.add(uid); 335 } 336 } 337 } 338 if (isSpecialHdsPermission(perm) && mHdsUid != -1) { 339 acc.add(mHdsUid); 340 } 341 var unwrapped = acc.toArray(); 342 Arrays.sort(unwrapped); 343 return unwrapped; 344 } 345 346 /** 347 * Aggregation operation on all package states list: groups by states by app-id and merges the 348 * packageName for each state into an ArraySet. 349 */ generatePackageMappings( Collection<PackageState> appInfos)350 private static Map<Integer, Set<String>> generatePackageMappings( 351 Collection<PackageState> appInfos) { 352 Collector<PackageState, Object, Set<String>> reducer = 353 Collectors.mapping( 354 (PackageState p) -> p.getPackageName(), 355 Collectors.toCollection(() -> new ArraySet(1))); 356 357 return appInfos.stream() 358 .collect( 359 Collectors.groupingBy( 360 /* predicate */ (PackageState p) -> p.getAppId(), 361 /* factory */ HashMap::new, 362 /* downstream collector */ reducer)); 363 } 364 } 365