Loading android/app/src/com/android/bluetooth/a2dp/A2dpService.java +5 −7 Original line number Original line Diff line number Diff line Loading @@ -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); Loading android/app/src/com/android/bluetooth/newavrcp/AvrcpNativeInterface.java +14 −9 Original line number Original line Diff line number Diff line Loading @@ -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; Loading Loading @@ -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) { Loading android/app/src/com/android/bluetooth/newavrcp/AvrcpTargetService.java +21 −20 Original line number Original line Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); } } /** /** Loading @@ -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. Loading Loading @@ -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); } } /** /** Loading @@ -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 Loading android/app/src/com/android/bluetooth/newavrcp/AvrcpVolumeManager.java +128 −66 Original line number Original line Diff line number Diff line Loading @@ -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; Loading @@ -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) { Loading @@ -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; Loading @@ -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); } } } } } Loading
android/app/src/com/android/bluetooth/a2dp/A2dpService.java +5 −7 Original line number Original line Diff line number Diff line Loading @@ -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); Loading
android/app/src/com/android/bluetooth/newavrcp/AvrcpNativeInterface.java +14 −9 Original line number Original line Diff line number Diff line Loading @@ -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; Loading Loading @@ -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) { Loading
android/app/src/com/android/bluetooth/newavrcp/AvrcpTargetService.java +21 −20 Original line number Original line Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); } } /** /** Loading @@ -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. Loading Loading @@ -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); } } /** /** Loading @@ -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 Loading
android/app/src/com/android/bluetooth/newavrcp/AvrcpVolumeManager.java +128 −66 Original line number Original line Diff line number Diff line Loading @@ -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; Loading @@ -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) { Loading @@ -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; Loading @@ -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); } } } } }