Loading services/core/java/com/android/server/audio/AudioServerPermissionProvider.java +38 −47 Original line number Diff line number Diff line Loading @@ -38,7 +38,6 @@ import android.os.RemoteException; import android.os.Trace; import android.os.Process; import android.os.UserHandle; import android.util.ArraySet; import android.util.IntArray; import android.util.Log; Loading @@ -46,7 +45,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.media.permission.INativePermissionController; import com.android.media.permission.PermissionEnum; import com.android.media.permission.UidPackageState; import com.android.server.pm.pkg.PackageState; import com.android.media.permission.UidPackageState.PackageState; import java.util.Arrays; import java.util.Collection; Loading @@ -55,11 +54,8 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.function.BiPredicate; import java.util.function.Supplier; import java.util.stream.Collector; import java.util.stream.Collectors; /** Responsible for synchronizing system server permission state to the native audioserver. */ public class AudioServerPermissionProvider { Loading Loading @@ -108,7 +104,7 @@ public class AudioServerPermissionProvider { private INativePermissionController mDest; @GuardedBy("mLock") private final Map<Integer, Set<String>> mPackageMap; private final Map<Integer, Map<String, PackageState>> mPackageMap; // Values are sorted @GuardedBy("mLock") Loading @@ -121,10 +117,13 @@ public class AudioServerPermissionProvider { private int mHdsUid = -1; /** * @param appInfos - PackageState for all apps on the device, used to populate init state * @param packageMap - Map from app-ids to Map of packageNames to PackageState (also containing * name) * @param permissionPredicate - Check if a UID holds an android permission (string) * @param userIdSupplier - Return all users (not uids) on the device, which apps can run in */ public AudioServerPermissionProvider( Collection<PackageState> appInfos, Map<Integer, Map<String, PackageState>> packageMap, BiPredicate<Integer, String> permissionPredicate, Supplier<int[]> userIdSupplier) { for (int i = 0; i < PermissionEnum.ENUM_SIZE; i++) { Loading @@ -133,7 +132,7 @@ public class AudioServerPermissionProvider { mUserIdSupplier = userIdSupplier; mPermissionPredicate = permissionPredicate; // Initialize the package state mPackageMap = generatePackageMappings(appInfos); mPackageMap = packageMap; } /** Loading Loading @@ -162,35 +161,47 @@ public class AudioServerPermissionProvider { } /** * Called when a package is added or removed * Called when a package is added, modified, or removed * * @param uid - uid of modified package (only app-id matters) * @param packageName - the (new) packageName * @param packageState - the (new) packageState * @param isRemove - true if the package is being removed, false if it is being added */ public void onModifyPackageState(int uid, String packageName, boolean isRemove) { public void onModifyPackageState(int uid, PackageState packageState, boolean isRemove) { // No point in maintaining package mappings for uids of different users uid = UserHandle.getAppId(uid); synchronized (mLock) { // Update state Set<String> packages; Map<String, PackageState> packages; if (!isRemove) { packages = mPackageMap.computeIfAbsent(uid, unused -> new ArraySet(1)); packages.add(packageName); packages = mPackageMap.computeIfAbsent(uid, unused -> new HashMap<>()); if (packageState.equals(packages.put(packageState.packageName, packageState))) { // no change return; } } else { packages = mPackageMap.get(uid); if (packages != null) { packages.remove(packageName); if (packages.isEmpty()) mPackageMap.remove(uid); if (packages.remove(packageState.packageName) == null) { // no change return; } if (packages.isEmpty()) { mPackageMap.remove(uid); } } else { // no change return; } } // Push state to destination if (mDest == null) { // Will re-sync when service is back online return; } var state = new UidPackageState(); state.uid = uid; state.packageNames = packages != null ? List.copyOf(packages) : Collections.emptyList(); state.packageStates = List.copyOf(packages.values()); try { mDest.updatePackagesForUid(state); } catch (RemoteException e) { Loading Loading @@ -230,10 +241,9 @@ public class AudioServerPermissionProvider { public void setIsolatedServiceUid(int uid, int owningUid) { synchronized (mLock) { if (mHdsUid == uid) return; var packageNameSet = mPackageMap.get(UserHandle.getAppId(owningUid)); if (packageNameSet != null) { var packageName = packageNameSet.iterator().next(); onModifyPackageState(uid, packageName, /* isRemove= */ false); var packages = mPackageMap.get(UserHandle.getAppId(owningUid)); if (packages != null) { var packageState = packages.values().iterator().next(); onModifyPackageState(uid, packageState, /* isRemove= */ false); } else { Log.wtf(TAG, "setIsolatedService owning uid not found"); } Loading Loading @@ -263,16 +273,16 @@ public class AudioServerPermissionProvider { public void clearIsolatedServiceUid(int uid) { synchronized (mLock) { var packageNameSet = mPackageMap.get(UserHandle.getAppId(uid)); var packages = mPackageMap.get(UserHandle.getAppId(uid)); if (mHdsUid != uid) { Log.wtf(TAG, "Unexpected isolated service uid cleared: " + uid + packageNameSet "Unexpected isolated service uid cleared: " + uid + packages + ", expected " + mHdsUid); return; } if (packageNameSet != null) { var packageName = packageNameSet.iterator().next(); onModifyPackageState(uid, packageName, /* isRemove= */ true); if (packages != null) { var packageState = packages.values().iterator().next(); onModifyPackageState(uid, packageState, /* isRemove= */ true); } else { Log.wtf(TAG, "clearIsolatedService uid not found"); } Loading Loading @@ -319,7 +329,7 @@ public class AudioServerPermissionProvider { entry -> { UidPackageState state = new UidPackageState(); state.uid = entry.getKey(); state.packageNames = List.copyOf(entry.getValue()); state.packageStates = List.copyOf(entry.getValue().values()); return state; }) .toList(); Loading Loading @@ -361,23 +371,4 @@ public class AudioServerPermissionProvider { Arrays.sort(unwrapped); return unwrapped; } /** * Aggregation operation on all package states list: groups by states by app-id and merges the * packageName for each state into an ArraySet. */ private static Map<Integer, Set<String>> generatePackageMappings( Collection<PackageState> appInfos) { Collector<PackageState, Object, Set<String>> reducer = Collectors.mapping( (PackageState p) -> p.getPackageName(), Collectors.toCollection(() -> new ArraySet(1))); return appInfos.stream() .collect( Collectors.groupingBy( /* predicate */ (PackageState p) -> p.getAppId(), /* factory */ HashMap::new, /* downstream collector */ reducer)); } } services/core/java/com/android/server/audio/AudioService.java +49 −5 Original line number Diff line number Diff line Loading @@ -32,6 +32,7 @@ import static android.Manifest.permission.QUERY_AUDIO_STATE; import static android.Manifest.permission.WRITE_SETTINGS; import static android.app.BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT; import static android.content.Intent.ACTION_PACKAGE_ADDED; import static android.content.Intent.ACTION_PACKAGE_REPLACED; import static android.content.Intent.EXTRA_ARCHIVAL; import static android.content.Intent.EXTRA_REPLACING; import static android.media.AudioDeviceInfo.TYPE_BLUETOOTH_A2DP; Loading Loading @@ -123,6 +124,7 @@ import android.content.IntentFilter; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.UserInfo; import android.content.res.Configuration; import android.content.res.Resources; Loading Loading @@ -266,6 +268,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.SomeArgs; import com.android.internal.util.DumpUtils; import com.android.internal.util.Preconditions; import com.android.media.permission.UidPackageState; import com.android.modules.expresslog.Counter; import com.android.server.EventLogTags; import com.android.server.LocalManagerRegistry; Loading Loading @@ -314,6 +317,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.BooleanSupplier; import java.util.stream.Collector; import java.util.stream.Collectors; /** Loading Loading @@ -13626,16 +13630,51 @@ public class AudioService extends IAudioService.Stub private static final String mMetricsId = MediaMetrics.Name.AUDIO_SERVICE + MediaMetrics.SEPARATOR; /* * Create AIDL defined package state for audioserver */ @VisibleForTesting static UidPackageState.PackageState makePackageState(PackageState p) { final var ret = new UidPackageState.PackageState(); ret.packageName = p.getPackageName(); ret.targetSdk = p.getTargetSdkVersion(); ret.isPlaybackCaptureAllowed = p.isAudioPlaybackCaptureAllowed(); return ret; } /** * Aggregation operation on all package states list: groups by states by app-id and merges the * packages per app-id into a Map keyed by the packageName. */ @VisibleForTesting static Map<Integer, Map<String, UidPackageState.PackageState>> generatePackageMap( Collection<PackageState> appInfos) { Collector<PackageState, Object, Map<String, UidPackageState.PackageState>> reducer = Collectors.toMap(p -> p.getPackageName(), /* key */ AudioService::makePackageState, /* val */ (x, y) -> x, /* mergeFunc, x,y should be identical */ () -> new ArrayMap(1) /* factory */); return appInfos.stream() .collect( Collectors.groupingBy( /* predicate */ (PackageState p) -> p.getAppId(), /* factory */ HashMap::new, /* downstream collector */ reducer)); } private static AudioServerPermissionProvider initializeAudioServerPermissionProvider( Context context, AudioPolicyFacade audioPolicy, Executor audioserverExecutor) { Collection<PackageState> packageStates = null; try (PackageManagerLocal.UnfilteredSnapshot snapshot = Map<Integer, Map<String, UidPackageState.PackageState>> packageStates = null; try (var snapshot = LocalManagerRegistry.getManager(PackageManagerLocal.class) .withUnfilteredSnapshot()) { packageStates = snapshot.getPackageStates().values(); packageStates = generatePackageMap(snapshot.getPackageStates().values()); } var umi = LocalServices.getService(UserManagerInternal.class); var pmsi = LocalServices.getService(PermissionManagerServiceInternal.class); var pmi = LocalServices.getService(PackageManagerInternal.class); var provider = new AudioServerPermissionProvider(packageStates, (Integer uid, String perm) -> ActivityManager.checkComponentPermission(perm, uid, /* owningUid = */ -1, /* exported */true) Loading @@ -13650,6 +13689,7 @@ public class AudioService extends IAudioService.Stub IntentFilter packageUpdateFilter = new IntentFilter(); packageUpdateFilter.addAction(ACTION_PACKAGE_ADDED); packageUpdateFilter.addAction(ACTION_PACKAGE_REPLACED); packageUpdateFilter.addDataScheme("package"); context.registerReceiverForAllUsers(new BroadcastReceiver() { Loading @@ -13662,9 +13702,13 @@ public class AudioService extends IAudioService.Stub intent.getBooleanExtra(EXTRA_REPLACING, false) + " archival: " + intent.getBooleanExtra(EXTRA_ARCHIVAL, false) + " for package " + pkgName + " with uid " + uid); if (ACTION_PACKAGE_ADDED.equals(action)) { if (ACTION_PACKAGE_ADDED.equals(action) || ACTION_PACKAGE_REPLACED.equals(action)) { audioserverExecutor.execute(() -> provider.onModifyPackageState(uid, pkgName, false /* isRemoved */)); provider.onModifyPackageState( uid, makePackageState(pmi.getPackageStateInternal(pkgName)), false /* isRemoved */)); } } }, packageUpdateFilter, null, null); // main thread is fine, since dispatch on executor services/tests/servicestests/src/com/android/server/audio/AudioServerPermissionProviderTest.java +213 −130 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
services/core/java/com/android/server/audio/AudioServerPermissionProvider.java +38 −47 Original line number Diff line number Diff line Loading @@ -38,7 +38,6 @@ import android.os.RemoteException; import android.os.Trace; import android.os.Process; import android.os.UserHandle; import android.util.ArraySet; import android.util.IntArray; import android.util.Log; Loading @@ -46,7 +45,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.media.permission.INativePermissionController; import com.android.media.permission.PermissionEnum; import com.android.media.permission.UidPackageState; import com.android.server.pm.pkg.PackageState; import com.android.media.permission.UidPackageState.PackageState; import java.util.Arrays; import java.util.Collection; Loading @@ -55,11 +54,8 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.function.BiPredicate; import java.util.function.Supplier; import java.util.stream.Collector; import java.util.stream.Collectors; /** Responsible for synchronizing system server permission state to the native audioserver. */ public class AudioServerPermissionProvider { Loading Loading @@ -108,7 +104,7 @@ public class AudioServerPermissionProvider { private INativePermissionController mDest; @GuardedBy("mLock") private final Map<Integer, Set<String>> mPackageMap; private final Map<Integer, Map<String, PackageState>> mPackageMap; // Values are sorted @GuardedBy("mLock") Loading @@ -121,10 +117,13 @@ public class AudioServerPermissionProvider { private int mHdsUid = -1; /** * @param appInfos - PackageState for all apps on the device, used to populate init state * @param packageMap - Map from app-ids to Map of packageNames to PackageState (also containing * name) * @param permissionPredicate - Check if a UID holds an android permission (string) * @param userIdSupplier - Return all users (not uids) on the device, which apps can run in */ public AudioServerPermissionProvider( Collection<PackageState> appInfos, Map<Integer, Map<String, PackageState>> packageMap, BiPredicate<Integer, String> permissionPredicate, Supplier<int[]> userIdSupplier) { for (int i = 0; i < PermissionEnum.ENUM_SIZE; i++) { Loading @@ -133,7 +132,7 @@ public class AudioServerPermissionProvider { mUserIdSupplier = userIdSupplier; mPermissionPredicate = permissionPredicate; // Initialize the package state mPackageMap = generatePackageMappings(appInfos); mPackageMap = packageMap; } /** Loading Loading @@ -162,35 +161,47 @@ public class AudioServerPermissionProvider { } /** * Called when a package is added or removed * Called when a package is added, modified, or removed * * @param uid - uid of modified package (only app-id matters) * @param packageName - the (new) packageName * @param packageState - the (new) packageState * @param isRemove - true if the package is being removed, false if it is being added */ public void onModifyPackageState(int uid, String packageName, boolean isRemove) { public void onModifyPackageState(int uid, PackageState packageState, boolean isRemove) { // No point in maintaining package mappings for uids of different users uid = UserHandle.getAppId(uid); synchronized (mLock) { // Update state Set<String> packages; Map<String, PackageState> packages; if (!isRemove) { packages = mPackageMap.computeIfAbsent(uid, unused -> new ArraySet(1)); packages.add(packageName); packages = mPackageMap.computeIfAbsent(uid, unused -> new HashMap<>()); if (packageState.equals(packages.put(packageState.packageName, packageState))) { // no change return; } } else { packages = mPackageMap.get(uid); if (packages != null) { packages.remove(packageName); if (packages.isEmpty()) mPackageMap.remove(uid); if (packages.remove(packageState.packageName) == null) { // no change return; } if (packages.isEmpty()) { mPackageMap.remove(uid); } } else { // no change return; } } // Push state to destination if (mDest == null) { // Will re-sync when service is back online return; } var state = new UidPackageState(); state.uid = uid; state.packageNames = packages != null ? List.copyOf(packages) : Collections.emptyList(); state.packageStates = List.copyOf(packages.values()); try { mDest.updatePackagesForUid(state); } catch (RemoteException e) { Loading Loading @@ -230,10 +241,9 @@ public class AudioServerPermissionProvider { public void setIsolatedServiceUid(int uid, int owningUid) { synchronized (mLock) { if (mHdsUid == uid) return; var packageNameSet = mPackageMap.get(UserHandle.getAppId(owningUid)); if (packageNameSet != null) { var packageName = packageNameSet.iterator().next(); onModifyPackageState(uid, packageName, /* isRemove= */ false); var packages = mPackageMap.get(UserHandle.getAppId(owningUid)); if (packages != null) { var packageState = packages.values().iterator().next(); onModifyPackageState(uid, packageState, /* isRemove= */ false); } else { Log.wtf(TAG, "setIsolatedService owning uid not found"); } Loading Loading @@ -263,16 +273,16 @@ public class AudioServerPermissionProvider { public void clearIsolatedServiceUid(int uid) { synchronized (mLock) { var packageNameSet = mPackageMap.get(UserHandle.getAppId(uid)); var packages = mPackageMap.get(UserHandle.getAppId(uid)); if (mHdsUid != uid) { Log.wtf(TAG, "Unexpected isolated service uid cleared: " + uid + packageNameSet "Unexpected isolated service uid cleared: " + uid + packages + ", expected " + mHdsUid); return; } if (packageNameSet != null) { var packageName = packageNameSet.iterator().next(); onModifyPackageState(uid, packageName, /* isRemove= */ true); if (packages != null) { var packageState = packages.values().iterator().next(); onModifyPackageState(uid, packageState, /* isRemove= */ true); } else { Log.wtf(TAG, "clearIsolatedService uid not found"); } Loading Loading @@ -319,7 +329,7 @@ public class AudioServerPermissionProvider { entry -> { UidPackageState state = new UidPackageState(); state.uid = entry.getKey(); state.packageNames = List.copyOf(entry.getValue()); state.packageStates = List.copyOf(entry.getValue().values()); return state; }) .toList(); Loading Loading @@ -361,23 +371,4 @@ public class AudioServerPermissionProvider { Arrays.sort(unwrapped); return unwrapped; } /** * Aggregation operation on all package states list: groups by states by app-id and merges the * packageName for each state into an ArraySet. */ private static Map<Integer, Set<String>> generatePackageMappings( Collection<PackageState> appInfos) { Collector<PackageState, Object, Set<String>> reducer = Collectors.mapping( (PackageState p) -> p.getPackageName(), Collectors.toCollection(() -> new ArraySet(1))); return appInfos.stream() .collect( Collectors.groupingBy( /* predicate */ (PackageState p) -> p.getAppId(), /* factory */ HashMap::new, /* downstream collector */ reducer)); } }
services/core/java/com/android/server/audio/AudioService.java +49 −5 Original line number Diff line number Diff line Loading @@ -32,6 +32,7 @@ import static android.Manifest.permission.QUERY_AUDIO_STATE; import static android.Manifest.permission.WRITE_SETTINGS; import static android.app.BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT; import static android.content.Intent.ACTION_PACKAGE_ADDED; import static android.content.Intent.ACTION_PACKAGE_REPLACED; import static android.content.Intent.EXTRA_ARCHIVAL; import static android.content.Intent.EXTRA_REPLACING; import static android.media.AudioDeviceInfo.TYPE_BLUETOOTH_A2DP; Loading Loading @@ -123,6 +124,7 @@ import android.content.IntentFilter; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.UserInfo; import android.content.res.Configuration; import android.content.res.Resources; Loading Loading @@ -266,6 +268,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.SomeArgs; import com.android.internal.util.DumpUtils; import com.android.internal.util.Preconditions; import com.android.media.permission.UidPackageState; import com.android.modules.expresslog.Counter; import com.android.server.EventLogTags; import com.android.server.LocalManagerRegistry; Loading Loading @@ -314,6 +317,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.BooleanSupplier; import java.util.stream.Collector; import java.util.stream.Collectors; /** Loading Loading @@ -13626,16 +13630,51 @@ public class AudioService extends IAudioService.Stub private static final String mMetricsId = MediaMetrics.Name.AUDIO_SERVICE + MediaMetrics.SEPARATOR; /* * Create AIDL defined package state for audioserver */ @VisibleForTesting static UidPackageState.PackageState makePackageState(PackageState p) { final var ret = new UidPackageState.PackageState(); ret.packageName = p.getPackageName(); ret.targetSdk = p.getTargetSdkVersion(); ret.isPlaybackCaptureAllowed = p.isAudioPlaybackCaptureAllowed(); return ret; } /** * Aggregation operation on all package states list: groups by states by app-id and merges the * packages per app-id into a Map keyed by the packageName. */ @VisibleForTesting static Map<Integer, Map<String, UidPackageState.PackageState>> generatePackageMap( Collection<PackageState> appInfos) { Collector<PackageState, Object, Map<String, UidPackageState.PackageState>> reducer = Collectors.toMap(p -> p.getPackageName(), /* key */ AudioService::makePackageState, /* val */ (x, y) -> x, /* mergeFunc, x,y should be identical */ () -> new ArrayMap(1) /* factory */); return appInfos.stream() .collect( Collectors.groupingBy( /* predicate */ (PackageState p) -> p.getAppId(), /* factory */ HashMap::new, /* downstream collector */ reducer)); } private static AudioServerPermissionProvider initializeAudioServerPermissionProvider( Context context, AudioPolicyFacade audioPolicy, Executor audioserverExecutor) { Collection<PackageState> packageStates = null; try (PackageManagerLocal.UnfilteredSnapshot snapshot = Map<Integer, Map<String, UidPackageState.PackageState>> packageStates = null; try (var snapshot = LocalManagerRegistry.getManager(PackageManagerLocal.class) .withUnfilteredSnapshot()) { packageStates = snapshot.getPackageStates().values(); packageStates = generatePackageMap(snapshot.getPackageStates().values()); } var umi = LocalServices.getService(UserManagerInternal.class); var pmsi = LocalServices.getService(PermissionManagerServiceInternal.class); var pmi = LocalServices.getService(PackageManagerInternal.class); var provider = new AudioServerPermissionProvider(packageStates, (Integer uid, String perm) -> ActivityManager.checkComponentPermission(perm, uid, /* owningUid = */ -1, /* exported */true) Loading @@ -13650,6 +13689,7 @@ public class AudioService extends IAudioService.Stub IntentFilter packageUpdateFilter = new IntentFilter(); packageUpdateFilter.addAction(ACTION_PACKAGE_ADDED); packageUpdateFilter.addAction(ACTION_PACKAGE_REPLACED); packageUpdateFilter.addDataScheme("package"); context.registerReceiverForAllUsers(new BroadcastReceiver() { Loading @@ -13662,9 +13702,13 @@ public class AudioService extends IAudioService.Stub intent.getBooleanExtra(EXTRA_REPLACING, false) + " archival: " + intent.getBooleanExtra(EXTRA_ARCHIVAL, false) + " for package " + pkgName + " with uid " + uid); if (ACTION_PACKAGE_ADDED.equals(action)) { if (ACTION_PACKAGE_ADDED.equals(action) || ACTION_PACKAGE_REPLACED.equals(action)) { audioserverExecutor.execute(() -> provider.onModifyPackageState(uid, pkgName, false /* isRemoved */)); provider.onModifyPackageState( uid, makePackageState(pmi.getPackageStateInternal(pkgName)), false /* isRemoved */)); } } }, packageUpdateFilter, null, null); // main thread is fine, since dispatch on executor
services/tests/servicestests/src/com/android/server/audio/AudioServerPermissionProviderTest.java +213 −130 File changed.Preview size limit exceeded, changes collapsed. Show changes