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
diff --git a/media/java/android/media/AudioManagerInternal.java b/media/java/android/media/AudioManagerInternal.java
index c263245..fd71f86 100644
--- a/media/java/android/media/AudioManagerInternal.java
+++ b/media/java/android/media/AudioManagerInternal.java
@@ -44,8 +44,9 @@
* 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 14eae8d..c5180af 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.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 @@
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 @@
@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 @@
@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 @@
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 @@
}
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 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 @@
@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 c89992d..74d218c 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 @@
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 @@
}
@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 0f3b0aa..636cbee 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 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 @@
}
@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 cfcc04b..89b9850 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 @@
LocalServices.getService(PermissionManagerServiceInternal.class)
.setHotwordDetectionServiceProvider(() -> uid);
mIdentity = new HotwordDetectionServiceIdentity(uid, mVoiceInteractionServiceUid);
- addServiceUidForAudioPolicy(uid);
+ addServiceUidForAudioPolicy(uid, mVoiceInteractionServiceUid);
}
}));
}
@@ -1187,23 +1187,17 @@
});
}
- 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);
+ }
}
}