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

Commit 97decf69 authored by Ajay Panicker's avatar Ajay Panicker Committed by android-build-merger
Browse files

Merge "Set device volume when AudioManager says device is connected"

am: 8bf41a99

Change-Id: I939d690866e746acb304382c23a87cc4faee85ea
parents 7c7d520b 8bf41a99
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.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.support.annotation.NonNull;
import android.support.annotation.Nullable;
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);
        }
    }
}