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

Commit 515b5009 authored by Vlad Popa's avatar Vlad Popa
Browse files

Use cache for min max volume getter

Using the IpcDataCache to reduce the number of binder calls between
AudioManager and AudioService. Especially for the get min max stream
volume this makes a lot of sense since they do not change that often.

Flag: android.media.audio.cache_get_stream_min_max_volume
Test: atest AudioManagerTest
Bug: 383667500
Change-Id: I040fb20e100099461a6fd31d10726f6ac3b4b02d
parent 20cda896
Loading
Loading
Loading
Loading
+86 −1
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_AUDIO;
import static android.content.Context.DEVICE_ID_DEFAULT;
import static android.media.audio.Flags.autoPublicVolumeApiHardening;
import static android.media.audio.Flags.automaticBtDeviceType;
import static android.media.audio.Flags.cacheGetStreamMinMaxVolume;
import static android.media.audio.Flags.FLAG_DEPRECATE_STREAM_BT_SCO;
import static android.media.audio.Flags.FLAG_FOCUS_EXCLUSIVE_WITH_RECORDING;
import static android.media.audio.Flags.FLAG_FOCUS_FREEZE_TEST_API;
@@ -58,7 +59,6 @@ import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.media.AudioAttributes.AttributeSystemUsage;
import android.media.AudioDeviceInfo;
import android.media.CallbackUtil.ListenerInfo;
import android.media.audiopolicy.AudioPolicy;
import android.media.audiopolicy.AudioPolicy.AudioPolicyFocusListener;
@@ -75,6 +75,7 @@ import android.os.Binder;
import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.os.IpcDataCache;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
@@ -1230,6 +1231,84 @@ public class AudioManager {
        }
    }

    /**
     * API string for caching the min volume for each stream
     * @hide
     **/
    public static final String VOLUME_MIN_CACHING_API = "getStreamMinVolume";
    /**
     * API string for caching the max volume for each stream
     * @hide
     **/
    public static final String VOLUME_MAX_CACHING_API = "getStreamMaxVolume";
    private static final int VOLUME_MIN_MAX_CACHING_SIZE = 16;

    private final IpcDataCache.QueryHandler<VolumeCacheQuery, Integer> mVolQuery =
            new IpcDataCache.QueryHandler<>() {
                @Override
                public Integer apply(VolumeCacheQuery query) {
                    final IAudioService service = getService();
                    try {
                        return switch (query.queryCommand) {
                            case QUERY_VOL_MIN -> service.getStreamMinVolume(query.stream);
                            case QUERY_VOL_MAX -> service.getStreamMaxVolume(query.stream);
                            default -> {
                                Log.w(TAG, "Not a valid volume cache query: " + query);
                                yield null;
                            }
                        };
                    } catch (RemoteException e) {
                        throw e.rethrowFromSystemServer();
                    }
                }

            };

    private final IpcDataCache<VolumeCacheQuery, Integer> mVolMinCache =
            new IpcDataCache<>(VOLUME_MIN_MAX_CACHING_SIZE, IpcDataCache.MODULE_SYSTEM,
                    VOLUME_MIN_CACHING_API, VOLUME_MIN_CACHING_API, mVolQuery);

    private final IpcDataCache<VolumeCacheQuery, Integer> mVolMaxCache =
            new IpcDataCache<>(VOLUME_MIN_MAX_CACHING_SIZE, IpcDataCache.MODULE_SYSTEM,
                    VOLUME_MAX_CACHING_API, VOLUME_MAX_CACHING_API, mVolQuery);

    /**
     * Used to invalidate the cache for the given API
     * @hide
     **/
    public static void clearVolumeCache(String api) {
        if (cacheGetStreamMinMaxVolume()) {
            IpcDataCache.invalidateCache(IpcDataCache.MODULE_SYSTEM, api);
        }
    }

    private static final int QUERY_VOL_MIN = 1;
    private static final int QUERY_VOL_MAX = 2;
    /** @hide */
    @IntDef(prefix = "QUERY_VOL", value = {
            QUERY_VOL_MIN,
            QUERY_VOL_MAX}
    )
    @Retention(RetentionPolicy.SOURCE)
    private @interface QueryVolCommand {}

    private record VolumeCacheQuery(int stream, @QueryVolCommand int queryCommand) {
        private String queryVolCommandToString() {
            return switch (queryCommand) {
                case QUERY_VOL_MIN -> "getStreamMinVolume";
                case QUERY_VOL_MAX -> "getStreamMaxVolume";
                default -> "invalid command";
            };
        }

        @NonNull
        @Override
        public String toString() {
            return TextUtils.formatSimple("VolumeCacheQuery(stream=%d, queryCommand=%s)", stream,
                    queryVolCommandToString());
        }
    }

    /**
     * Returns the maximum volume index for a particular stream.
     *
@@ -1238,6 +1317,9 @@ public class AudioManager {
     * @see #getStreamVolume(int)
     */
    public int getStreamMaxVolume(int streamType) {
        if (cacheGetStreamMinMaxVolume()) {
            return mVolMaxCache.query(new VolumeCacheQuery(streamType, QUERY_VOL_MAX));
        }
        final IAudioService service = getService();
        try {
            return service.getStreamMaxVolume(streamType);
@@ -1271,6 +1353,9 @@ public class AudioManager {
     */
    @TestApi
    public int getStreamMinVolumeInt(int streamType) {
        if (cacheGetStreamMinMaxVolume()) {
            return mVolMinCache.query(new VolumeCacheQuery(streamType, QUERY_VOL_MIN));
        }
        final IAudioService service = getService();
        try {
            return service.getStreamMinVolume(streamType);
+39 −0
Original line number Diff line number Diff line
@@ -16,6 +16,15 @@

package com.android.audiopolicytest;

import static android.media.AudioManager.STREAM_ACCESSIBILITY;
import static android.media.AudioManager.STREAM_ALARM;
import static android.media.AudioManager.STREAM_DTMF;
import static android.media.AudioManager.STREAM_MUSIC;
import static android.media.AudioManager.STREAM_NOTIFICATION;
import static android.media.AudioManager.STREAM_RING;
import static android.media.AudioManager.STREAM_SYSTEM;
import static android.media.AudioManager.STREAM_VOICE_CALL;

import static androidx.test.core.app.ApplicationProvider.getApplicationContext;

import static com.android.audiopolicytest.AudioVolumeTestUtil.DEFAULT_ATTRIBUTES;
@@ -28,11 +37,15 @@ import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;

import android.content.Context;
import android.media.AudioAttributes;
import android.media.AudioManager;
import android.media.AudioSystem;
import android.media.IAudioService;
import android.media.audiopolicy.AudioProductStrategy;
import android.media.audiopolicy.AudioVolumeGroup;
import android.os.IBinder;
import android.os.ServiceManager;
import android.platform.test.annotations.Presubmit;
import android.util.Log;

@@ -206,6 +219,32 @@ public class AudioManagerTest {
        }
    }

    //-----------------------------------------------------------------
    // Test getStreamVolume consistency with AudioService
    //-----------------------------------------------------------------
    @Test
    public void getStreamMinMaxVolume_consistentWithAs() throws Exception {
        IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
        IAudioService service = IAudioService.Stub.asInterface(b);
        final int[] streamTypes = {
                STREAM_VOICE_CALL,
                STREAM_SYSTEM,
                STREAM_RING,
                STREAM_MUSIC,
                STREAM_ALARM,
                STREAM_NOTIFICATION,
                STREAM_DTMF,
                STREAM_ACCESSIBILITY,
        };

        for (int streamType : streamTypes) {
            assertEquals(service.getStreamMinVolume(streamType),
                    mAudioManager.getStreamMinVolume(streamType));
            assertEquals(service.getStreamMaxVolume(streamType),
                    mAudioManager.getStreamMaxVolume(streamType));
        }
    }

    //-----------------------------------------------------------------
    // Test Volume per Attributes setter/getters
    //-----------------------------------------------------------------
+26 −0
Original line number Diff line number Diff line
@@ -65,6 +65,10 @@ class AudioManagerShellCommand extends ShellCommand {
                return setRingerMode();
            case "set-volume":
                return setVolume();
            case "get-min-volume":
                return getMinVolume();
            case "get-max-volume":
                return getMaxVolume();
            case "set-device-volume":
                return setDeviceVolume();
            case "adj-mute":
@@ -106,6 +110,10 @@ class AudioManagerShellCommand extends ShellCommand {
        pw.println("    Sets the Ringer mode to one of NORMAL|SILENT|VIBRATE");
        pw.println("  set-volume STREAM_TYPE VOLUME_INDEX");
        pw.println("    Sets the volume for STREAM_TYPE to VOLUME_INDEX");
        pw.println("  get-min-volume STREAM_TYPE");
        pw.println("    Gets the min volume for STREAM_TYPE");
        pw.println("  get-max-volume STREAM_TYPE");
        pw.println("    Gets the max volume for STREAM_TYPE");
        pw.println("  set-device-volume STREAM_TYPE VOLUME_INDEX NATIVE_DEVICE_TYPE");
        pw.println("    Sets for NATIVE_DEVICE_TYPE the STREAM_TYPE volume to VOLUME_INDEX");
        pw.println("  adj-mute STREAM_TYPE");
@@ -296,6 +304,24 @@ class AudioManagerShellCommand extends ShellCommand {
        return 0;
    }

    private int getMinVolume() {
        final Context context = mService.mContext;
        final AudioManager am = context.getSystemService(AudioManager.class);
        final int stream = readIntArg();
        final int result = am.getStreamMinVolume(stream);
        getOutPrintWriter().println("AudioManager.getStreamMinVolume(" + stream + ") -> " + result);
        return 0;
    }

    private int getMaxVolume() {
        final Context context = mService.mContext;
        final AudioManager am = context.getSystemService(AudioManager.class);
        final int stream = readIntArg();
        final int result = am.getStreamMaxVolume(stream);
        getOutPrintWriter().println("AudioManager.getStreamMaxVolume(" + stream + ") -> " + result);
        return 0;
    }

    private int setDeviceVolume() {
        final Context context = mService.mContext;
        final AudioDeviceVolumeManager advm = (AudioDeviceVolumeManager) context.getSystemService(
+17 −1
Original line number Diff line number Diff line
@@ -47,9 +47,9 @@ import static android.media.AudioManager.RINGER_MODE_NORMAL;
import static android.media.AudioManager.RINGER_MODE_SILENT;
import static android.media.AudioManager.RINGER_MODE_VIBRATE;
import static android.media.AudioManager.STREAM_SYSTEM;
import static android.media.IAudioManagerNative.HardeningType;
import static android.media.audio.Flags.autoPublicVolumeApiHardening;
import static android.media.audio.Flags.automaticBtDeviceType;
import static android.media.audio.Flags.cacheGetStreamMinMaxVolume;
import static android.media.audio.Flags.concurrentAudioRecordBypassPermission;
import static android.media.audio.Flags.featureSpatialAudioHeadtrackingLowLatency;
import static android.media.audio.Flags.focusFreezeTestApi;
@@ -4966,6 +4966,8 @@ public class AudioService extends IAudioService.Stub
                + ringMyCar());
        pw.println("\tandroid.media.audio.Flags.concurrentAudioRecordBypassPermission:"
                + concurrentAudioRecordBypassPermission());
        pw.println("\tandroid.media.audio.Flags.cacheGetStreamMinMaxVolume:"
                + cacheGetStreamMinMaxVolume());
    }
    private void dumpAudioMode(PrintWriter pw) {
@@ -9356,6 +9358,12 @@ public class AudioService extends IAudioService.Stub
                    mIndexMinNoPerm = mIndexMin;
                }
            }
            if (cacheGetStreamMinMaxVolume() && mStreamType == AudioSystem.STREAM_VOICE_CALL) {
                if (DEBUG_VOL) {
                    Log.d(TAG, "Clear min volume cache from updateIndexFactors");
                }
                AudioManager.clearVolumeCache(AudioManager.VOLUME_MIN_CACHING_API);
            }
            final int status = AudioSystem.initStreamVolume(
                    mStreamType, indexMinVolCurve, indexMaxVolCurve);
@@ -9393,11 +9401,19 @@ public class AudioService extends IAudioService.Stub
         * @param index minimum index expressed in "UI units", i.e. no 10x factor
         */
        public void updateNoPermMinIndex(int index) {
            boolean changedNoPermMinIndex =
                    cacheGetStreamMinMaxVolume() && (index * 10) != mIndexMinNoPerm;
            mIndexMinNoPerm = index * 10;
            if (mIndexMinNoPerm < mIndexMin) {
                Log.e(TAG, "Invalid mIndexMinNoPerm for stream " + mStreamType);
                mIndexMinNoPerm = mIndexMin;
            }
            if (changedNoPermMinIndex) {
                if (DEBUG_VOL) {
                    Log.d(TAG, "Clear min volume cache from updateNoPermMinIndex");
                }
                AudioManager.clearVolumeCache(AudioManager.VOLUME_MIN_CACHING_API);
            }
        }
        /**
+2 −0
Original line number Diff line number Diff line
@@ -38,6 +38,7 @@ import android.media.AudioManager;
import android.media.AudioSystem;
import android.media.IAudioDeviceVolumeDispatcher;
import android.media.VolumeInfo;
import android.os.IpcDataCache;
import android.os.PermissionEnforcer;
import android.os.RemoteException;
import android.os.test.TestLooper;
@@ -83,6 +84,7 @@ public class AbsoluteVolumeBehaviorTest {

    @Before
    public void setUp() throws Exception {
        IpcDataCache.disableForTestMode();
        mTestLooper = new TestLooper();

        mContext = spy(ApplicationProvider.getApplicationContext());
Loading