diff options
3 files changed, 227 insertions, 8 deletions
diff --git a/services/core/java/com/android/server/audio/AudioServerPermissionProvider.java b/services/core/java/com/android/server/audio/AudioServerPermissionProvider.java index 5ea3c4bf538d..76191bbe1a78 100644 --- a/services/core/java/com/android/server/audio/AudioServerPermissionProvider.java +++ b/services/core/java/com/android/server/audio/AudioServerPermissionProvider.java @@ -16,40 +16,76 @@ package com.android.server.audio; +import static android.Manifest.permission.CALL_AUDIO_INTERCEPTION; +import static android.Manifest.permission.MODIFY_AUDIO_ROUTING; +import static android.Manifest.permission.MODIFY_PHONE_STATE; +import static android.Manifest.permission.RECORD_AUDIO; + import android.annotation.Nullable; import android.os.RemoteException; import android.os.UserHandle; import android.util.ArraySet; +import android.util.IntArray; 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 java.util.Arrays; import java.util.Collection; import java.util.Collections; 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 { + static final String[] MONITORED_PERMS = new String[PermissionEnum.ENUM_SIZE]; + + static { + MONITORED_PERMS[PermissionEnum.MODIFY_AUDIO_ROUTING] = MODIFY_AUDIO_ROUTING; + MONITORED_PERMS[PermissionEnum.MODIFY_PHONE_STATE] = MODIFY_PHONE_STATE; + MONITORED_PERMS[PermissionEnum.RECORD_AUDIO] = RECORD_AUDIO; + MONITORED_PERMS[PermissionEnum.CALL_AUDIO_INTERCEPTION] = CALL_AUDIO_INTERCEPTION; + } + private final Object mLock = new Object(); + private final Supplier<int[]> mUserIdSupplier; + private final BiPredicate<Integer, String> mPermissionPredicate; @GuardedBy("mLock") private INativePermissionController mDest; @GuardedBy("mLock") private final Map<Integer, Set<String>> mPackageMap; + // Values are sorted + @GuardedBy("mLock") + private final int[][] mPermMap = new int[PermissionEnum.ENUM_SIZE][]; + + @GuardedBy("mLock") + private boolean mIsUpdateDeferred = true; /** * @param appInfos - PackageState for all apps on the device, used to populate init state */ - public AudioServerPermissionProvider(Collection<PackageState> appInfos) { + public AudioServerPermissionProvider( + Collection<PackageState> appInfos, + BiPredicate<Integer, String> permissionPredicate, + Supplier<int[]> userIdSupplier) { + for (int i = 0; i < PermissionEnum.ENUM_SIZE; i++) { + Objects.requireNonNull(MONITORED_PERMS[i]); + } + mUserIdSupplier = userIdSupplier; + mPermissionPredicate = permissionPredicate; // Initialize the package state mPackageMap = generatePackageMappings(appInfos); } @@ -64,6 +100,18 @@ public class AudioServerPermissionProvider { synchronized (mLock) { mDest = pc; resetNativePackageState(); + try { + for (byte i = 0; i < PermissionEnum.ENUM_SIZE; i++) { + if (mIsUpdateDeferred) { + mPermMap[i] = getUidsHoldingPerm(MONITORED_PERMS[i]); + } + mDest.populatePermissionState(i, mPermMap[i]); + } + mIsUpdateDeferred = false; + } catch (RemoteException e) { + // We will re-init the state when the service comes back up + mDest = null; + } } } @@ -106,6 +154,30 @@ public class AudioServerPermissionProvider { } } + /** Called whenever any package/permission changes occur which invalidate uids holding perms */ + public void onPermissionStateChanged() { + synchronized (mLock) { + if (mDest == null) { + mIsUpdateDeferred = true; + return; + } + try { + for (byte i = 0; i < PermissionEnum.ENUM_SIZE; i++) { + var newPerms = getUidsHoldingPerm(MONITORED_PERMS[i]); + if (!Arrays.equals(newPerms, mPermMap[i])) { + mPermMap[i] = newPerms; + mDest.populatePermissionState(i, 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; + } + } + } + /** Called when full syncing package state to audioserver. */ @GuardedBy("mLock") private void resetNativePackageState() { @@ -128,6 +200,23 @@ 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) { + 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)) { + acc.add(uid); + } + } + } + var unwrapped = acc.toArray(); + 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. diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 09d19b258e25..b4bf3e390524 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -204,6 +204,7 @@ import android.os.VibrationAttributes; import android.os.VibrationEffect; import android.os.Vibrator; import android.os.VibratorManager; +import android.permission.PermissionManager; import android.provider.Settings; import android.provider.Settings.System; import android.service.notification.ZenModeConfig; @@ -241,6 +242,7 @@ import com.android.server.pm.PackageManagerLocal; import com.android.server.pm.UserManagerInternal; import com.android.server.pm.UserManagerInternal.UserRestrictionsListener; import com.android.server.pm.UserManagerService; +import com.android.server.pm.permission.PermissionManagerServiceInternal; import com.android.server.pm.pkg.PackageState; import com.android.server.utils.EventLogger; import com.android.server.wm.ActivityTaskManagerInternal; @@ -269,6 +271,8 @@ import java.util.concurrent.Executor; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; import java.util.function.BooleanSupplier; import java.util.stream.Collectors; @@ -11878,12 +11882,40 @@ public class AudioService extends IAudioService.Stub .withUnfilteredSnapshot()) { packageStates = snapshot.getPackageStates().values(); } - var provider = new AudioServerPermissionProvider(packageStates); + 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), + () -> umi.getUserIds() + ); audioPolicy.registerOnStartTask(() -> { provider.onServiceStart(audioPolicy.getPermissionController()); }); // Set up event listeners + // Must be kept in sync with PermissionManager + Runnable cacheSysPropHandler = new Runnable() { + private AtomicReference<SystemProperties.Handle> mHandle = new AtomicReference(); + private AtomicLong mNonce = new AtomicLong(); + @Override + public void run() { + if (mHandle.get() == null) { + // Cache the handle + mHandle.compareAndSet(null, SystemProperties.find( + PermissionManager.CACHE_KEY_PACKAGE_INFO)); + } + long nonce; + SystemProperties.Handle ref; + if ((ref = mHandle.get()) != null && (nonce = ref.getLong(0)) != 0 && + mNonce.getAndSet(nonce) != nonce) { + audioserverExecutor.execute(() -> provider.onPermissionStateChanged()); + } + } + }; + + SystemProperties.addChangeCallback(cacheSysPropHandler); + IntentFilter packageUpdateFilter = new IntentFilter(); packageUpdateFilter.addAction(ACTION_PACKAGE_ADDED); packageUpdateFilter.addAction(ACTION_PACKAGE_REMOVED); 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 8d772ad5c124..0f3b0aa72b72 100644 --- a/services/tests/servicestests/src/com/android/server/audio/AudioServerPermissionProviderTest.java +++ b/services/tests/servicestests/src/com/android/server/audio/AudioServerPermissionProviderTest.java @@ -15,15 +15,23 @@ */ package com.android.server.audio; +import static com.android.server.audio.AudioServerPermissionProvider.MONITORED_PERMS; + +import static org.mockito.AdditionalMatchers.aryEq; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyByte; import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.any; +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.os.RemoteException; import android.platform.test.annotations.Presubmit; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -45,6 +53,8 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Objects; +import java.util.function.BiPredicate; +import java.util.function.Supplier; @RunWith(AndroidJUnit4.class) @Presubmit @@ -64,6 +74,9 @@ public final class AudioServerPermissionProviderTest { @Mock public PackageState mMockPackageStateFive_10001_four; @Mock public PackageState mMockPackageStateSix_10000_two; + @Mock public BiPredicate<Integer, String> mMockPermPred; + @Mock public Supplier<int[]> mMockUserIdSupplier; + public List<UidPackageState> mInitPackageListExpected; // Argument matcher which matches that the state is equal even if the package names are out of @@ -148,6 +161,13 @@ public final class AudioServerPermissionProviderTest { when(mMockPackageStateSix_10000_two.getAppId()).thenReturn(10000); when(mMockPackageStateSix_10000_two.getPackageName()).thenReturn("com.package.two"); + + when(mMockUserIdSupplier.get()).thenReturn(new int[] {0, 1}); + + when(mMockPermPred.test(eq(10000), eq(MONITORED_PERMS[0]))).thenReturn(true); + when(mMockPermPred.test(eq(110001), eq(MONITORED_PERMS[0]))).thenReturn(true); + when(mMockPermPred.test(eq(10001), eq(MONITORED_PERMS[1]))).thenReturn(true); + when(mMockPermPred.test(eq(110000), eq(MONITORED_PERMS[1]))).thenReturn(true); } @Test @@ -168,7 +188,9 @@ public final class AudioServerPermissionProviderTest { createUidPackageState( 10001, List.of("com.package.two", "com.package.four"))); - mPermissionProvider = new AudioServerPermissionProvider(initPackageListData); + mPermissionProvider = + new AudioServerPermissionProvider( + initPackageListData, mMockPermPred, mMockUserIdSupplier); mPermissionProvider.onServiceStart(mMockPc); verify(mMockPc) .populatePackagesForUids(argThat(new PackageStateListMatcher(expectedPackageList))); @@ -179,7 +201,9 @@ public final class AudioServerPermissionProviderTest { // 10000: one | 10001: two var initPackageListData = List.of(mMockPackageStateOne_10000_one, mMockPackageStateTwo_10001_two); - mPermissionProvider = new AudioServerPermissionProvider(initPackageListData); + mPermissionProvider = + new AudioServerPermissionProvider( + initPackageListData, mMockPermPred, mMockUserIdSupplier); mPermissionProvider.onServiceStart(mMockPc); // new uid, including user component @@ -196,7 +220,9 @@ public final class AudioServerPermissionProviderTest { // 10000: one | 10001: two var initPackageListData = List.of(mMockPackageStateOne_10000_one, mMockPackageStateTwo_10001_two); - mPermissionProvider = new AudioServerPermissionProvider(initPackageListData); + mPermissionProvider = + new AudioServerPermissionProvider( + initPackageListData, mMockPermPred, mMockUserIdSupplier); mPermissionProvider.onServiceStart(mMockPc); // Includes user-id @@ -211,7 +237,9 @@ public final class AudioServerPermissionProviderTest { // 10000: one | 10001: two var initPackageListData = List.of(mMockPackageStateOne_10000_one, mMockPackageStateTwo_10001_two); - mPermissionProvider = new AudioServerPermissionProvider(initPackageListData); + mPermissionProvider = + new AudioServerPermissionProvider( + initPackageListData, mMockPermPred, mMockUserIdSupplier); mPermissionProvider.onServiceStart(mMockPc); // Includes user-id @@ -233,7 +261,9 @@ public final class AudioServerPermissionProviderTest { mMockPackageStateOne_10000_one, mMockPackageStateTwo_10001_two, mMockPackageStateSix_10000_two); - mPermissionProvider = new AudioServerPermissionProvider(initPackageListData); + mPermissionProvider = + new AudioServerPermissionProvider( + initPackageListData, mMockPermPred, mMockUserIdSupplier); mPermissionProvider.onServiceStart(mMockPc); // Includes user-id @@ -255,7 +285,9 @@ public final class AudioServerPermissionProviderTest { mMockPackageStateOne_10000_one, mMockPackageStateTwo_10001_two, mMockPackageStateSix_10000_two); - mPermissionProvider = new AudioServerPermissionProvider(initPackageListData); + mPermissionProvider = + new AudioServerPermissionProvider( + initPackageListData, mMockPermPred, mMockUserIdSupplier); mPermissionProvider.onServiceStart(mMockPc); mPermissionProvider.onModifyPackageState(1_10000, "com.package.one", true /* isRemove */); verify(mMockPc) @@ -299,6 +331,72 @@ public final class AudioServerPermissionProviderTest { verify(newMockPc).updatePackagesForUid(any()); } + @Test + public void testPermissionsPopulated_onStart() throws Exception { + // expected state from setUp: + // PERM[0]: [10000, 110001] + // PERM[1]: [10001, 110000] + // PERM[...]: [] + var initPackageListData = + List.of(mMockPackageStateOne_10000_one, mMockPackageStateTwo_10001_two); + mPermissionProvider = + new AudioServerPermissionProvider( + initPackageListData, mMockPermPred, mMockUserIdSupplier); + + mPermissionProvider.onServiceStart(mMockPc); + verify(mMockPc).populatePermissionState(eq((byte) 0), aryEq(new int[] {10000, 110001})); + verify(mMockPc).populatePermissionState(eq((byte) 1), aryEq(new int[] {10001, 110000})); + for (int i = 2; i < MONITORED_PERMS.length; i++) { + verify(mMockPc).populatePermissionState(eq((byte) i), aryEq(new int[] {})); + } + verify(mMockPc, times(MONITORED_PERMS.length)).populatePermissionState(anyByte(), any()); + } + + @Test + public void testPermissionsPopulated_onChange() throws Exception { + var initPackageListData = + List.of(mMockPackageStateOne_10000_one, mMockPackageStateTwo_10001_two); + mPermissionProvider = + new AudioServerPermissionProvider( + initPackageListData, mMockPermPred, mMockUserIdSupplier); + + mPermissionProvider.onServiceStart(mMockPc); + clearInvocations(mMockPc); + // Ensure the provided permission state is changed + when(mMockPermPred.test(eq(110001), eq(MONITORED_PERMS[1]))).thenReturn(true); + + mPermissionProvider.onPermissionStateChanged(); + verify(mMockPc) + .populatePermissionState(eq((byte) 1), aryEq(new int[] {10001, 110000, 110001})); + verify(mMockPc).populatePermissionState(anyByte(), any()); // exactly once + } + + @Test + public void testPermissionPopulatedDeferred_onDeadService() throws Exception { + var initPackageListData = + List.of(mMockPackageStateOne_10000_one, mMockPackageStateTwo_10001_two); + mPermissionProvider = + new AudioServerPermissionProvider( + initPackageListData, mMockPermPred, mMockUserIdSupplier); + + // throw on the first call to mark the service as dead + doThrow(new RemoteException()) + .doNothing() + .when(mMockPc) + .populatePermissionState(anyByte(), any()); + mPermissionProvider.onServiceStart(mMockPc); + clearInvocations(mMockPc); + clearInvocations(mMockPermPred); + + mPermissionProvider.onPermissionStateChanged(); + verify(mMockPermPred, never()).test(any(), any()); + verify(mMockPc, never()).populatePermissionState(anyByte(), any()); + mPermissionProvider.onServiceStart(mMockPc); + for (int i = 0; i < MONITORED_PERMS.length; i++) { + verify(mMockPc).populatePermissionState(eq((byte) i), any()); + } + } + private static UidPackageState createUidPackageState(int uid, List<String> packages) { var res = new UidPackageState(); res.uid = uid; |