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

Commit acb31f36 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Set device volume when AudioManager says device is connected" into pi-dev

parents 4657c0a5 53bf8064
Loading
Loading
Loading
Loading
+5 −7
Original line number Original line Diff line number Diff line
@@ -803,14 +803,12 @@ public class A2dpService extends ProfileService {
            Log.d(TAG, "broadcastActiveDevice(" + device + ")");
            Log.d(TAG, "broadcastActiveDevice(" + device + ")");
        }
        }


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


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


package com.android.bluetooth.avrcp;
package com.android.bluetooth.avrcp;


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


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


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


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


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


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


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


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


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


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


    void deviceDisconnected(String bdaddr) {
    void deviceDisconnected(BluetoothDevice device) {
        Log.i(TAG, "deviceDisconnected: bdaddr=" + bdaddr);
        Log.i(TAG, "deviceDisconnected: device=" + device);
        mVolumeManager.deviceDisconnected(bdaddr);
        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
     * 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.
     * saved volume use the current system volume.
     */
     */
    public void volumeDeviceSwitched(String bdaddr) {
    public void volumeDeviceSwitched(BluetoothDevice device) {
        if (DEBUG) {
        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.
    // 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);
        mMediaPlayerList.sendMediaKeyEvent(event, pushed);
    }
    }


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


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


        StringBuilder tempBuilder = new StringBuilder();
        if (mMediaPlayerList != null) {
        if (mMediaPlayerList != null) {
            mMediaPlayerList.dump(sb);
            mMediaPlayerList.dump(tempBuilder);
        } else {
        } 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
    private static class AvrcpTargetBinder extends IBluetoothAvrcpTarget.Stub
+128 −66
Original line number Original line Diff line number Diff line
@@ -16,15 +16,22 @@


package com.android.bluetooth.avrcp;
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.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences;
import android.media.AudioDeviceCallback;
import android.media.AudioDeviceInfo;
import android.media.AudioManager;
import android.media.AudioManager;
import android.util.Log;
import android.util.Log;


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


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


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


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


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


    private int getVolume(String bdaddr, int defaultValue) {
    private int getVolume(@NonNull BluetoothDevice device, int defaultValue) {
        if (!mVolumeMap.containsKey(bdaddr)) {
        if (!mVolumeMap.containsKey(device)) {
            Log.w(TAG, "getVolume: Couldn't find volume preference for device: " + bdaddr);
            Log.w(TAG, "getVolume: Couldn't find volume preference for device: " + device);
            return defaultValue;
            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
        // 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
        // Get the current system volume and try to get the preference volume
        int currVolume = mAudioManager.getStreamVolume(STREAM_MUSIC);
        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
        // If the preference volume isn't equal to the current stream volume then that means
        // we had a stored preference.
        // we had a stored preference.
        if (DEBUG) {
        d("switchVolumeDevice: currVolume=" + currVolume + " savedVolume=" + savedVolume);
            Log.d(TAG, "switchVolumeDevice: currVolume=" + currVolume

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


        // If absolute volume for the device is supported, set the volume for the device
        // 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);
            int avrcpVolume = systemToAvrcpVolume(savedVolume);
            Log.i(TAG, "switchVolumeDevice: Updating device volume: avrcpVolume=" + avrcpVolume);
            Log.i(TAG, "switchVolumeDevice: Updating device volume: avrcpVolume=" + avrcpVolume);
            mNativeInterface.sendVolumeChanged(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,
    AvrcpVolumeManager(Context context, AudioManager audioManager,
            AvrcpNativeInterface nativeInterface) {
            AvrcpNativeInterface nativeInterface) {
        mContext = context;
        mContext = context;
@@ -103,93 +144,114 @@ class AvrcpVolumeManager {
        mNativeInterface = nativeInterface;
        mNativeInterface = nativeInterface;
        sDeviceMaxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
        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();
        Map<String, ?> allKeys = getVolumeMap().getAll();
        SharedPreferences.Editor volumeMapEditor = getVolumeMap().edit();
        for (Map.Entry<String, ?> entry : allKeys.entrySet()) {
        for (Map.Entry<String, ?> entry : allKeys.entrySet()) {
            String key = entry.getKey();
            String key = entry.getKey();
            Object value = entry.getValue();
            Object value = entry.getValue();
            if (value instanceof Integer) {
            BluetoothDevice d = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(key);
                mVolumeMap.put(key, (Integer) value);

            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() {
    void storeVolume() {
        SharedPreferences.Editor pref = getVolumeMap().edit();
        SharedPreferences.Editor pref = getVolumeMap().edit();
        int storeVolume =  mAudioManager.getStreamVolume(STREAM_MUSIC);
        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);
                + " : " + storeVolume);
        mVolumeMap.put(mCurrentDeviceAddr, storeVolume);
        mVolumeMap.put(mCurrentDevice, storeVolume);
        pref.putInt(mCurrentDeviceAddr, 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();
        pref.apply();
    }
    }


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


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


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


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


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


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


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


        // No new active device.
        // If device is null, that means that there is no active Bluetooth device
        if (bdaddr.isEmpty()) {
        if (device == null) {
            return;
            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(@NonNull BluetoothDevice device) {
    }
        d("deviceDisconnected: device=" + device);

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


    public void dump(StringBuilder sb) {
    public void dump(StringBuilder sb) {
        sb.append("Bluetooth Device Volume Map:\n");
        sb.append("AvrcpVolumeManager:\n");
        sb.append("  Device Address    : Volume\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();
        Map<String, ?> allKeys = getVolumeMap().getAll();
        for (Map.Entry<String, ?> entry : allKeys.entrySet()) {
        for (Map.Entry<String, ?> entry : allKeys.entrySet()) {
            String key = entry.getKey();
            Object value = entry.getValue();
            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) {
            if (value instanceof Integer) {
                sb.append("  " + key + " : " + (Integer) value + "\n");
                sb.append(String.format("    %-17s : %-14s : %3d : %s\n",
                mVolumeMap.put(key, (Integer) value);
                        d.getAddress(), deviceName, (Integer) value, absoluteVolume));
            }
        }
        }
    }
    }

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