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

Commit fe0bc7ec authored by Atneya Nair's avatar Atneya Nair Committed by Android (Google) Code Review
Browse files

Merge "Add audioserver package caching" into main

parents bc00f4c8 6bf26c72
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 −23
Original line number Diff line number Diff line
@@ -31,6 +31,10 @@ import static android.Manifest.permission.MODIFY_PHONE_STATE;
import static android.Manifest.permission.QUERY_AUDIO_STATE;
import static android.Manifest.permission.WRITE_SETTINGS;
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;
@@ -59,6 +63,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.absVolumeIndexFix;
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;
@@ -240,15 +245,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;
@@ -273,6 +281,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;
@@ -303,6 +312,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 */
@@ -1021,14 +1032,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
@@ -1102,25 +1121,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}
@@ -1137,13 +1137,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;
@@ -4559,6 +4562,8 @@ 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:"
@@ -11937,6 +11942,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