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

Commit aa1cbdb4 authored by William Escande's avatar William Escande Committed by Gerrit Code Review
Browse files

Merge "Added documentation on AvrcpVolumeManager." into main

parents 576f236f afacf6b3
Loading
Loading
Loading
Loading
+105 −4
Original line number Diff line number Diff line
@@ -37,6 +37,21 @@ import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;

/**
 * Handles volume changes from or to the remote device or system.
 *
 * <p>{@link AudioManager#setDeviceVolumeBehavior} is used to inform Media Framework of the current
 * active {@link BluetoothDevice} and its absolute volume support.
 *
 * <p>When absolute volume is supported, the volume should be synced between Media framework and the
 * remote device. Otherwise, only system volume is used.
 *
 * <p>AVRCP volume ranges from 0 to 127 which might not correspond to the system volume. As such,
 * volume sent to either Media Framework or remote device is converted accordingly.
 *
 * <p>Volume changes are stored as system volume in {@link SharedPreferences} and retrieved at
 * device connection.
 */
class AvrcpVolumeManager extends AudioDeviceCallback {
    public static final String TAG = AvrcpVolumeManager.class.getSimpleName();

@@ -57,15 +72,31 @@ class AvrcpVolumeManager extends AudioDeviceCallback {
    AudioManager mAudioManager;
    AvrcpNativeInterface mNativeInterface;

    // Absolute volume support map.
    HashMap<BluetoothDevice, Boolean> mDeviceMap = new HashMap();

    // Volume stored is system volume (0 - {@code sDeviceMaxVolume}).
    HashMap<BluetoothDevice, Integer> mVolumeMap = new HashMap();

    BluetoothDevice mCurrentDevice = null;
    boolean mAbsoluteVolumeSupported = false;

    /**
     * Converts given {@code avrcpVolume} (0 - 127) to equivalent in system volume (0 - {@code
     * sDeviceMaxVolume}).
     *
     * <p>Max system volume is retrieved from {@link AudioManager}.
     */
    static int avrcpToSystemVolume(int avrcpVolume) {
        return (int) Math.round((double) avrcpVolume * sDeviceMaxVolume / AVRCP_MAX_VOL);
    }

    /**
     * Converts given {@code deviceVolume} (0 - {@code sDeviceMaxVolume}) to equivalent in AVRCP
     * volume (0 - 127).
     *
     * <p>Max system volume is retrieved from {@link AudioManager}.
     */
    static int systemToAvrcpVolume(int deviceVolume) {
        int avrcpVolume =
                (int) Math.round((double) deviceVolume * AVRCP_MAX_VOL / sDeviceMaxVolume);
@@ -73,10 +104,26 @@ class AvrcpVolumeManager extends AudioDeviceCallback {
        return avrcpVolume;
    }

    /**
     * Retrieves the {@link SharedPreferences} of the map device / volume.
     *
     * <p>The map is read to retrieve the last volume set for a bonded {@link BluetoothDevice}.
     *
     * <p>The map is written each time a volume update occurs from or to the remote device.
     */
    private SharedPreferences getVolumeMap() {
        return mContext.getSharedPreferences(VOLUME_MAP, Context.MODE_PRIVATE);
    }

    /**
     * Informs {@link AudioManager} that a new {@link BluetoothDevice} has been connected and is the
     * new desired audio output.
     *
     * <p>If AVRCP absolute volume is supported, this will also send the saved or new volume to the
     * remote device.
     *
     * <p>Absolute volume support is conditional to its presence in the {@code mDeviceMap}.
     */
    private void switchVolumeDevice(@NonNull BluetoothDevice device) {
        // Inform the audio manager that the device has changed
        d("switchVolumeDevice: Set Absolute volume support to " + mDeviceMap.get(device));
@@ -115,6 +162,12 @@ class AvrcpVolumeManager extends AudioDeviceCallback {
        }
    }

    /**
     * Instantiates all class variables.
     *
     * <p>Fills {@code mVolumeMap} with content from {@link #getVolumeMap}, removing unbonded
     * devices if necessary.
     */
    AvrcpVolumeManager(Context context, AudioManager audioManager,
            AvrcpNativeInterface nativeInterface) {
        mContext = context;
@@ -145,6 +198,10 @@ class AvrcpVolumeManager extends AudioDeviceCallback {
        volumeMapEditor.apply();
    }

    /**
     * Stores system volume (0 - {@code sDeviceMaxVolume}) for device in {@code mVolumeMap} and
     * writes the map in the {@link SharedPreferences}.
     */
    synchronized void storeVolumeForDevice(@NonNull BluetoothDevice device, int storeVolume) {
        if (device.getBondState() != BluetoothDevice.BOND_BONDED) {
            return;
@@ -159,11 +216,19 @@ class AvrcpVolumeManager extends AudioDeviceCallback {
        pref.apply();
    }

    /**
     * Retrieves system volume (0 - {@code sDeviceMaxVolume}) and calls {@link
     * #storeVolumeForDevice(BluetoothDevice, int)} with {@code device}.
     */
    synchronized void storeVolumeForDevice(@NonNull BluetoothDevice device) {
        int storeVolume =  mAudioManager.getLastAudibleStreamVolume(STREAM_MUSIC);
        storeVolumeForDevice(device, storeVolume);
    }

    /**
     * Removes the stored volume of a device from {@code mVolumeMap} and writes the map in the
     * {@link SharedPreferences}.
     */
    synchronized void removeStoredVolumeForDevice(@NonNull BluetoothDevice device) {
        if (device.getBondState() != BluetoothDevice.BOND_NONE) {
            return;
@@ -178,6 +243,12 @@ class AvrcpVolumeManager extends AudioDeviceCallback {
        pref.apply();
    }

    /**
     * Returns system volume (0 - {@code sDeviceMaxVolume}) stored in {@code mVolumeMap} for
     * corresponding {@code device}.
     *
     * @param defaultValue Value to return if device is not in the map.
     */
    synchronized int getVolume(@NonNull BluetoothDevice device, int defaultValue) {
        if (!mVolumeMap.containsKey(device)) {
            Log.w(TAG, "getVolume: Couldn't find volume preference for device: " + device);
@@ -188,10 +259,18 @@ class AvrcpVolumeManager extends AudioDeviceCallback {
        return mVolumeMap.get(device);
    }

    /** Returns the system volume (0 - {@code sDeviceMaxVolume}) applied to a new device */
    public int getNewDeviceVolume() {
        return sNewDeviceVolume;
    }

    /**
     * Informs {@link AudioManager} of a remote device volume change and stores it.
     *
     * <p>See {@link #avrcpToSystemVolume}.
     *
     * @param avrcpVolume in range (0 - 127) received from remote device.
     */
    void setVolume(@NonNull BluetoothDevice device, int avrcpVolume) {
        int deviceVolume = avrcpToSystemVolume(avrcpVolume);
        mVolumeEventLogger.logd(TAG, "setVolume:"
@@ -205,6 +284,13 @@ class AvrcpVolumeManager extends AudioDeviceCallback {
        storeVolumeForDevice(device);
    }

    /**
     * Informs remote device of a system volume change and stores it.
     *
     * <p>See {@link #systemToAvrcpVolume}.
     *
     * @param deviceVolume in range (0 - {@code sDeviceMaxVolume}) received from system.
     */
    void sendVolumeChanged(@NonNull BluetoothDevice device, int deviceVolume) {
        if (deviceVolume == getVolume(device, -1)) {
            d("sendVolumeChanged: Skipping update volume to same as current.");
@@ -220,10 +306,7 @@ class AvrcpVolumeManager extends AudioDeviceCallback {
        storeVolumeForDevice(device);
    }

    /**
     * True if remote device supported Absolute volume, false if remote device is not supported or
     * not connected.
     */
    /** Returns whether absolute volume is supported by {@code device}. */
    boolean getAbsoluteVolumeSupported(BluetoothDevice device) {
        if (mDeviceMap.containsKey(device)) {
            return mDeviceMap.get(device);
@@ -231,6 +314,16 @@ class AvrcpVolumeManager extends AudioDeviceCallback {
        return false;
    }

    /**
     * Callback from Media Framework to indicate new audio device was added.
     *
     * <p>Checks if the current active device is in the {@code addedDevices} list in order to inform
     * {@link AudioManager} to take it as selected audio output. See {@link #switchVolumeDevice}.
     *
     * <p>If the remote device absolute volume support hasn't been established yet or if the current
     * active device is not in the {@code addedDevices} list, this doesn't inform {@link
     * AudioManager}. See {@link #deviceConnected} and {@link #volumeDeviceSwitched}.
     */
    @Override
    public synchronized void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) {
        if (mCurrentDevice == null) {
@@ -265,6 +358,10 @@ class AvrcpVolumeManager extends AudioDeviceCallback {
        switchVolumeDevice(mCurrentDevice);
    }

    /**
     * Stores absolute volume support for {@code device}. If the current active device is the same
     * as {@code device}, calls {@link #switchVolumeDevice}.
     */
    synchronized void deviceConnected(@NonNull BluetoothDevice device, boolean absoluteVolume) {
        d("deviceConnected: device=" + device + " absoluteVolume=" + absoluteVolume);

@@ -277,6 +374,10 @@ class AvrcpVolumeManager extends AudioDeviceCallback {
        }
    }

    /**
     * Called when the A2DP active device changed, this will call {@link #switchVolumeDevice} if we
     * already know the absolute volume support of {@code device}.
     */
    synchronized void volumeDeviceSwitched(@Nullable BluetoothDevice device) {
        d("volumeDeviceSwitched: mCurrentDevice=" + mCurrentDevice + " device=" + device);