Loading media/java/android/media/AudioManagerInternal.java +2 −1 Original line number Diff line number Diff line Loading @@ -44,8 +44,9 @@ public abstract class AudioManagerInternal { * Add the UID for a new assistant service * * @param uid UID of the newly available assistants * @param owningUid UID of the actual assistant app, if {@code uid} is a isolated proc */ public abstract void addAssistantServiceUid(int uid); public abstract void addAssistantServiceUid(int uid, int owningUid); /** * Remove the UID for an existing assistant service Loading services/core/java/com/android/server/audio/AudioServerPermissionProvider.java +86 −4 Original line number Diff line number Diff line Loading @@ -36,6 +36,7 @@ import android.os.RemoteException; import android.os.UserHandle; import android.util.ArraySet; import android.util.IntArray; import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.media.permission.INativePermissionController; Loading @@ -61,6 +62,9 @@ public class AudioServerPermissionProvider { static final String[] MONITORED_PERMS = new String[PermissionEnum.ENUM_SIZE]; static final byte[] HDS_PERMS = new byte[] {PermissionEnum.CAPTURE_AUDIO_HOTWORD, PermissionEnum.CAPTURE_AUDIO_OUTPUT, PermissionEnum.RECORD_AUDIO}; static { MONITORED_PERMS[PermissionEnum.RECORD_AUDIO] = RECORD_AUDIO; MONITORED_PERMS[PermissionEnum.MODIFY_AUDIO_ROUTING] = MODIFY_AUDIO_ROUTING; Loading Loading @@ -88,6 +92,7 @@ public class AudioServerPermissionProvider { @GuardedBy("mLock") private final Map<Integer, Set<String>> mPackageMap; // Values are sorted @GuardedBy("mLock") private final int[][] mPermMap = new int[PermissionEnum.ENUM_SIZE][]; Loading @@ -95,6 +100,9 @@ public class AudioServerPermissionProvider { @GuardedBy("mLock") private boolean mIsUpdateDeferred = true; @GuardedBy("mLock") private int mHdsUid = -1; /** * @param appInfos - PackageState for all apps on the device, used to populate init state */ Loading Loading @@ -124,7 +132,7 @@ public class AudioServerPermissionProvider { try { for (byte i = 0; i < PermissionEnum.ENUM_SIZE; i++) { if (mIsUpdateDeferred) { mPermMap[i] = getUidsHoldingPerm(MONITORED_PERMS[i]); mPermMap[i] = getUidsHoldingPerm(i); } mDest.populatePermissionState(i, mPermMap[i]); } Loading Loading @@ -184,7 +192,7 @@ public class AudioServerPermissionProvider { } try { for (byte i = 0; i < PermissionEnum.ENUM_SIZE; i++) { var newPerms = getUidsHoldingPerm(MONITORED_PERMS[i]); var newPerms = getUidsHoldingPerm(i); if (!Arrays.equals(newPerms, mPermMap[i])) { mPermMap[i] = newPerms; mDest.populatePermissionState(i, newPerms); Loading @@ -199,6 +207,77 @@ public class AudioServerPermissionProvider { } } public void setIsolatedServiceUid(int uid, int owningUid) { synchronized (mLock) { if (mHdsUid == uid) return; var packageNameSet = mPackageMap.get(owningUid); if (packageNameSet == null) return; var packageName = packageNameSet.iterator().next(); onModifyPackageState(uid, packageName, /* isRemove= */ false); // permissions mHdsUid = uid; if (mDest == null) { mIsUpdateDeferred = true; return; } try { for (byte perm : HDS_PERMS) { int[] newPerms = new int[mPermMap[perm].length + 1]; System.arraycopy(mPermMap[perm], 0, newPerms, 0, mPermMap[perm].length); newPerms[newPerms.length - 1] = mHdsUid; Arrays.sort(newPerms); mPermMap[perm] = newPerms; mDest.populatePermissionState(perm, newPerms); } } catch (RemoteException e) { // We will re-init the state when the service comes back up mDest = null; // We didn't necessarily finish mIsUpdateDeferred = true; } } } public void clearIsolatedServiceUid(int uid) { synchronized (mLock) { if (mHdsUid != uid) return; var packageNameSet = mPackageMap.get(uid); if (packageNameSet == null) return; var packageName = packageNameSet.iterator().next(); onModifyPackageState(uid, packageName, /* isRemove= */ true); // permissions if (mDest == null) { mIsUpdateDeferred = true; return; } try { for (byte perm : HDS_PERMS) { int[] newPerms = new int[mPermMap[perm].length - 1]; int ind = Arrays.binarySearch(mPermMap[perm], uid); if (ind < 0) continue; System.arraycopy(mPermMap[perm], 0, newPerms, 0, ind); System.arraycopy(mPermMap[perm], ind + 1, newPerms, ind, mPermMap[perm].length - ind - 1); mPermMap[perm] = newPerms; mDest.populatePermissionState(perm, newPerms); } } catch (RemoteException e) { // We will re-init the state when the service comes back up mDest = null; // We didn't necessarily finish mIsUpdateDeferred = true; } mHdsUid = -1; } } private boolean isSpecialHdsPermission(int perm) { for (var hdsPerm : HDS_PERMS) { if (perm == hdsPerm) return true; } return false; } /** Called when full syncing package state to audioserver. */ @GuardedBy("mLock") private void resetNativePackageState() { Loading @@ -223,16 +302,19 @@ public class AudioServerPermissionProvider { @GuardedBy("mLock") /** Return all uids (not app-ids) which currently hold a given permission. Not app-op aware */ private int[] getUidsHoldingPerm(String perm) { private int[] getUidsHoldingPerm(int perm) { IntArray acc = new IntArray(); for (int userId : mUserIdSupplier.get()) { for (int appId : mPackageMap.keySet()) { int uid = UserHandle.getUid(userId, appId); if (mPermissionPredicate.test(uid, perm)) { if (mPermissionPredicate.test(uid, MONITORED_PERMS[perm])) { acc.add(uid); } } } if (isSpecialHdsPermission(perm) && mHdsUid != -1) { acc.add(mHdsUid); } var unwrapped = acc.toArray(); Arrays.sort(unwrapped); return unwrapped; Loading services/core/java/com/android/server/audio/AudioService.java +10 −3 Original line number Diff line number Diff line Loading @@ -11967,8 +11967,9 @@ public class AudioService extends IAudioService.Stub var umi = LocalServices.getService(UserManagerInternal.class); var pmsi = LocalServices.getService(PermissionManagerServiceInternal.class); var provider = new AudioServerPermissionProvider(packageStates, (Integer uid, String perm) -> (pmsi.checkUidPermission(uid, perm, Context.DEVICE_ID_DEFAULT) == PackageManager.PERMISSION_GRANTED), (Integer uid, String perm) -> ActivityManager.checkComponentPermission(perm, uid, /* owningUid = */ -1, /* exported */true) == PackageManager.PERMISSION_GRANTED, () -> umi.getUserIds() ); audioPolicy.registerOnStartTask(() -> { Loading Loading @@ -12330,13 +12331,19 @@ public class AudioService extends IAudioService.Stub } @Override public void addAssistantServiceUid(int uid) { public void addAssistantServiceUid(int uid, int owningUid) { if (audioserverPermissions()) { mPermissionProvider.setIsolatedServiceUid(uid, owningUid); } sendMsg(mAudioHandler, MSG_ADD_ASSISTANT_SERVICE_UID, SENDMSG_QUEUE, uid, 0, null, 0); } @Override public void removeAssistantServiceUid(int uid) { if (audioserverPermissions()) { mPermissionProvider.clearIsolatedServiceUid(uid); } sendMsg(mAudioHandler, MSG_REMOVE_ASSISTANT_SERVICE_UID, SENDMSG_QUEUE, uid, 0, null, 0); } services/tests/servicestests/src/com/android/server/audio/AudioServerPermissionProviderTest.java +51 −0 Original line number Diff line number Diff line Loading @@ -37,6 +37,7 @@ import android.platform.test.annotations.Presubmit; import androidx.test.ext.junit.runners.AndroidJUnit4; 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; Loading Loading @@ -352,6 +353,56 @@ public final class AudioServerPermissionProviderTest { verify(mMockPc, times(MONITORED_PERMS.length)).populatePermissionState(anyByte(), any()); } @Test public void testSpecialHotwordPermissions() throws Exception { BiPredicate<Integer, String> customPermPred = mock(BiPredicate.class); var initPackageListData = List.of(mMockPackageStateOne_10000_one, mMockPackageStateTwo_10001_two); // expected state // PERM[CAPTURE_AUDIO_HOTWORD]: [10000] // PERM[CAPTURE_AUDIO_OUTPUT]: [10001] // PERM[RECORD_AUDIO]: [10001] // PERM[...]: [] when(customPermPred.test( eq(10000), eq(MONITORED_PERMS[PermissionEnum.CAPTURE_AUDIO_HOTWORD]))) .thenReturn(true); when(customPermPred.test( eq(10001), eq(MONITORED_PERMS[PermissionEnum.CAPTURE_AUDIO_OUTPUT]))) .thenReturn(true); when(customPermPred.test(eq(10001), eq(MONITORED_PERMS[PermissionEnum.RECORD_AUDIO]))) .thenReturn(true); mPermissionProvider = new AudioServerPermissionProvider( initPackageListData, customPermPred, () -> new int[] {0}); int HDS_UID = 99001; mPermissionProvider.onServiceStart(mMockPc); clearInvocations(mMockPc); mPermissionProvider.setIsolatedServiceUid(HDS_UID, 10000); verify(mMockPc) .populatePermissionState( eq((byte) PermissionEnum.CAPTURE_AUDIO_HOTWORD), aryEq(new int[] {10000, HDS_UID})); verify(mMockPc) .populatePermissionState( eq((byte) PermissionEnum.CAPTURE_AUDIO_OUTPUT), aryEq(new int[] {10001, HDS_UID})); verify(mMockPc) .populatePermissionState( eq((byte) PermissionEnum.RECORD_AUDIO), aryEq(new int[] {10001, HDS_UID})); clearInvocations(mMockPc); mPermissionProvider.clearIsolatedServiceUid(HDS_UID); verify(mMockPc) .populatePermissionState( eq((byte) PermissionEnum.CAPTURE_AUDIO_HOTWORD), aryEq(new int[] {10000})); verify(mMockPc) .populatePermissionState( eq((byte) PermissionEnum.CAPTURE_AUDIO_OUTPUT), aryEq(new int[] {10001})); verify(mMockPc) .populatePermissionState( eq((byte) PermissionEnum.RECORD_AUDIO), aryEq(new int[] {10001})); } @Test public void testPermissionsPopulated_onChange() throws Exception { var initPackageListData = Loading services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java +10 −16 Original line number Diff line number Diff line Loading @@ -1165,7 +1165,7 @@ final class HotwordDetectionConnection { LocalServices.getService(PermissionManagerServiceInternal.class) .setHotwordDetectionServiceProvider(() -> uid); mIdentity = new HotwordDetectionServiceIdentity(uid, mVoiceInteractionServiceUid); addServiceUidForAudioPolicy(uid); addServiceUidForAudioPolicy(uid, mVoiceInteractionServiceUid); } })); } Loading @@ -1187,23 +1187,17 @@ final class HotwordDetectionConnection { }); } private void addServiceUidForAudioPolicy(int uid) { mScheduledExecutorService.execute(() -> { AudioManagerInternal audioManager = LocalServices.getService(AudioManagerInternal.class); private void addServiceUidForAudioPolicy(int isolatedUid, int owningUid) { AudioManagerInternal audioManager = LocalServices.getService(AudioManagerInternal.class); if (audioManager != null) { audioManager.addAssistantServiceUid(uid); audioManager.addAssistantServiceUid(isolatedUid, owningUid); } }); } private void removeServiceUidForAudioPolicy(int uid) { mScheduledExecutorService.execute(() -> { AudioManagerInternal audioManager = LocalServices.getService(AudioManagerInternal.class); AudioManagerInternal audioManager = LocalServices.getService(AudioManagerInternal.class); if (audioManager != null) { audioManager.removeAssistantServiceUid(uid); } }); } } Loading
media/java/android/media/AudioManagerInternal.java +2 −1 Original line number Diff line number Diff line Loading @@ -44,8 +44,9 @@ public abstract class AudioManagerInternal { * Add the UID for a new assistant service * * @param uid UID of the newly available assistants * @param owningUid UID of the actual assistant app, if {@code uid} is a isolated proc */ public abstract void addAssistantServiceUid(int uid); public abstract void addAssistantServiceUid(int uid, int owningUid); /** * Remove the UID for an existing assistant service Loading
services/core/java/com/android/server/audio/AudioServerPermissionProvider.java +86 −4 Original line number Diff line number Diff line Loading @@ -36,6 +36,7 @@ import android.os.RemoteException; import android.os.UserHandle; import android.util.ArraySet; import android.util.IntArray; import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.media.permission.INativePermissionController; Loading @@ -61,6 +62,9 @@ public class AudioServerPermissionProvider { static final String[] MONITORED_PERMS = new String[PermissionEnum.ENUM_SIZE]; static final byte[] HDS_PERMS = new byte[] {PermissionEnum.CAPTURE_AUDIO_HOTWORD, PermissionEnum.CAPTURE_AUDIO_OUTPUT, PermissionEnum.RECORD_AUDIO}; static { MONITORED_PERMS[PermissionEnum.RECORD_AUDIO] = RECORD_AUDIO; MONITORED_PERMS[PermissionEnum.MODIFY_AUDIO_ROUTING] = MODIFY_AUDIO_ROUTING; Loading Loading @@ -88,6 +92,7 @@ public class AudioServerPermissionProvider { @GuardedBy("mLock") private final Map<Integer, Set<String>> mPackageMap; // Values are sorted @GuardedBy("mLock") private final int[][] mPermMap = new int[PermissionEnum.ENUM_SIZE][]; Loading @@ -95,6 +100,9 @@ public class AudioServerPermissionProvider { @GuardedBy("mLock") private boolean mIsUpdateDeferred = true; @GuardedBy("mLock") private int mHdsUid = -1; /** * @param appInfos - PackageState for all apps on the device, used to populate init state */ Loading Loading @@ -124,7 +132,7 @@ public class AudioServerPermissionProvider { try { for (byte i = 0; i < PermissionEnum.ENUM_SIZE; i++) { if (mIsUpdateDeferred) { mPermMap[i] = getUidsHoldingPerm(MONITORED_PERMS[i]); mPermMap[i] = getUidsHoldingPerm(i); } mDest.populatePermissionState(i, mPermMap[i]); } Loading Loading @@ -184,7 +192,7 @@ public class AudioServerPermissionProvider { } try { for (byte i = 0; i < PermissionEnum.ENUM_SIZE; i++) { var newPerms = getUidsHoldingPerm(MONITORED_PERMS[i]); var newPerms = getUidsHoldingPerm(i); if (!Arrays.equals(newPerms, mPermMap[i])) { mPermMap[i] = newPerms; mDest.populatePermissionState(i, newPerms); Loading @@ -199,6 +207,77 @@ public class AudioServerPermissionProvider { } } public void setIsolatedServiceUid(int uid, int owningUid) { synchronized (mLock) { if (mHdsUid == uid) return; var packageNameSet = mPackageMap.get(owningUid); if (packageNameSet == null) return; var packageName = packageNameSet.iterator().next(); onModifyPackageState(uid, packageName, /* isRemove= */ false); // permissions mHdsUid = uid; if (mDest == null) { mIsUpdateDeferred = true; return; } try { for (byte perm : HDS_PERMS) { int[] newPerms = new int[mPermMap[perm].length + 1]; System.arraycopy(mPermMap[perm], 0, newPerms, 0, mPermMap[perm].length); newPerms[newPerms.length - 1] = mHdsUid; Arrays.sort(newPerms); mPermMap[perm] = newPerms; mDest.populatePermissionState(perm, newPerms); } } catch (RemoteException e) { // We will re-init the state when the service comes back up mDest = null; // We didn't necessarily finish mIsUpdateDeferred = true; } } } public void clearIsolatedServiceUid(int uid) { synchronized (mLock) { if (mHdsUid != uid) return; var packageNameSet = mPackageMap.get(uid); if (packageNameSet == null) return; var packageName = packageNameSet.iterator().next(); onModifyPackageState(uid, packageName, /* isRemove= */ true); // permissions if (mDest == null) { mIsUpdateDeferred = true; return; } try { for (byte perm : HDS_PERMS) { int[] newPerms = new int[mPermMap[perm].length - 1]; int ind = Arrays.binarySearch(mPermMap[perm], uid); if (ind < 0) continue; System.arraycopy(mPermMap[perm], 0, newPerms, 0, ind); System.arraycopy(mPermMap[perm], ind + 1, newPerms, ind, mPermMap[perm].length - ind - 1); mPermMap[perm] = newPerms; mDest.populatePermissionState(perm, newPerms); } } catch (RemoteException e) { // We will re-init the state when the service comes back up mDest = null; // We didn't necessarily finish mIsUpdateDeferred = true; } mHdsUid = -1; } } private boolean isSpecialHdsPermission(int perm) { for (var hdsPerm : HDS_PERMS) { if (perm == hdsPerm) return true; } return false; } /** Called when full syncing package state to audioserver. */ @GuardedBy("mLock") private void resetNativePackageState() { Loading @@ -223,16 +302,19 @@ public class AudioServerPermissionProvider { @GuardedBy("mLock") /** Return all uids (not app-ids) which currently hold a given permission. Not app-op aware */ private int[] getUidsHoldingPerm(String perm) { private int[] getUidsHoldingPerm(int perm) { IntArray acc = new IntArray(); for (int userId : mUserIdSupplier.get()) { for (int appId : mPackageMap.keySet()) { int uid = UserHandle.getUid(userId, appId); if (mPermissionPredicate.test(uid, perm)) { if (mPermissionPredicate.test(uid, MONITORED_PERMS[perm])) { acc.add(uid); } } } if (isSpecialHdsPermission(perm) && mHdsUid != -1) { acc.add(mHdsUid); } var unwrapped = acc.toArray(); Arrays.sort(unwrapped); return unwrapped; Loading
services/core/java/com/android/server/audio/AudioService.java +10 −3 Original line number Diff line number Diff line Loading @@ -11967,8 +11967,9 @@ public class AudioService extends IAudioService.Stub var umi = LocalServices.getService(UserManagerInternal.class); var pmsi = LocalServices.getService(PermissionManagerServiceInternal.class); var provider = new AudioServerPermissionProvider(packageStates, (Integer uid, String perm) -> (pmsi.checkUidPermission(uid, perm, Context.DEVICE_ID_DEFAULT) == PackageManager.PERMISSION_GRANTED), (Integer uid, String perm) -> ActivityManager.checkComponentPermission(perm, uid, /* owningUid = */ -1, /* exported */true) == PackageManager.PERMISSION_GRANTED, () -> umi.getUserIds() ); audioPolicy.registerOnStartTask(() -> { Loading Loading @@ -12330,13 +12331,19 @@ public class AudioService extends IAudioService.Stub } @Override public void addAssistantServiceUid(int uid) { public void addAssistantServiceUid(int uid, int owningUid) { if (audioserverPermissions()) { mPermissionProvider.setIsolatedServiceUid(uid, owningUid); } sendMsg(mAudioHandler, MSG_ADD_ASSISTANT_SERVICE_UID, SENDMSG_QUEUE, uid, 0, null, 0); } @Override public void removeAssistantServiceUid(int uid) { if (audioserverPermissions()) { mPermissionProvider.clearIsolatedServiceUid(uid); } sendMsg(mAudioHandler, MSG_REMOVE_ASSISTANT_SERVICE_UID, SENDMSG_QUEUE, uid, 0, null, 0); }
services/tests/servicestests/src/com/android/server/audio/AudioServerPermissionProviderTest.java +51 −0 Original line number Diff line number Diff line Loading @@ -37,6 +37,7 @@ import android.platform.test.annotations.Presubmit; import androidx.test.ext.junit.runners.AndroidJUnit4; 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; Loading Loading @@ -352,6 +353,56 @@ public final class AudioServerPermissionProviderTest { verify(mMockPc, times(MONITORED_PERMS.length)).populatePermissionState(anyByte(), any()); } @Test public void testSpecialHotwordPermissions() throws Exception { BiPredicate<Integer, String> customPermPred = mock(BiPredicate.class); var initPackageListData = List.of(mMockPackageStateOne_10000_one, mMockPackageStateTwo_10001_two); // expected state // PERM[CAPTURE_AUDIO_HOTWORD]: [10000] // PERM[CAPTURE_AUDIO_OUTPUT]: [10001] // PERM[RECORD_AUDIO]: [10001] // PERM[...]: [] when(customPermPred.test( eq(10000), eq(MONITORED_PERMS[PermissionEnum.CAPTURE_AUDIO_HOTWORD]))) .thenReturn(true); when(customPermPred.test( eq(10001), eq(MONITORED_PERMS[PermissionEnum.CAPTURE_AUDIO_OUTPUT]))) .thenReturn(true); when(customPermPred.test(eq(10001), eq(MONITORED_PERMS[PermissionEnum.RECORD_AUDIO]))) .thenReturn(true); mPermissionProvider = new AudioServerPermissionProvider( initPackageListData, customPermPred, () -> new int[] {0}); int HDS_UID = 99001; mPermissionProvider.onServiceStart(mMockPc); clearInvocations(mMockPc); mPermissionProvider.setIsolatedServiceUid(HDS_UID, 10000); verify(mMockPc) .populatePermissionState( eq((byte) PermissionEnum.CAPTURE_AUDIO_HOTWORD), aryEq(new int[] {10000, HDS_UID})); verify(mMockPc) .populatePermissionState( eq((byte) PermissionEnum.CAPTURE_AUDIO_OUTPUT), aryEq(new int[] {10001, HDS_UID})); verify(mMockPc) .populatePermissionState( eq((byte) PermissionEnum.RECORD_AUDIO), aryEq(new int[] {10001, HDS_UID})); clearInvocations(mMockPc); mPermissionProvider.clearIsolatedServiceUid(HDS_UID); verify(mMockPc) .populatePermissionState( eq((byte) PermissionEnum.CAPTURE_AUDIO_HOTWORD), aryEq(new int[] {10000})); verify(mMockPc) .populatePermissionState( eq((byte) PermissionEnum.CAPTURE_AUDIO_OUTPUT), aryEq(new int[] {10001})); verify(mMockPc) .populatePermissionState( eq((byte) PermissionEnum.RECORD_AUDIO), aryEq(new int[] {10001})); } @Test public void testPermissionsPopulated_onChange() throws Exception { var initPackageListData = Loading
services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java +10 −16 Original line number Diff line number Diff line Loading @@ -1165,7 +1165,7 @@ final class HotwordDetectionConnection { LocalServices.getService(PermissionManagerServiceInternal.class) .setHotwordDetectionServiceProvider(() -> uid); mIdentity = new HotwordDetectionServiceIdentity(uid, mVoiceInteractionServiceUid); addServiceUidForAudioPolicy(uid); addServiceUidForAudioPolicy(uid, mVoiceInteractionServiceUid); } })); } Loading @@ -1187,23 +1187,17 @@ final class HotwordDetectionConnection { }); } private void addServiceUidForAudioPolicy(int uid) { mScheduledExecutorService.execute(() -> { AudioManagerInternal audioManager = LocalServices.getService(AudioManagerInternal.class); private void addServiceUidForAudioPolicy(int isolatedUid, int owningUid) { AudioManagerInternal audioManager = LocalServices.getService(AudioManagerInternal.class); if (audioManager != null) { audioManager.addAssistantServiceUid(uid); audioManager.addAssistantServiceUid(isolatedUid, owningUid); } }); } private void removeServiceUidForAudioPolicy(int uid) { mScheduledExecutorService.execute(() -> { AudioManagerInternal audioManager = LocalServices.getService(AudioManagerInternal.class); AudioManagerInternal audioManager = LocalServices.getService(AudioManagerInternal.class); if (audioManager != null) { audioManager.removeAssistantServiceUid(uid); } }); } }