diff options
| author | 2024-06-12 17:37:03 -0700 | |
|---|---|---|
| committer | 2024-06-26 17:53:46 -0700 | |
| commit | af33533f90da239137a02f56a2c89a80fd2ee02f (patch) | |
| tree | c54cb6d5d138c1584b5b572e8a8091198e026aa1 | |
| parent | 91277279e09ad549c9d1d8b818f3c30c76a82c89 (diff) | |
Make AudioService perm provider HDS aware
Replicate the special handling for the isolated UID associated with HDS
in the audioserver permission provider logic.
- VoiceInteractionManagerService now communicates the owner uid of
trusted process, which is useful for package association/validation
- VIMS no longer posts this call on an executor, it should be sync to
ensure correct handling by audioserver
- Keep track of the active HDS uid in the PermissionProvider, and grant
it the appropriate special-case permissions, updating audioserver when
HDS uid is changed
- Change permission predicate to be more correct (old API was internal)
Bug: 338089555
Test: atest AudioServerPermissionProviderTest#testSpecialHotwordPermissions
Test: manual verification of hotword detection
Flag: com.android.media.audio.audioserver_permissions
Change-Id: Idf44e236f3992c10badbb8f96fd52bd54198430d
5 files changed, 159 insertions, 24 deletions
diff --git a/media/java/android/media/AudioManagerInternal.java b/media/java/android/media/AudioManagerInternal.java index c2632458435a..fd71f8694503 100644 --- a/media/java/android/media/AudioManagerInternal.java +++ b/media/java/android/media/AudioManagerInternal.java @@ -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 diff --git a/services/core/java/com/android/server/audio/AudioServerPermissionProvider.java b/services/core/java/com/android/server/audio/AudioServerPermissionProvider.java index 14eae8da0417..c5180afcce7d 100644 --- a/services/core/java/com/android/server/audio/AudioServerPermissionProvider.java +++ b/services/core/java/com/android/server/audio/AudioServerPermissionProvider.java @@ -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; @@ -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; @@ -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][]; @@ -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 */ @@ -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]); } @@ -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); @@ -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() { @@ -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; diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index c89992d6b57c..74d218cba7ab 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -11963,8 +11963,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(() -> { @@ -12326,13 +12327,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); } diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioServerPermissionProviderTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioServerPermissionProviderTest.java index 0f3b0aa72b72..636cbeef27f7 100644 --- a/services/tests/servicestests/src/com/android/server/audio/AudioServerPermissionProviderTest.java +++ b/services/tests/servicestests/src/com/android/server/audio/AudioServerPermissionProviderTest.java @@ -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; @@ -353,6 +354,56 @@ public final class AudioServerPermissionProviderTest { } @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 = List.of(mMockPackageStateOne_10000_one, mMockPackageStateTwo_10001_two); diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java index cfcc04b10107..89b98509de34 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java @@ -1165,7 +1165,7 @@ final class HotwordDetectionConnection { LocalServices.getService(PermissionManagerServiceInternal.class) .setHotwordDetectionServiceProvider(() -> uid); mIdentity = new HotwordDetectionServiceIdentity(uid, mVoiceInteractionServiceUid); - addServiceUidForAudioPolicy(uid); + addServiceUidForAudioPolicy(uid, mVoiceInteractionServiceUid); } })); } @@ -1187,23 +1187,17 @@ final class HotwordDetectionConnection { }); } - private void addServiceUidForAudioPolicy(int uid) { - mScheduledExecutorService.execute(() -> { - AudioManagerInternal audioManager = - LocalServices.getService(AudioManagerInternal.class); - if (audioManager != null) { - audioManager.addAssistantServiceUid(uid); - } - }); + private void addServiceUidForAudioPolicy(int isolatedUid, int owningUid) { + AudioManagerInternal audioManager = LocalServices.getService(AudioManagerInternal.class); + if (audioManager != null) { + audioManager.addAssistantServiceUid(isolatedUid, owningUid); + } } private void removeServiceUidForAudioPolicy(int uid) { - mScheduledExecutorService.execute(() -> { - AudioManagerInternal audioManager = - LocalServices.getService(AudioManagerInternal.class); - if (audioManager != null) { - audioManager.removeAssistantServiceUid(uid); - } - }); + AudioManagerInternal audioManager = LocalServices.getService(AudioManagerInternal.class); + if (audioManager != null) { + audioManager.removeAssistantServiceUid(uid); + } } } |