Loading android/app/src/com/android/bluetooth/a2dp/A2dpService.java +18 −11 Original line number Diff line number Diff line Loading @@ -438,6 +438,11 @@ public class A2dpService extends ProfileService { if (DBG) { Log.d(TAG, "setActiveDevice(" + device + "): previous is " + previousActiveDevice); } if (previousActiveDevice != null && AvrcpTargetService.get() != null) { AvrcpTargetService.get().storeVolumeForDevice(previousActiveDevice); } if (device == null) { // Clear the active device mActiveDevice = null; Loading @@ -455,7 +460,7 @@ public class A2dpService extends ProfileService { previousActiveDevice, BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.A2DP, getConnectionState(previousActiveDevice) == BluetoothProfile.STATE_CONNECTED); == BluetoothProfile.STATE_CONNECTED, -1); // Make sure the Active device in native layer is set to null and audio is off if (!mA2dpNativeInterface.setActiveDevice(null)) { Log.w(TAG, "setActiveDevice(null): Cannot remove active device in native " Loading Loading @@ -498,11 +503,21 @@ public class A2dpService extends ProfileService { if (previousActiveDevice != null) { mAudioManager.setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent( previousActiveDevice, BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.A2DP, true); BluetoothProfile.A2DP, true, -1); } int rememberedVolume = -1; if (AvrcpTargetService.get() != null) { AvrcpTargetService.get().volumeDeviceSwitched(mActiveDevice); rememberedVolume = AvrcpTargetService.get() .getRememberedVolumeForDevice(mActiveDevice); } mAudioManager.setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent( mActiveDevice, BluetoothProfile.STATE_CONNECTED, BluetoothProfile.A2DP, true); true, rememberedVolume); // Inform the Audio Service about the codec configuration // change, so the Audio Service can reset accordingly the audio // feeding parameters in the Audio HAL to the Bluetooth stack. Loading Loading @@ -806,14 +821,6 @@ public class A2dpService extends ProfileService { Log.d(TAG, "broadcastActiveDevice(" + device + ")"); } // 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); } Intent intent = new Intent(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED); intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT Loading android/app/src/com/android/bluetooth/newavrcp/AvrcpTargetService.java +22 −3 Original line number Diff line number Diff line Loading @@ -198,9 +198,9 @@ public class AvrcpTargetService extends ProfileService { } /** * Signal to the service that the current audio out device has changed. The current volume * 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. * Signal to the service that the current audio out device has changed and to inform * the audio service whether the new device supports absolute volume. If it does, also * set the absolute volume level on the remote device. */ public void volumeDeviceSwitched(BluetoothDevice device) { if (DEBUG) { Loading @@ -209,6 +209,25 @@ public class AvrcpTargetService extends ProfileService { mVolumeManager.volumeDeviceSwitched(device); } /** * Store the current system volume for a device in order to be retrieved later. */ public void storeVolumeForDevice(BluetoothDevice device) { if (device == null) return; mVolumeManager.storeVolumeForDevice(device); } /** * Retrieve the remembered volume for a device. Returns -1 if there is no volume for the * device. */ public int getRememberedVolumeForDevice(BluetoothDevice device) { if (device == null) return -1; return mVolumeManager.getVolume(device, -1); } // TODO (apanicke): Add checks to blacklist Absolute Volume devices if they behave poorly. void setVolume(int avrcpVolume) { int deviceVolume = Loading android/app/src/com/android/bluetooth/newavrcp/AvrcpVolumeManager.java +29 −69 Original line number Diff line number Diff line Loading @@ -22,8 +22,6 @@ 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; Loading @@ -31,7 +29,7 @@ import java.util.HashMap; import java.util.Map; import java.util.Objects; class AvrcpVolumeManager extends AudioDeviceCallback { class AvrcpVolumeManager { public static final String TAG = "NewAvrcpVolumeManager"; public static final boolean DEBUG = true; Loading @@ -48,7 +46,6 @@ class AvrcpVolumeManager extends AudioDeviceCallback { HashMap<BluetoothDevice, Boolean> mDeviceMap = new HashMap(); HashMap<BluetoothDevice, Integer> mVolumeMap = new HashMap(); BluetoothDevice mDeferredDevice = null; BluetoothDevice mCurrentDevice = null; boolean mAbsoluteVolumeSupported = false; Loading @@ -67,31 +64,17 @@ class AvrcpVolumeManager extends AudioDeviceCallback { return mContext.getSharedPreferences(VOLUME_MAP, Context.MODE_PRIVATE); } 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(device); } private void switchVolumeDevice(@NonNull BluetoothDevice device) { // Inform the audio manager that the device has changed d("switchVolumeDevice: Set Absolute volume support to " + mDeviceMap.get(device)); 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(device, currVolume); // If the preference volume isn't equal to the current stream volume then that means // we had a stored preference. 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(device)) { int avrcpVolume = systemToAvrcpVolume(savedVolume); Loading @@ -100,43 +83,6 @@ class AvrcpVolumeManager extends AudioDeviceCallback { } } @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; Loading @@ -144,8 +90,6 @@ class AvrcpVolumeManager extends AudioDeviceCallback { mNativeInterface = nativeInterface; sDeviceMaxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC); 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. Loading @@ -166,19 +110,29 @@ class AvrcpVolumeManager extends AudioDeviceCallback { volumeMapEditor.apply(); } void storeVolume() { void storeVolumeForDevice(BluetoothDevice device) { SharedPreferences.Editor pref = getVolumeMap().edit(); int storeVolume = mAudioManager.getStreamVolume(STREAM_MUSIC); Log.i(TAG, "storeVolume: Storing stream volume level for device " + mCurrentDevice Log.i(TAG, "storeVolume: Storing stream volume level for device " + device + " : " + storeVolume); mVolumeMap.put(mCurrentDevice, storeVolume); pref.putInt(mCurrentDevice.getAddress(), storeVolume); mVolumeMap.put(device, storeVolume); pref.putInt(device.getAddress(), storeVolume); // Always use apply() since it is asynchronous, otherwise the call can hang waiting for // storage to be written. pref.apply(); } 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; } d("getVolume: Returning volume " + mVolumeMap.get(device)); return mVolumeMap.get(device); } synchronized void deviceConnected(@NonNull BluetoothDevice device, boolean absoluteVolume) { d("deviceConnected: device=" + device + " absoluteVolume=" + absoluteVolume); Loading @@ -198,17 +152,23 @@ class AvrcpVolumeManager extends AudioDeviceCallback { return; } // Store the previous volume if a device was active. if (mCurrentDevice != null) { storeVolume(); } // Wait until AudioManager informs us that the new device is connected mDeferredDevice = device; mCurrentDevice = device; // If device is null, that means that there is no active Bluetooth device if (device == null) { mCurrentDevice = null; return; } // If the device is connected then switch the device which informs audio manager that // absolute volume is supported and set the volume for the device. Otherwise disable // absolute volume until the new device connects to prevent sending unattenuated audio. if (mDeviceMap.containsKey(device)) { switchVolumeDevice(device); } else { d("volumeDeviceSwitched: Set Absolute Volume support to false until device connects."); mAudioManager.avrcpSupportsAbsoluteVolume(device.getAddress(), false); } } Loading @@ -220,7 +180,7 @@ class AvrcpVolumeManager extends AudioDeviceCallback { public void dump(StringBuilder sb) { sb.append("AvrcpVolumeManager:\n"); sb.append(" mCurrentDevice: " + mCurrentDevice + "\n"); sb.append(" mDeferredDevice: " + mDeferredDevice + "\n"); sb.append(" Current System Volume: " + mAudioManager.getStreamVolume(STREAM_MUSIC) + "\n"); sb.append(" Device Volume Memory Map:\n"); sb.append(String.format(" %-17s : %-14s : %3s : %s\n", "Device Address", "Device Name", "Vol", "AbsVol")); Loading Loading
android/app/src/com/android/bluetooth/a2dp/A2dpService.java +18 −11 Original line number Diff line number Diff line Loading @@ -438,6 +438,11 @@ public class A2dpService extends ProfileService { if (DBG) { Log.d(TAG, "setActiveDevice(" + device + "): previous is " + previousActiveDevice); } if (previousActiveDevice != null && AvrcpTargetService.get() != null) { AvrcpTargetService.get().storeVolumeForDevice(previousActiveDevice); } if (device == null) { // Clear the active device mActiveDevice = null; Loading @@ -455,7 +460,7 @@ public class A2dpService extends ProfileService { previousActiveDevice, BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.A2DP, getConnectionState(previousActiveDevice) == BluetoothProfile.STATE_CONNECTED); == BluetoothProfile.STATE_CONNECTED, -1); // Make sure the Active device in native layer is set to null and audio is off if (!mA2dpNativeInterface.setActiveDevice(null)) { Log.w(TAG, "setActiveDevice(null): Cannot remove active device in native " Loading Loading @@ -498,11 +503,21 @@ public class A2dpService extends ProfileService { if (previousActiveDevice != null) { mAudioManager.setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent( previousActiveDevice, BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.A2DP, true); BluetoothProfile.A2DP, true, -1); } int rememberedVolume = -1; if (AvrcpTargetService.get() != null) { AvrcpTargetService.get().volumeDeviceSwitched(mActiveDevice); rememberedVolume = AvrcpTargetService.get() .getRememberedVolumeForDevice(mActiveDevice); } mAudioManager.setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent( mActiveDevice, BluetoothProfile.STATE_CONNECTED, BluetoothProfile.A2DP, true); true, rememberedVolume); // Inform the Audio Service about the codec configuration // change, so the Audio Service can reset accordingly the audio // feeding parameters in the Audio HAL to the Bluetooth stack. Loading Loading @@ -806,14 +821,6 @@ public class A2dpService extends ProfileService { Log.d(TAG, "broadcastActiveDevice(" + device + ")"); } // 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); } Intent intent = new Intent(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED); intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT Loading
android/app/src/com/android/bluetooth/newavrcp/AvrcpTargetService.java +22 −3 Original line number Diff line number Diff line Loading @@ -198,9 +198,9 @@ public class AvrcpTargetService extends ProfileService { } /** * Signal to the service that the current audio out device has changed. The current volume * 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. * Signal to the service that the current audio out device has changed and to inform * the audio service whether the new device supports absolute volume. If it does, also * set the absolute volume level on the remote device. */ public void volumeDeviceSwitched(BluetoothDevice device) { if (DEBUG) { Loading @@ -209,6 +209,25 @@ public class AvrcpTargetService extends ProfileService { mVolumeManager.volumeDeviceSwitched(device); } /** * Store the current system volume for a device in order to be retrieved later. */ public void storeVolumeForDevice(BluetoothDevice device) { if (device == null) return; mVolumeManager.storeVolumeForDevice(device); } /** * Retrieve the remembered volume for a device. Returns -1 if there is no volume for the * device. */ public int getRememberedVolumeForDevice(BluetoothDevice device) { if (device == null) return -1; return mVolumeManager.getVolume(device, -1); } // TODO (apanicke): Add checks to blacklist Absolute Volume devices if they behave poorly. void setVolume(int avrcpVolume) { int deviceVolume = Loading
android/app/src/com/android/bluetooth/newavrcp/AvrcpVolumeManager.java +29 −69 Original line number Diff line number Diff line Loading @@ -22,8 +22,6 @@ 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; Loading @@ -31,7 +29,7 @@ import java.util.HashMap; import java.util.Map; import java.util.Objects; class AvrcpVolumeManager extends AudioDeviceCallback { class AvrcpVolumeManager { public static final String TAG = "NewAvrcpVolumeManager"; public static final boolean DEBUG = true; Loading @@ -48,7 +46,6 @@ class AvrcpVolumeManager extends AudioDeviceCallback { HashMap<BluetoothDevice, Boolean> mDeviceMap = new HashMap(); HashMap<BluetoothDevice, Integer> mVolumeMap = new HashMap(); BluetoothDevice mDeferredDevice = null; BluetoothDevice mCurrentDevice = null; boolean mAbsoluteVolumeSupported = false; Loading @@ -67,31 +64,17 @@ class AvrcpVolumeManager extends AudioDeviceCallback { return mContext.getSharedPreferences(VOLUME_MAP, Context.MODE_PRIVATE); } 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(device); } private void switchVolumeDevice(@NonNull BluetoothDevice device) { // Inform the audio manager that the device has changed d("switchVolumeDevice: Set Absolute volume support to " + mDeviceMap.get(device)); 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(device, currVolume); // If the preference volume isn't equal to the current stream volume then that means // we had a stored preference. 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(device)) { int avrcpVolume = systemToAvrcpVolume(savedVolume); Loading @@ -100,43 +83,6 @@ class AvrcpVolumeManager extends AudioDeviceCallback { } } @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; Loading @@ -144,8 +90,6 @@ class AvrcpVolumeManager extends AudioDeviceCallback { mNativeInterface = nativeInterface; sDeviceMaxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC); 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. Loading @@ -166,19 +110,29 @@ class AvrcpVolumeManager extends AudioDeviceCallback { volumeMapEditor.apply(); } void storeVolume() { void storeVolumeForDevice(BluetoothDevice device) { SharedPreferences.Editor pref = getVolumeMap().edit(); int storeVolume = mAudioManager.getStreamVolume(STREAM_MUSIC); Log.i(TAG, "storeVolume: Storing stream volume level for device " + mCurrentDevice Log.i(TAG, "storeVolume: Storing stream volume level for device " + device + " : " + storeVolume); mVolumeMap.put(mCurrentDevice, storeVolume); pref.putInt(mCurrentDevice.getAddress(), storeVolume); mVolumeMap.put(device, storeVolume); pref.putInt(device.getAddress(), storeVolume); // Always use apply() since it is asynchronous, otherwise the call can hang waiting for // storage to be written. pref.apply(); } 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; } d("getVolume: Returning volume " + mVolumeMap.get(device)); return mVolumeMap.get(device); } synchronized void deviceConnected(@NonNull BluetoothDevice device, boolean absoluteVolume) { d("deviceConnected: device=" + device + " absoluteVolume=" + absoluteVolume); Loading @@ -198,17 +152,23 @@ class AvrcpVolumeManager extends AudioDeviceCallback { return; } // Store the previous volume if a device was active. if (mCurrentDevice != null) { storeVolume(); } // Wait until AudioManager informs us that the new device is connected mDeferredDevice = device; mCurrentDevice = device; // If device is null, that means that there is no active Bluetooth device if (device == null) { mCurrentDevice = null; return; } // If the device is connected then switch the device which informs audio manager that // absolute volume is supported and set the volume for the device. Otherwise disable // absolute volume until the new device connects to prevent sending unattenuated audio. if (mDeviceMap.containsKey(device)) { switchVolumeDevice(device); } else { d("volumeDeviceSwitched: Set Absolute Volume support to false until device connects."); mAudioManager.avrcpSupportsAbsoluteVolume(device.getAddress(), false); } } Loading @@ -220,7 +180,7 @@ class AvrcpVolumeManager extends AudioDeviceCallback { public void dump(StringBuilder sb) { sb.append("AvrcpVolumeManager:\n"); sb.append(" mCurrentDevice: " + mCurrentDevice + "\n"); sb.append(" mDeferredDevice: " + mDeferredDevice + "\n"); sb.append(" Current System Volume: " + mAudioManager.getStreamVolume(STREAM_MUSIC) + "\n"); sb.append(" Device Volume Memory Map:\n"); sb.append(String.format(" %-17s : %-14s : %3s : %s\n", "Device Address", "Device Name", "Vol", "AbsVol")); Loading