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);
+        }
     }
 }