Loading android/app/src/com/android/bluetooth/vc/VolumeControlService.java +24 −7 Original line number Diff line number Diff line Loading @@ -785,12 +785,13 @@ public class VolumeControlService extends ProfileService { + (", flags: " + flags)); /* We are here, because system has just started and LeAudio device is connected. If * remote device has User Persistent flag set, Android sets the volume to local cache * and to the audio system. * and to the audio system if not already streaming to other devices. * If Reset Flag is set, then Android sets to remote devices either cached volume volume * taken from audio manager. * Note, to match BR/EDR behavior, don't show volume change in UI here */ if ((flags & VOLUME_FLAGS_PERSISTED_USER_SET_VOLUME_MASK) == 0x01) { if (((flags & VOLUME_FLAGS_PERSISTED_USER_SET_VOLUME_MASK) == 0x01) && (getConnectedDevices().size() == 1)) { updateGroupCacheAndAudioSystem(groupId, volume, mute, false); return; } Loading Loading @@ -1138,12 +1139,12 @@ public class VolumeControlService extends ProfileService { input.setPropSettings(id, unit, min, max); } void messageFromNative(VolumeControlStackEvent stackEvent) { void handleStackEvent(VolumeControlStackEvent stackEvent) { if (!isAvailable()) { Log.e(TAG, "Event ignored, service not available: " + stackEvent); return; } Log.d(TAG, "messageFromNative: " + stackEvent); Log.d(TAG, "handleStackEvent: " + stackEvent); if (stackEvent.type == VolumeControlStackEvent.EVENT_TYPE_VOLUME_STATE_CHANGED) { handleVolumeControlChanged( Loading Loading @@ -1182,20 +1183,36 @@ public class VolumeControlService extends ProfileService { return; } Log.e(TAG, "Unhandled event: " + stackEvent); } void messageFromNative(VolumeControlStackEvent stackEvent) { Log.d(TAG, "messageFromNative: " + stackEvent); // Group events should be handled here directly boolean isGroupEvent = (stackEvent.device == null); if (isGroupEvent) { handleStackEvent(stackEvent); return; } // Other device events should be serialized via their state machines so they are processed // in the same order they were sent from the native code. synchronized (mStateMachines) { VolumeControlStateMachine sm = mStateMachines.get(device); VolumeControlStateMachine sm = mStateMachines.get(stackEvent.device); if (sm == null) { if (stackEvent.type == VolumeControlStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED) { switch (stackEvent.valueInt1) { case STATE_CONNECTED, STATE_CONNECTING -> { sm = getOrCreateStateMachine(device); sm = getOrCreateStateMachine(stackEvent.device); } } } } if (sm == null) { Log.e(TAG, "Cannot process stack event: no state machine: " + stackEvent); Log.w(TAG, "Cannot forward stack event: no state machine: " + stackEvent); handleStackEvent(stackEvent); return; } sm.sendMessage(VolumeControlStateMachine.MESSAGE_STACK_EVENT, stackEvent); Loading android/app/src/com/android/bluetooth/vc/VolumeControlStackEvent.java +0 −2 Original line number Diff line number Diff line Loading @@ -102,8 +102,6 @@ public class VolumeControlStackEvent { private static String eventTypeValue2ToString(int type, int value) { switch (type) { case EVENT_TYPE_CONNECTION_STATE_CHANGED: return BluetoothProfile.getConnectionStateName(value); case EVENT_TYPE_VOLUME_STATE_CHANGED: return "{volume:" + value + "}"; case EVENT_TYPE_DEVICE_AVAILABLE: Loading android/app/src/com/android/bluetooth/vc/VolumeControlStateMachine.java +18 −4 Original line number Diff line number Diff line Loading @@ -157,7 +157,10 @@ class VolumeControlStateMachine extends StateMachine { case VolumeControlStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED -> { processConnectionEvent(event.valueInt1); } default -> Log.e(TAG, "Disconnected: ignoring stack event: " + event); default -> { Log.e(TAG, "Disconnected: forwarding stack event: " + event); mService.handleStackEvent(event); } } } default -> { Loading Loading @@ -262,7 +265,14 @@ class VolumeControlStateMachine extends StateMachine { case VolumeControlStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED -> { processConnectionEvent(event.valueInt1); } default -> Log.e(TAG, "Connecting: ignoring stack event: " + event); case VolumeControlStackEvent.EVENT_TYPE_VOLUME_STATE_CHANGED -> { Log.w(TAG, "Defer volume change received while connecting: " + mDevice); deferMessage(message); } default -> { Log.e(TAG, "Connecting: forwarding stack event: " + event); mService.handleStackEvent(event); } } } default -> { Loading Loading @@ -355,7 +365,10 @@ class VolumeControlStateMachine extends StateMachine { case VolumeControlStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED -> { processConnectionEvent(event.valueInt1); } default -> Log.e(TAG, "Disconnecting: ignoring stack event: " + event); default -> { Log.e(TAG, "Disconnecting: forwarding stack event: " + event); mService.handleStackEvent(event); } } } default -> { Loading Loading @@ -452,7 +465,8 @@ class VolumeControlStateMachine extends StateMachine { processConnectionEvent(event.valueInt1); } default -> { Log.e(TAG, "Connected: ignoring stack event: " + event); Log.e(TAG, "Connected: forwarding stack event: " + event); mService.handleStackEvent(event); } } } Loading android/app/tests/unit/src/com/android/bluetooth/vc/VolumeControlServiceTest.java +11 −4 Original line number Diff line number Diff line Loading @@ -610,8 +610,8 @@ public class VolumeControlServiceTest { (int) Math.round((double) (volumeDevice * MEDIA_MAX_VOL) / BT_LE_AUDIO_MAX_VOL); verify(mAudioManager).setStreamVolume(anyInt(), eq(expectedAfVol), anyInt()); // Connect second device and read different volume. Expect it will be set to AF and to // another set member // Connect second device and read different volume. Expect it will NOT be set to AF // and to another set member, but the existing volume gets applied to it generateDeviceAvailableMessageFromNative(mDeviceTwo, 1); generateConnectionMessageFromNative(mDeviceTwo, STATE_CONNECTED, STATE_DISCONNECTED); assertThat(mService.getConnectionState(mDeviceTwo)).isEqualTo(STATE_CONNECTED); Loading @@ -625,9 +625,12 @@ public class VolumeControlServiceTest { flags, initialMuteState, initialAutonomousFlag); expectedAfVol = expectedAfVol = volumeDevice; int unexpectedAfVol = (int) Math.round((double) (volumeDeviceTwo * MEDIA_MAX_VOL) / BT_LE_AUDIO_MAX_VOL); verify(mAudioManager).setStreamVolume(anyInt(), eq(expectedAfVol), anyInt()); verify(mAudioManager, times(0)).setStreamVolume(anyInt(), eq(unexpectedAfVol), anyInt()); verify(mNativeInterface).setGroupVolume(eq(groupId), eq(expectedAfVol)); } private void testConnectedDeviceWithResetFlag( Loading Loading @@ -1276,6 +1279,7 @@ public class VolumeControlServiceTest { stackEvent.valueBool1 = mute; stackEvent.valueBool2 = isAutonomous; mService.messageFromNative(stackEvent); mLooper.dispatchAll(); } private void generateDeviceOffsetChangedMessageFromNative( Loading @@ -1288,6 +1292,7 @@ public class VolumeControlServiceTest { event.valueInt1 = extOffsetIndex; // external output index event.valueInt2 = offset; // offset value mService.messageFromNative(event); mLooper.dispatchAll(); } private void generateDeviceLocationChangedMessageFromNative( Loading @@ -1300,6 +1305,7 @@ public class VolumeControlServiceTest { event.valueInt1 = extOffsetIndex; // external output index event.valueInt2 = location; // location mService.messageFromNative(event); mLooper.dispatchAll(); } private void generateDeviceDescriptionChangedMessageFromNative( Loading @@ -1312,6 +1318,7 @@ public class VolumeControlServiceTest { event.valueInt1 = extOffsetIndex; // external output index event.valueString1 = description; // description mService.messageFromNative(event); mLooper.dispatchAll(); } @SafeVarargs Loading Loading
android/app/src/com/android/bluetooth/vc/VolumeControlService.java +24 −7 Original line number Diff line number Diff line Loading @@ -785,12 +785,13 @@ public class VolumeControlService extends ProfileService { + (", flags: " + flags)); /* We are here, because system has just started and LeAudio device is connected. If * remote device has User Persistent flag set, Android sets the volume to local cache * and to the audio system. * and to the audio system if not already streaming to other devices. * If Reset Flag is set, then Android sets to remote devices either cached volume volume * taken from audio manager. * Note, to match BR/EDR behavior, don't show volume change in UI here */ if ((flags & VOLUME_FLAGS_PERSISTED_USER_SET_VOLUME_MASK) == 0x01) { if (((flags & VOLUME_FLAGS_PERSISTED_USER_SET_VOLUME_MASK) == 0x01) && (getConnectedDevices().size() == 1)) { updateGroupCacheAndAudioSystem(groupId, volume, mute, false); return; } Loading Loading @@ -1138,12 +1139,12 @@ public class VolumeControlService extends ProfileService { input.setPropSettings(id, unit, min, max); } void messageFromNative(VolumeControlStackEvent stackEvent) { void handleStackEvent(VolumeControlStackEvent stackEvent) { if (!isAvailable()) { Log.e(TAG, "Event ignored, service not available: " + stackEvent); return; } Log.d(TAG, "messageFromNative: " + stackEvent); Log.d(TAG, "handleStackEvent: " + stackEvent); if (stackEvent.type == VolumeControlStackEvent.EVENT_TYPE_VOLUME_STATE_CHANGED) { handleVolumeControlChanged( Loading Loading @@ -1182,20 +1183,36 @@ public class VolumeControlService extends ProfileService { return; } Log.e(TAG, "Unhandled event: " + stackEvent); } void messageFromNative(VolumeControlStackEvent stackEvent) { Log.d(TAG, "messageFromNative: " + stackEvent); // Group events should be handled here directly boolean isGroupEvent = (stackEvent.device == null); if (isGroupEvent) { handleStackEvent(stackEvent); return; } // Other device events should be serialized via their state machines so they are processed // in the same order they were sent from the native code. synchronized (mStateMachines) { VolumeControlStateMachine sm = mStateMachines.get(device); VolumeControlStateMachine sm = mStateMachines.get(stackEvent.device); if (sm == null) { if (stackEvent.type == VolumeControlStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED) { switch (stackEvent.valueInt1) { case STATE_CONNECTED, STATE_CONNECTING -> { sm = getOrCreateStateMachine(device); sm = getOrCreateStateMachine(stackEvent.device); } } } } if (sm == null) { Log.e(TAG, "Cannot process stack event: no state machine: " + stackEvent); Log.w(TAG, "Cannot forward stack event: no state machine: " + stackEvent); handleStackEvent(stackEvent); return; } sm.sendMessage(VolumeControlStateMachine.MESSAGE_STACK_EVENT, stackEvent); Loading
android/app/src/com/android/bluetooth/vc/VolumeControlStackEvent.java +0 −2 Original line number Diff line number Diff line Loading @@ -102,8 +102,6 @@ public class VolumeControlStackEvent { private static String eventTypeValue2ToString(int type, int value) { switch (type) { case EVENT_TYPE_CONNECTION_STATE_CHANGED: return BluetoothProfile.getConnectionStateName(value); case EVENT_TYPE_VOLUME_STATE_CHANGED: return "{volume:" + value + "}"; case EVENT_TYPE_DEVICE_AVAILABLE: Loading
android/app/src/com/android/bluetooth/vc/VolumeControlStateMachine.java +18 −4 Original line number Diff line number Diff line Loading @@ -157,7 +157,10 @@ class VolumeControlStateMachine extends StateMachine { case VolumeControlStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED -> { processConnectionEvent(event.valueInt1); } default -> Log.e(TAG, "Disconnected: ignoring stack event: " + event); default -> { Log.e(TAG, "Disconnected: forwarding stack event: " + event); mService.handleStackEvent(event); } } } default -> { Loading Loading @@ -262,7 +265,14 @@ class VolumeControlStateMachine extends StateMachine { case VolumeControlStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED -> { processConnectionEvent(event.valueInt1); } default -> Log.e(TAG, "Connecting: ignoring stack event: " + event); case VolumeControlStackEvent.EVENT_TYPE_VOLUME_STATE_CHANGED -> { Log.w(TAG, "Defer volume change received while connecting: " + mDevice); deferMessage(message); } default -> { Log.e(TAG, "Connecting: forwarding stack event: " + event); mService.handleStackEvent(event); } } } default -> { Loading Loading @@ -355,7 +365,10 @@ class VolumeControlStateMachine extends StateMachine { case VolumeControlStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED -> { processConnectionEvent(event.valueInt1); } default -> Log.e(TAG, "Disconnecting: ignoring stack event: " + event); default -> { Log.e(TAG, "Disconnecting: forwarding stack event: " + event); mService.handleStackEvent(event); } } } default -> { Loading Loading @@ -452,7 +465,8 @@ class VolumeControlStateMachine extends StateMachine { processConnectionEvent(event.valueInt1); } default -> { Log.e(TAG, "Connected: ignoring stack event: " + event); Log.e(TAG, "Connected: forwarding stack event: " + event); mService.handleStackEvent(event); } } } Loading
android/app/tests/unit/src/com/android/bluetooth/vc/VolumeControlServiceTest.java +11 −4 Original line number Diff line number Diff line Loading @@ -610,8 +610,8 @@ public class VolumeControlServiceTest { (int) Math.round((double) (volumeDevice * MEDIA_MAX_VOL) / BT_LE_AUDIO_MAX_VOL); verify(mAudioManager).setStreamVolume(anyInt(), eq(expectedAfVol), anyInt()); // Connect second device and read different volume. Expect it will be set to AF and to // another set member // Connect second device and read different volume. Expect it will NOT be set to AF // and to another set member, but the existing volume gets applied to it generateDeviceAvailableMessageFromNative(mDeviceTwo, 1); generateConnectionMessageFromNative(mDeviceTwo, STATE_CONNECTED, STATE_DISCONNECTED); assertThat(mService.getConnectionState(mDeviceTwo)).isEqualTo(STATE_CONNECTED); Loading @@ -625,9 +625,12 @@ public class VolumeControlServiceTest { flags, initialMuteState, initialAutonomousFlag); expectedAfVol = expectedAfVol = volumeDevice; int unexpectedAfVol = (int) Math.round((double) (volumeDeviceTwo * MEDIA_MAX_VOL) / BT_LE_AUDIO_MAX_VOL); verify(mAudioManager).setStreamVolume(anyInt(), eq(expectedAfVol), anyInt()); verify(mAudioManager, times(0)).setStreamVolume(anyInt(), eq(unexpectedAfVol), anyInt()); verify(mNativeInterface).setGroupVolume(eq(groupId), eq(expectedAfVol)); } private void testConnectedDeviceWithResetFlag( Loading Loading @@ -1276,6 +1279,7 @@ public class VolumeControlServiceTest { stackEvent.valueBool1 = mute; stackEvent.valueBool2 = isAutonomous; mService.messageFromNative(stackEvent); mLooper.dispatchAll(); } private void generateDeviceOffsetChangedMessageFromNative( Loading @@ -1288,6 +1292,7 @@ public class VolumeControlServiceTest { event.valueInt1 = extOffsetIndex; // external output index event.valueInt2 = offset; // offset value mService.messageFromNative(event); mLooper.dispatchAll(); } private void generateDeviceLocationChangedMessageFromNative( Loading @@ -1300,6 +1305,7 @@ public class VolumeControlServiceTest { event.valueInt1 = extOffsetIndex; // external output index event.valueInt2 = location; // location mService.messageFromNative(event); mLooper.dispatchAll(); } private void generateDeviceDescriptionChangedMessageFromNative( Loading @@ -1312,6 +1318,7 @@ public class VolumeControlServiceTest { event.valueInt1 = extOffsetIndex; // external output index event.valueString1 = description; // description mService.messageFromNative(event); mLooper.dispatchAll(); } @SafeVarargs Loading