Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 6bf26c72 authored by Atneya Nair's avatar Atneya Nair
Browse files

Add audioserver package caching

Audioserver upcalls to validate that package names belong to a
particular uid. This causes deadlocks due to binder threadpool
starvation.

Add AudioServerPermissionProvider to send packages per uid down to
audioserver on start, and shuttle updates from pm downwards.

Bug: 338089555
Test: atest AudioServerPermissionProviderTest
Flag: com.android.media.audio.audioserver_permissions
Change-Id: Ia6225fad76004b8e83c28b9084e8ff7443ef20ca
parent 33445334
Loading
Loading
Loading
Loading
+3 −1
Original line number Diff line number Diff line
@@ -16,12 +16,14 @@

package com.android.server.audio;

import com.android.media.permission.INativePermissionController;

/**
 * Facade to IAudioPolicyService which fulfills AudioService dependencies.
 * See @link{IAudioPolicyService.aidl}
 */
public interface AudioPolicyFacade {

    public boolean isHotwordStreamSupported(boolean lookbackAudio);
    public INativePermissionController getPermissionController();
    public void registerOnStartTask(Runnable r);
}
+149 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.audio;

import android.annotation.Nullable;
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.ArraySet;

import com.android.internal.annotations.GuardedBy;
import com.android.media.permission.INativePermissionController;
import com.android.media.permission.UidPackageState;
import com.android.server.pm.pkg.PackageState;

import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collector;
import java.util.stream.Collectors;

/** Responsible for synchronizing system server permission state to the native audioserver. */
public class AudioServerPermissionProvider {

    private final Object mLock = new Object();

    @GuardedBy("mLock")
    private INativePermissionController mDest;

    @GuardedBy("mLock")
    private final Map<Integer, Set<String>> mPackageMap;

    /**
     * @param appInfos - PackageState for all apps on the device, used to populate init state
     */
    public AudioServerPermissionProvider(Collection<PackageState> appInfos) {
        // Initialize the package state
        mPackageMap = generatePackageMappings(appInfos);
    }

    /**
     * Called whenever audioserver starts (or started before us)
     *
     * @param pc - The permission controller interface from audioserver, which we push updates to
     */
    public void onServiceStart(@Nullable INativePermissionController pc) {
        if (pc == null) return;
        synchronized (mLock) {
            mDest = pc;
            resetNativePackageState();
        }
    }

    /**
     * Called when a package is added or removed
     *
     * @param uid - uid of modified package (only app-id matters)
     * @param packageName - the (new) packageName
     * @param isRemove - true if the package is being removed, false if it is being added
     */
    public void onModifyPackageState(int uid, String packageName, boolean isRemove) {
        // No point in maintaining package mappings for uids of different users
        uid = UserHandle.getAppId(uid);
        synchronized (mLock) {
            // Update state
            Set<String> packages;
            if (!isRemove) {
                packages = mPackageMap.computeIfAbsent(uid, unused -> new ArraySet(1));
                packages.add(packageName);
            } else {
                packages = mPackageMap.get(uid);
                if (packages != null) {
                    packages.remove(packageName);
                    if (packages.isEmpty()) mPackageMap.remove(uid);
                }
            }
            // Push state to destination
            if (mDest == null) {
                return;
            }
            var state = new UidPackageState();
            state.uid = uid;
            state.packageNames = packages != null ? List.copyOf(packages) : Collections.emptyList();
            try {
                mDest.updatePackagesForUid(state);
            } catch (RemoteException e) {
                // We will re-init the state when the service comes back up
                mDest = null;
            }
        }
    }

    /** Called when full syncing package state to audioserver. */
    @GuardedBy("mLock")
    private void resetNativePackageState() {
        if (mDest == null) return;
        List<UidPackageState> states =
                mPackageMap.entrySet().stream()
                        .map(
                                entry -> {
                                    UidPackageState state = new UidPackageState();
                                    state.uid = entry.getKey();
                                    state.packageNames = List.copyOf(entry.getValue());
                                    return state;
                                })
                        .toList();
        try {
            mDest.populatePackagesForUids(states);
        } catch (RemoteException e) {
            // We will re-init the state when the service comes back up
            mDest = null;
        }
    }

    /**
     * Aggregation operation on all package states list: groups by states by app-id and merges the
     * packageName for each state into an ArraySet.
     */
    private static Map<Integer, Set<String>> generatePackageMappings(
            Collection<PackageState> appInfos) {
        Collector<PackageState, Object, Set<String>> reducer =
                Collectors.mapping(
                        (PackageState p) -> p.getPackageName(),
                        Collectors.toCollection(() -> new ArraySet(1)));

        return appInfos.stream()
                .collect(
                        Collectors.groupingBy(
                                /* predicate */ (PackageState p) -> p.getAppId(),
                                /* factory */ HashMap::new,
                                /* downstream collector */ reducer));
    }
}
+67 −24
Original line number Diff line number Diff line
@@ -18,6 +18,10 @@ package com.android.server.audio;
import static android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED;
import static android.app.BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT;
import static android.content.Intent.ACTION_PACKAGE_ADDED;
import static android.content.Intent.ACTION_PACKAGE_REMOVED;
import static android.content.Intent.EXTRA_ARCHIVAL;
import static android.content.Intent.EXTRA_REPLACING;
import static android.media.AudioDeviceInfo.TYPE_BLE_HEADSET;
import static android.media.AudioDeviceInfo.TYPE_BLE_SPEAKER;
import static android.media.AudioDeviceInfo.TYPE_BLUETOOTH_A2DP;
@@ -44,6 +48,7 @@ import static android.provider.Settings.Secure.VOLUME_HUSH_VIBRATE;
import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
import static com.android.media.audio.Flags.alarmMinVolumeZero;
import static com.android.media.audio.Flags.audioserverPermissions;
import static com.android.media.audio.Flags.disablePrescaleAbsoluteVolume;
import static com.android.media.audio.Flags.ringerModeAffectsAlarm;
import static com.android.media.audio.Flags.setStreamVolumeOrder;
@@ -225,15 +230,18 @@ import com.android.internal.os.SomeArgs;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.Preconditions;
import com.android.server.EventLogTags;
import com.android.server.LocalManagerRegistry;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.audio.AudioServiceEvents.DeviceVolumeEvent;
import com.android.server.audio.AudioServiceEvents.PhoneStateEvent;
import com.android.server.audio.AudioServiceEvents.VolChangedBroadcastEvent;
import com.android.server.audio.AudioServiceEvents.VolumeEvent;
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.pkg.PackageState;
import com.android.server.utils.EventLogger;
import com.android.server.wm.ActivityTaskManagerInternal;
@@ -258,6 +266,7 @@ import java.util.Objects;
import java.util.Set;
import java.util.TreeSet;
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.function.BooleanSupplier;
@@ -288,6 +297,8 @@ public class AudioService extends IAudioService.Stub
    private final SettingsAdapter mSettings;
    private final AudioPolicyFacade mAudioPolicy;
    private final AudioServerPermissionProvider mPermissionProvider;
    private final MusicFxHelper mMusicFxHelper;
    /** Debug audio mode */
@@ -995,14 +1006,22 @@ public class AudioService extends IAudioService.Stub
        public Lifecycle(Context context) {
            super(context);
            var audioserverLifecycleExecutor = Executors.newSingleThreadExecutor();
            var audioPolicyFacade = new DefaultAudioPolicyFacade(audioserverLifecycleExecutor);
            mService = new AudioService(context,
                              AudioSystemAdapter.getDefaultAdapter(),
                              SystemServerAdapter.getDefaultAdapter(context),
                              SettingsAdapter.getDefaultAdapter(),
                              new AudioVolumeGroupHelper(),
                              new DefaultAudioPolicyFacade(r -> r.run()),
                              null);
                              audioPolicyFacade,
                              null,
                              context.getSystemService(AppOpsManager.class),
                              PermissionEnforcer.fromContext(context),
                              audioserverPermissions() ?
                                initializeAudioServerPermissionProvider(
                                    context, audioPolicyFacade, audioserverLifecycleExecutor) :
                                    null
                              );
        }
        @Override
@@ -1076,25 +1095,6 @@ public class AudioService extends IAudioService.Stub
    ///////////////////////////////////////////////////////////////////////////
    /**
     * @param context
     * @param audioSystem Adapter for {@link AudioSystem}
     * @param systemServer Adapter for privileged functionality for system server components
     * @param settings Adapter for {@link Settings}
     * @param audioVolumeGroupHelper Adapter for {@link AudioVolumeGroup}
     * @param audioPolicy Interface of a facade to IAudioPolicyManager
     * @param looper Looper to use for the service's message handler. If this is null, an
     *               {@link AudioSystemThread} is created as the messaging thread instead.
     */
    public AudioService(Context context, AudioSystemAdapter audioSystem,
            SystemServerAdapter systemServer, SettingsAdapter settings,
            AudioVolumeGroupHelperBase audioVolumeGroupHelper, AudioPolicyFacade audioPolicy,
            @Nullable Looper looper) {
        this (context, audioSystem, systemServer, settings, audioVolumeGroupHelper,
                audioPolicy, looper, context.getSystemService(AppOpsManager.class),
                PermissionEnforcer.fromContext(context));
    }
    /**
     * @param context
     * @param audioSystem Adapter for {@link AudioSystem}
@@ -1111,13 +1111,16 @@ public class AudioService extends IAudioService.Stub
    public AudioService(Context context, AudioSystemAdapter audioSystem,
            SystemServerAdapter systemServer, SettingsAdapter settings,
            AudioVolumeGroupHelperBase audioVolumeGroupHelper, AudioPolicyFacade audioPolicy,
            @Nullable Looper looper, AppOpsManager appOps, @NonNull PermissionEnforcer enforcer) {
            @Nullable Looper looper, AppOpsManager appOps, @NonNull PermissionEnforcer enforcer,
            /* @NonNull */ AudioServerPermissionProvider permissionProvider) {
        super(enforcer);
        sLifecycleLogger.enqueue(new EventLogger.StringEvent("AudioService()"));
        mContext = context;
        mContentResolver = context.getContentResolver();
        mAppOps = appOps;
        mPermissionProvider = permissionProvider;
        mAudioSystem = audioSystem;
        mSystemServer = systemServer;
        mAudioVolumeGroupHelper = audioVolumeGroupHelper;
@@ -4546,9 +4549,10 @@ public class AudioService extends IAudioService.Stub
                + featureSpatialAudioHeadtrackingLowLatency());
        pw.println("\tandroid.media.audio.focusFreezeTestApi:"
                + focusFreezeTestApi());
        pw.println("\tcom.android.media.audio.audioserverPermissions:"
                + audioserverPermissions());
        pw.println("\tcom.android.media.audio.disablePrescaleAbsoluteVolume:"
                + disablePrescaleAbsoluteVolume());
        pw.println("\tcom.android.media.audio.setStreamVolumeOrder:"
                + setStreamVolumeOrder());
        pw.println("\tandroid.media.audio.roForegroundAudioControl:"
@@ -11866,6 +11870,45 @@ public class AudioService extends IAudioService.Stub
    private static final String mMetricsId = MediaMetrics.Name.AUDIO_SERVICE
            + MediaMetrics.SEPARATOR;
    private static AudioServerPermissionProvider initializeAudioServerPermissionProvider(
            Context context, AudioPolicyFacade audioPolicy, Executor audioserverExecutor) {
        Collection<PackageState> packageStates = null;
        try (PackageManagerLocal.UnfilteredSnapshot snapshot =
                    LocalManagerRegistry.getManager(PackageManagerLocal.class)
                        .withUnfilteredSnapshot()) {
            packageStates = snapshot.getPackageStates().values();
        }
        var provider = new AudioServerPermissionProvider(packageStates);
        audioPolicy.registerOnStartTask(() -> {
            provider.onServiceStart(audioPolicy.getPermissionController());
        });
        // Set up event listeners
        IntentFilter packageUpdateFilter = new IntentFilter();
        packageUpdateFilter.addAction(ACTION_PACKAGE_ADDED);
        packageUpdateFilter.addAction(ACTION_PACKAGE_REMOVED);
        packageUpdateFilter.addDataScheme("package");
        context.registerReceiverForAllUsers(new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                String action = intent.getAction();
                String pkgName = intent.getData().getEncodedSchemeSpecificPart();
                int uid = intent.getIntExtra(Intent.EXTRA_UID, Process.INVALID_UID);
                if (intent.getBooleanExtra(EXTRA_REPLACING, false) ||
                        intent.getBooleanExtra(EXTRA_ARCHIVAL, false)) return;
                if (action.equals(ACTION_PACKAGE_ADDED)) {
                    audioserverExecutor.execute(() ->
                            provider.onModifyPackageState(uid, pkgName, false /* isRemoved */));
                } else if (action.equals(ACTION_PACKAGE_REMOVED)) {
                    audioserverExecutor.execute(() ->
                            provider.onModifyPackageState(uid, pkgName, true /* isRemoved */));
                }
            }
        }, packageUpdateFilter, null, null); // main thread is fine, since dispatch on executor
        return provider;
    }
    // Inform AudioFlinger of our device's low RAM attribute
    private static void readAndSetLowRamDevice()
    {
+25 −0
Original line number Diff line number Diff line
@@ -16,10 +16,15 @@

package com.android.server.audio;

import android.annotation.Nullable;
import android.media.IAudioPolicyService;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;

import com.android.media.permission.INativePermissionController;

import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.function.Function;

@@ -43,6 +48,7 @@ public class DefaultAudioPolicyFacade implements AudioPolicyFacade {
                        (Function<IBinder, IAudioPolicyService>)
                                IAudioPolicyService.Stub::asInterface,
                        e);
        mServiceHolder.registerOnStartTask(i -> Binder.allowBlocking(i.asBinder()));
    }

    @Override
@@ -55,4 +61,23 @@ public class DefaultAudioPolicyFacade implements AudioPolicyFacade {
            throw new IllegalStateException();
        }
    }

    @Override
    public @Nullable INativePermissionController getPermissionController() {
        IAudioPolicyService ap = mServiceHolder.checkService();
        if (ap == null) return null;
        try {
            var res = Objects.requireNonNull(ap.getPermissionController());
            Binder.allowBlocking(res.asBinder());
            return res;
        } catch (RemoteException e) {
            mServiceHolder.attemptClear(ap.asBinder());
            return null;
        }
    }

    @Override
    public void registerOnStartTask(Runnable task) {
        mServiceHolder.registerOnStartTask(unused -> task.run());
    }
}
+4 −1
Original line number Diff line number Diff line
@@ -28,6 +28,7 @@ import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.app.AppOpsManager;
import android.content.Context;
import android.content.res.Resources;
import android.media.AudioDeviceAttributes;
@@ -37,6 +38,7 @@ import android.media.AudioManager;
import android.media.AudioSystem;
import android.media.IAudioDeviceVolumeDispatcher;
import android.media.VolumeInfo;
import android.os.PermissionEnforcer;
import android.os.RemoteException;
import android.os.test.TestLooper;
import android.platform.test.annotations.Presubmit;
@@ -98,7 +100,8 @@ public class AbsoluteVolumeBehaviorTest {

        mAudioService = new AudioService(mContext, mSpyAudioSystem, mSystemServer,
                mSettingsAdapter, mAudioVolumeGroupHelper, mMockAudioPolicy,
                mTestLooper.getLooper()) {
                mTestLooper.getLooper(), mock(AppOpsManager.class), mock(PermissionEnforcer.class),
                mock(AudioServerPermissionProvider.class)) {
            @Override
            public int getDeviceForStream(int stream) {
                return AudioSystem.DEVICE_OUT_SPEAKER;
Loading