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

Commit 53bf8064 authored by Ajay Panicker's avatar Ajay Panicker
Browse files

Set device volume when AudioManager says device is connected

Instead of setting the remembered volume before the active device switch
happens, set it once we get a callback from the AudioManager that the
device has changed. This prevents the posibility of the new volume being
used with the old active device.

Bug: 79529581
Test: Switch devices multiple times and see that the volume changes when
the new device is connected.

Change-Id: I6a9e10654c844a5e2727d5409e27dd9e8176dd18
parent ee100a3f
Loading
Loading
Loading
Loading
+5 −7
Original line number Diff line number Diff line
@@ -803,14 +803,12 @@ public class A2dpService extends ProfileService {
            Log.d(TAG, "broadcastActiveDevice(" + device + ")");
        }

        // Currently the audio service can only remember the volume for a single device. We send
        // active device changed intent after informing AVRCP that the device switched so it can
        // set the stream volume to the new device before A2DP informs the audio service that the
        // device has changed. This is to avoid the indeterminate volume state that exists when
        // in the middle of switching devices.
        // We need to inform AVRCP that the device has switched before informing the audio service
        // so that AVRCP can prepare and wait on audio service connecting the new stream before
        // restoring the previous volume. Otherwise the updated volume could be applied to the
        // old active device before the switch has fully completed.
        if (AvrcpTargetService.get() != null) {
            AvrcpTargetService.get().volumeDeviceSwitched(
                    device != null ? device.getAddress() : "");
            AvrcpTargetService.get().volumeDeviceSwitched(device);
        }

        Intent intent = new Intent(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED);
+14 −9
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@

package com.android.bluetooth.avrcp;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.util.Log;

import java.util.List;
@@ -186,31 +188,34 @@ public class AvrcpNativeInterface {
    }

    void setActiveDevice(String bdaddr) {
        bdaddr = bdaddr.toUpperCase();
        d("setActiveDevice: bdaddr=" + bdaddr);
        mAvrcpService.setActiveDevice(bdaddr);
        BluetoothDevice device =
                BluetoothAdapter.getDefaultAdapter().getRemoteDevice(bdaddr.toUpperCase());
        d("setActiveDevice: device=" + device);
        mAvrcpService.setActiveDevice(device);
    }

    void deviceConnected(String bdaddr, boolean absoluteVolume) {
        bdaddr = bdaddr.toUpperCase();
        d("deviceConnected: bdaddr=" + bdaddr + " absoluteVolume=" + absoluteVolume);
        BluetoothDevice device =
                BluetoothAdapter.getDefaultAdapter().getRemoteDevice(bdaddr.toUpperCase());
        d("deviceConnected: device=" + device + " absoluteVolume=" + absoluteVolume);
        if (mAvrcpService == null) {
            Log.w(TAG, "deviceConnected: AvrcpTargetService is null");
            return;
        }

        mAvrcpService.deviceConnected(bdaddr, absoluteVolume);
        mAvrcpService.deviceConnected(device, absoluteVolume);
    }

    void deviceDisconnected(String bdaddr) {
        bdaddr = bdaddr.toUpperCase();
        d("deviceDisconnected: bdaddr=" + bdaddr);
        BluetoothDevice device =
                BluetoothAdapter.getDefaultAdapter().getRemoteDevice(bdaddr.toUpperCase());
        d("deviceDisconnected: device=" + device);
        if (mAvrcpService == null) {
            Log.w(TAG, "deviceDisconnected: AvrcpTargetService is null");
            return;
        }

        mAvrcpService.deviceDisconnected(bdaddr);
        mAvrcpService.deviceDisconnected(device);
    }

    void sendVolumeChanged(int volume) {
+21 −20
Original line number Diff line number Diff line
@@ -17,7 +17,6 @@
package com.android.bluetooth.avrcp;

import android.bluetooth.BluetoothA2dp;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.IBluetoothAvrcpTarget;
import android.content.BroadcastReceiver;
@@ -187,15 +186,15 @@ public class AvrcpTargetService extends ProfileService {
    private void init() {
    }

    void deviceConnected(String bdaddr, boolean absoluteVolume) {
        Log.i(TAG, "deviceConnected: bdaddr=" + bdaddr + " absoluteVolume=" + absoluteVolume);
        mVolumeManager.deviceConnected(bdaddr, absoluteVolume);
    void deviceConnected(BluetoothDevice device, boolean absoluteVolume) {
        Log.i(TAG, "deviceConnected: device=" + device + " absoluteVolume=" + absoluteVolume);
        mVolumeManager.deviceConnected(device, absoluteVolume);
        MetricsLogger.logProfileConnectionEvent(BluetoothMetricsProto.ProfileId.AVRCP);
    }

    void deviceDisconnected(String bdaddr) {
        Log.i(TAG, "deviceDisconnected: bdaddr=" + bdaddr);
        mVolumeManager.deviceDisconnected(bdaddr);
    void deviceDisconnected(BluetoothDevice device) {
        Log.i(TAG, "deviceDisconnected: device=" + device);
        mVolumeManager.deviceDisconnected(device);
    }

    /**
@@ -203,11 +202,11 @@ public class AvrcpTargetService extends ProfileService {
     * for the old device is saved and the new device has its volume restored. If there is no
     * saved volume use the current system volume.
     */
    public void volumeDeviceSwitched(String bdaddr) {
    public void volumeDeviceSwitched(BluetoothDevice device) {
        if (DEBUG) {
            Log.d(TAG, "volumeDeviceSwitched: bdaddr=" + bdaddr);
            Log.d(TAG, "volumeDeviceSwitched: device=" + device);
        }
        mVolumeManager.volumeDeviceSwitched(bdaddr);
        mVolumeManager.volumeDeviceSwitched(device);
    }

    // TODO (apanicke): Add checks to blacklist Absolute Volume devices if they behave poorly.
@@ -293,14 +292,12 @@ public class AvrcpTargetService extends ProfileService {
        mMediaPlayerList.sendMediaKeyEvent(event, pushed);
    }

    void setActiveDevice(String address) {
        Log.i(TAG, "setActiveDevice: address=" + address);
        BluetoothDevice d =
                BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
        if (d == null) {
            Log.wtfStack(TAG, "setActiveDevice: could not find device with address " + address);
    void setActiveDevice(BluetoothDevice device) {
        Log.i(TAG, "setActiveDevice: device=" + device);
        if (device == null) {
            Log.wtfStack(TAG, "setActiveDevice: could not find device " + device);
        }
        A2dpService.getA2dpService().setActiveDevice(d);
        A2dpService.getA2dpService().setActiveDevice(device);
    }

    /**
@@ -313,13 +310,17 @@ public class AvrcpTargetService extends ProfileService {
            return;
        }

        StringBuilder tempBuilder = new StringBuilder();
        if (mMediaPlayerList != null) {
            mMediaPlayerList.dump(sb);
            mMediaPlayerList.dump(tempBuilder);
        } else {
            sb.append("\nMedia Player List is empty\n");
            tempBuilder.append("\nMedia Player List is empty\n");
        }

        mVolumeManager.dump(sb);
        mVolumeManager.dump(tempBuilder);

        // Tab everything over by two spaces
        sb.append(tempBuilder.toString().replaceAll("(?m)^", "  "));
    }

    private static class AvrcpTargetBinder extends IBluetoothAvrcpTarget.Stub
+128 −66
Original line number Diff line number Diff line
@@ -16,15 +16,22 @@

package com.android.bluetooth.avrcp;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.Context;
import android.content.SharedPreferences;
import android.media.AudioDeviceCallback;
import android.media.AudioDeviceInfo;
import android.media.AudioManager;
import android.util.Log;

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

class AvrcpVolumeManager {
class AvrcpVolumeManager extends AudioDeviceCallback {
    public static final String TAG = "NewAvrcpVolumeManager";
    public static final boolean DEBUG = true;

@@ -39,9 +46,10 @@ class AvrcpVolumeManager {
    AudioManager mAudioManager;
    AvrcpNativeInterface mNativeInterface;

    HashMap<String, Boolean> mDeviceMap = new HashMap<String, Boolean>();
    HashMap<String, Integer> mVolumeMap = new HashMap<String, Integer>();
    String mCurrentDeviceAddr = "";
    HashMap<BluetoothDevice, Boolean> mDeviceMap = new HashMap();
    HashMap<BluetoothDevice, Integer> mVolumeMap = new HashMap();
    BluetoothDevice mDeferredDevice = null;
    BluetoothDevice mCurrentDevice = null;
    boolean mAbsoluteVolumeSupported = false;

    static int avrcpToSystemVolume(int avrcpVolume) {
@@ -59,43 +67,76 @@ class AvrcpVolumeManager {
        return mContext.getSharedPreferences(VOLUME_MAP, Context.MODE_PRIVATE);
    }

    private int getVolume(String bdaddr, int defaultValue) {
        if (!mVolumeMap.containsKey(bdaddr)) {
            Log.w(TAG, "getVolume: Couldn't find volume preference for device: " + bdaddr);
    private int getVolume(@NonNull BluetoothDevice device, int defaultValue) {
        if (!mVolumeMap.containsKey(device)) {
            Log.w(TAG, "getVolume: Couldn't find volume preference for device: " + device);
            return defaultValue;
        }

        return mVolumeMap.get(bdaddr);
        return mVolumeMap.get(device);
    }

    private void switchVolumeDevice(String bdaddr) {
    private void switchVolumeDevice(@NonNull BluetoothDevice device) {
        // Inform the audio manager that the device has changed
        mAudioManager.avrcpSupportsAbsoluteVolume(bdaddr, mDeviceMap.get(bdaddr));
        mAudioManager.avrcpSupportsAbsoluteVolume(device.getAddress(), mDeviceMap.get(device));

        // Get the current system volume and try to get the preference volume
        int currVolume = mAudioManager.getStreamVolume(STREAM_MUSIC);
        int savedVolume = getVolume(bdaddr, currVolume);
        int savedVolume = getVolume(device, currVolume);

        // If the preference volume isn't equal to the current stream volume then that means
        // we had a stored preference.
        if (DEBUG) {
            Log.d(TAG, "switchVolumeDevice: currVolume=" + currVolume
                    + " savedVolume=" + savedVolume);
        }
        if (savedVolume != currVolume) {
        d("switchVolumeDevice: currVolume=" + currVolume + " savedVolume=" + savedVolume);

        Log.i(TAG, "switchVolumeDevice: restoring volume level " + savedVolume);
        mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, savedVolume,
                AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_BLUETOOTH_ABS_VOLUME);
        }

        // If absolute volume for the device is supported, set the volume for the device
        if (mDeviceMap.get(bdaddr)) {
        if (mDeviceMap.get(device)) {
            int avrcpVolume = systemToAvrcpVolume(savedVolume);
            Log.i(TAG, "switchVolumeDevice: Updating device volume: avrcpVolume=" + avrcpVolume);
            mNativeInterface.sendVolumeChanged(avrcpVolume);
        }
    }

    @Override
    public synchronized void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) {
        if (mDeferredDevice == null) {
            d("onAudioDevicesAdded: Not expecting device changed");
            return;
        }

        boolean foundDevice = false;
        d("onAudioDevicesAdded: size: " + addedDevices.length);
        for (int i = 0; i < addedDevices.length; i++) {
            d("onAudioDevicesAdded: address=" + addedDevices[i].getAddress());
            if (addedDevices[i].getType() == AudioDeviceInfo.TYPE_BLUETOOTH_A2DP
                    && Objects.equals(addedDevices[i].getAddress(), mDeferredDevice.getAddress())) {
                foundDevice = true;
                break;
            }
        }

        if (!foundDevice) {
            d("Didn't find deferred device in list: device=" + mDeferredDevice);
            return;
        }

        mCurrentDevice = mDeferredDevice;
        mDeferredDevice = null;

        // A2DP can sometimes connect and set a device to active before AVRCP has determined if the
        // device supports absolute volume. Defer switching the device until AVRCP returns the
        // info.
        if (!mDeviceMap.containsKey(mCurrentDevice)) {
            Log.w(TAG, "volumeDeviceSwitched: Device isn't connected: " + mCurrentDevice);
            return;
        }

        switchVolumeDevice(mCurrentDevice);
    }

    AvrcpVolumeManager(Context context, AudioManager audioManager,
            AvrcpNativeInterface nativeInterface) {
        mContext = context;
@@ -103,93 +144,114 @@ class AvrcpVolumeManager {
        mNativeInterface = nativeInterface;
        sDeviceMaxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);

        // Load the volume map into a hash map since shared preferences are slow to poll and update
        mAudioManager.registerAudioDeviceCallback(this, null);

        // Load the stored volume preferences into a hash map since shared preferences are slow
        // to poll and update. If the device has been unbonded since last start remove it from
        // the map.
        Map<String, ?> allKeys = getVolumeMap().getAll();
        SharedPreferences.Editor volumeMapEditor = getVolumeMap().edit();
        for (Map.Entry<String, ?> entry : allKeys.entrySet()) {
            String key = entry.getKey();
            Object value = entry.getValue();
            if (value instanceof Integer) {
                mVolumeMap.put(key, (Integer) value);
            BluetoothDevice d = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(key);

            if (value instanceof Integer && d.getBondState() == BluetoothDevice.BOND_BONDED) {
                mVolumeMap.put(d, (Integer) value);
            } else {
                d("Removing " + key + " from the volume map");
                volumeMapEditor.remove(key);
            }
        }
        volumeMapEditor.apply();
    }

    void storeVolume() {
        SharedPreferences.Editor pref = getVolumeMap().edit();
        int storeVolume =  mAudioManager.getStreamVolume(STREAM_MUSIC);
        Log.i(TAG, "storeVolume: Storing stream volume level for device " + mCurrentDeviceAddr
        Log.i(TAG, "storeVolume: Storing stream volume level for device " + mCurrentDevice
                + " : " + storeVolume);
        mVolumeMap.put(mCurrentDeviceAddr, storeVolume);
        pref.putInt(mCurrentDeviceAddr, storeVolume);
        mVolumeMap.put(mCurrentDevice, storeVolume);
        pref.putInt(mCurrentDevice.getAddress(), storeVolume);

        // Always use apply() since it is asynchronous, otherwise the call can hang waiting for
        // storage to be written.
        pref.apply();
    }

    void deviceConnected(String bdaddr, boolean absoluteVolume) {
        if (DEBUG) {
            Log.d(TAG, "deviceConnected: bdaddr=" + bdaddr + " absoluteVolume=" + absoluteVolume);
        }
    synchronized void deviceConnected(@NonNull BluetoothDevice device, boolean absoluteVolume) {
        d("deviceConnected: device=" + device + " absoluteVolume=" + absoluteVolume);

        mDeviceMap.put(bdaddr, absoluteVolume);
        mDeviceMap.put(device, absoluteVolume);

        // AVRCP features lookup has completed after the device became active. Switch to the new
        // device now.
        if (bdaddr.equals(mCurrentDeviceAddr)) {
            switchVolumeDevice(bdaddr);
        if (device.equals(mCurrentDevice)) {
            switchVolumeDevice(device);
        }
    }

    void volumeDeviceSwitched(String bdaddr) {
        if (DEBUG) {
            Log.d(TAG, "volumeDeviceSwitched: mCurrentDeviceAddr=" + mCurrentDeviceAddr
                    + " bdaddr=" + bdaddr);
        }
    synchronized void volumeDeviceSwitched(@Nullable BluetoothDevice device) {
        d("volumeDeviceSwitched: mCurrentDevice=" + mCurrentDevice + " device=" + device);

        if (bdaddr == null || bdaddr.equals(mCurrentDeviceAddr)) {
        if (Objects.equals(device, mCurrentDevice)) {
            return;
        }

        // Store the previous volume if a device was active.
        if (!mCurrentDeviceAddr.isEmpty()) {
        if (mCurrentDevice != null) {
            storeVolume();
        }

        // Set the current volume device to the new device.
        mCurrentDeviceAddr = bdaddr;
        // Wait until AudioManager informs us that the new device is connected
        mDeferredDevice = device;

        // No new active device.
        if (bdaddr.isEmpty()) {
            return;
        // If device is null, that means that there is no active Bluetooth device
        if (device == null) {
            mCurrentDevice = null;
        }

        // A2DP can sometimes connect and set a device to active before AVRCP has determined if the
        // device supports absolute volume. Defer switching the device until AVRCP returns the
        // info.
        if (!mDeviceMap.containsKey(bdaddr)) {
            Log.w(TAG, "volumeDeviceSwitched: Device isn't connected: " + bdaddr);
            return;
    }

        switchVolumeDevice(bdaddr);
    }

    void deviceDisconnected(String bdaddr) {
        if (DEBUG) {
            Log.d(TAG, "deviceDisconnected: bdaddr=" + bdaddr);
        }
        mDeviceMap.remove(bdaddr);
    void deviceDisconnected(@NonNull BluetoothDevice device) {
        d("deviceDisconnected: device=" + device);
        mDeviceMap.remove(device);
    }

    public void dump(StringBuilder sb) {
        sb.append("Bluetooth Device Volume Map:\n");
        sb.append("  Device Address    : Volume\n");
        sb.append("AvrcpVolumeManager:\n");
        sb.append("  mCurrentDevice: " + mCurrentDevice + "\n");
        sb.append("  mDeferredDevice: " + mDeferredDevice + "\n");
        sb.append("  Device Volume Memory Map:\n");
        sb.append(String.format("    %-17s : %-14s : %3s : %s\n",
                "Device Address", "Device Name", "Vol", "AbsVol"));
        Map<String, ?> allKeys = getVolumeMap().getAll();
        for (Map.Entry<String, ?> entry : allKeys.entrySet()) {
            String key = entry.getKey();
            Object value = entry.getValue();
            BluetoothDevice d = BluetoothAdapter.getDefaultAdapter()
                    .getRemoteDevice(entry.getKey());

            String deviceName = d.getName();
            if (deviceName == null) {
                deviceName = "";
            } else if (deviceName.length() > 14) {
                deviceName = deviceName.substring(0, 11).concat("...");
            }

            String absoluteVolume = "NotConnected";
            if (mDeviceMap.containsKey(d)) {
                absoluteVolume = mDeviceMap.get(d).toString();
            }

            if (value instanceof Integer) {
                sb.append("  " + key + " : " + (Integer) value + "\n");
                mVolumeMap.put(key, (Integer) value);
                sb.append(String.format("    %-17s : %-14s : %3d : %s\n",
                        d.getAddress(), deviceName, (Integer) value, absoluteVolume));
            }
        }
    }

    static void d(String msg) {
        if (DEBUG) {
            Log.d(TAG, msg);
        }
    }
}