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

Commit ebd5f301 authored by Łukasz Rymanowski's avatar Łukasz Rymanowski Committed by Jack He
Browse files

LeAudioService: Store volume for the group

With this patch, if volume was previously known for the group,
it will be used when device become Active again

Bug: 233975367
Test: atest BluetoothInstrumentationTests
Tag: #feature
Change-Id: Ic1fae47b4e94975dffc69ee27f641c64cbf0df61
Merged-In: Ic1fae47b4e94975dffc69ee27f641c64cbf0df61
(cherry picked from commit 90b1b642cd5557364d3045da448e7d42560e2b2c)
(cherry picked from commit 41b2b4e6)
parent ce4e30ed
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -346,7 +346,7 @@ static void setVolumeNative(JNIEnv* env, jobject object, jbyteArray address,
  env->ReleaseByteArrayElements(address, addr, 0);
}

static void setVolumeGroupNative(JNIEnv* env, jobject object, jint group_id,
static void setGroupVolumeNative(JNIEnv* env, jobject object, jint group_id,
                                 jint volume) {
  if (!sVolumeControlInterface) {
    LOG(ERROR) << __func__
@@ -547,7 +547,7 @@ static JNINativeMethod sMethods[] = {
    {"disconnectVolumeControlNative", "([B)Z",
     (void*)disconnectVolumeControlNative},
    {"setVolumeNative", "([BI)V", (void*)setVolumeNative},
    {"setVolumeGroupNative", "(II)V", (void*)setVolumeGroupNative},
    {"setGroupVolumeNative", "(II)V", (void*)setGroupVolumeNative},
    {"muteNative", "([B)V", (void*)muteNative},
    {"muteGroupNative", "(I)V", (void*)muteGroupNative},
    {"unmuteNative", "([B)V", (void*)unmuteNative},
+44 −4
Original line number Diff line number Diff line
@@ -121,6 +121,9 @@ public class LeAudioService extends ProfileService {
    AudioManager mAudioManager;
    LeAudioTmapGattServer mTmapGattServer;

    @VisibleForTesting
    VolumeControlService mVolumeControlService;

    @VisibleForTesting
    RemoteCallbackList<IBluetoothLeBroadcastCallback> mBroadcastCallbacks;

@@ -379,6 +382,7 @@ public class LeAudioService extends ProfileService {

        mAdapterService = null;
        mAudioManager = null;
        mVolumeControlService = null;

        return true;
    }
@@ -407,6 +411,18 @@ public class LeAudioService extends ProfileService {
        sLeAudioService = instance;
    }

    private int getGroupVolume(int groupId) {
        if (mVolumeControlService == null) {
            mVolumeControlService = mServiceFactory.getVolumeControlService();
        }
        if (mVolumeControlService == null) {
            Log.e(TAG, "Volume control service is not available");
            return -1;
        }

        return mVolumeControlService.getGroupVolume(groupId);
    }

    public boolean connect(BluetoothDevice device) {
        if (DBG) {
            Log.d(TAG, "connect(): " + device);
@@ -822,6 +838,7 @@ public class LeAudioService extends ProfileService {
                        + previousInDevice + ", mActiveAudioInDevice" + mActiveAudioInDevice
                        + " isLeOutput: false");
            }

            mAudioManager.handleBluetoothActiveDeviceChanged(mActiveAudioInDevice,previousInDevice,
                    BluetoothProfileConnectionInfo.createLeAudioInfo(false, false));

@@ -890,9 +907,14 @@ public class LeAudioService extends ProfileService {
                        + previousOutDevice + ", mActiveOutDevice: " + mActiveAudioOutDevice
                        + " isLeOutput: true");
            }
            int volume = -1;
            if (mActiveAudioOutDevice != null) {
                volume = getGroupVolume(groupId);
            }

            mAudioManager.handleBluetoothActiveDeviceChanged(mActiveAudioOutDevice,
                    previousOutDevice,
                    BluetoothProfileConnectionInfo.createLeAudioInfo(suppressNoisyIntent, true));
                    getLeAudioOutputProfile(suppressNoisyIntent, volume));
            return true;
        }
        Log.d(TAG, "updateActiveOutDevice: Nothing to do.");
@@ -1045,6 +1067,22 @@ public class LeAudioService extends ProfileService {
        }
    }

    BluetoothProfileConnectionInfo getLeAudioOutputProfile(boolean suppressNoisyIntent,
            int volume) {
        /* TODO - b/236618595 */
        Parcel parcel = Parcel.obtain();
        parcel.writeInt(BluetoothProfile.LE_AUDIO);
        parcel.writeBoolean(suppressNoisyIntent);
        parcel.writeInt(volume);
        parcel.writeBoolean(true /* isLeOutput */);
        parcel.setDataPosition(0);

        BluetoothProfileConnectionInfo profileInfo =
                BluetoothProfileConnectionInfo.CREATOR.createFromParcel(parcel);
        parcel.recycle();
        return profileInfo;
    }

    BluetoothProfileConnectionInfo getBroadcastProfile(boolean suppressNoisyIntent) {
        Parcel parcel = Parcel.obtain();
        parcel.writeInt(BluetoothProfile.LE_AUDIO_BROADCAST);
@@ -1754,9 +1792,11 @@ public class LeAudioService extends ProfileService {
            return;
        }

        VolumeControlService service = mServiceFactory.getVolumeControlService();
        if (service != null) {
            service.setVolumeGroup(currentlyActiveGroupId, volume);
        if (mVolumeControlService == null) {
            mVolumeControlService = mServiceFactory.getVolumeControlService();
        }
        if (mVolumeControlService != null) {
            mVolumeControlService.setGroupVolume(currentlyActiveGroupId, volume);
        }
    }

+3 −3
Original line number Diff line number Diff line
@@ -113,8 +113,8 @@ public class VolumeControlNativeInterface {
     * @param volume
     */
    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
    public void setVolumeGroup(int groupId, int volume) {
        setVolumeGroupNative(groupId, volume);
    public void setGroupVolume(int groupId, int volume) {
        setGroupVolumeNative(groupId, volume);
    }

     /**
@@ -374,7 +374,7 @@ public class VolumeControlNativeInterface {
    private native boolean connectVolumeControlNative(byte[] address);
    private native boolean disconnectVolumeControlNative(byte[] address);
    private native void setVolumeNative(byte[] address, int volume);
    private native void setVolumeGroupNative(int groupId, int volume);
    private native void setGroupVolumeNative(int groupId, int volume);
    private native void muteNative(byte[] address);
    private native void muteGroupNative(int groupId);
    private native void unmuteNative(byte[] address);
+43 −4
Original line number Diff line number Diff line
@@ -181,6 +181,7 @@ public class VolumeControlService extends ProfileService {
    private final Map<BluetoothDevice, VolumeControlStateMachine> mStateMachines = new HashMap<>();
    private final Map<BluetoothDevice, VolumeControlOffsetDescriptor> mAudioOffsets =
                                                                            new HashMap<>();
    private final Map<Integer, Integer> mGroupVolumeCache = new HashMap<>();

    private BroadcastReceiver mBondStateChangedReceiver;
    private BroadcastReceiver mConnectionStateChangedReceiver;
@@ -246,6 +247,7 @@ public class VolumeControlService extends ProfileService {
        registerReceiver(mConnectionStateChangedReceiver, filter);

        mAudioOffsets.clear();
        mGroupVolumeCache.clear();
        mCallbacks = new RemoteCallbackList<IBluetoothVolumeControlCallback>();

        // Mark service as started
@@ -300,6 +302,7 @@ public class VolumeControlService extends ProfileService {
        mVolumeControlNativeInterface = null;

        mAudioOffsets.clear();
        mGroupVolumeCache.clear();

        // Clear AdapterService, VolumeControlNativeInterface
        mAudioManager = null;
@@ -580,8 +583,17 @@ public class VolumeControlService extends ProfileService {
    /**
     * {@hide}
     */
    public void setVolumeGroup(int groupId, int volume) {
        mVolumeControlNativeInterface.setVolumeGroup(groupId, volume);
    public void setGroupVolume(int groupId, int volume) {
        mGroupVolumeCache.put(groupId, volume);
        mVolumeControlNativeInterface.setGroupVolume(groupId, volume);
    }

    /**
     * {@hide}
     * @param groupId
     */
    public int getGroupVolume(int groupId) {
        return mGroupVolumeCache.getOrDefault(groupId, -1);
    }

    /**
@@ -620,6 +632,11 @@ public class VolumeControlService extends ProfileService {
        }
        // TODO: Handle the other arguments: device, groupId, mute.

        /* We are interested only in the group volume as any LeAudio device is a part of group */
        if (device == null) {
            mGroupVolumeCache.put(groupId, volume);
        }

        int streamType = getBluetoothContextualVolumeStream();
        mAudioManager.setStreamVolume(streamType, getDeviceVolume(streamType, volume),
                AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_BLUETOOTH_ABS_VOLUME);
@@ -1127,7 +1144,7 @@ public class VolumeControlService extends ProfileService {
        }

        @Override
        public void setVolumeGroup(int groupId, int volume, AttributionSource source,
        public void setGroupVolume(int groupId, int volume, AttributionSource source,
                SynchronousResultReceiver receiver) {
            try {
                Objects.requireNonNull(source, "source cannot be null");
@@ -1135,7 +1152,7 @@ public class VolumeControlService extends ProfileService {

                VolumeControlService service = getService(source);
                if (service != null) {
                    service.setVolumeGroup(groupId, volume);
                    service.setGroupVolume(groupId, volume);
                }
                receiver.send(null);
            } catch (RuntimeException e) {
@@ -1143,6 +1160,24 @@ public class VolumeControlService extends ProfileService {
            }
        }

        @Override
        public void getGroupVolume(int groupId, AttributionSource source,
                SynchronousResultReceiver receiver) {
            try {
                Objects.requireNonNull(source, "source cannot be null");
                Objects.requireNonNull(receiver, "receiver cannot be null");

                VolumeControlService service = getService(source);
                if (service != null) {
                    service.getGroupVolume(groupId);
                }
                receiver.send(null);
            } catch (RuntimeException e) {
                receiver.propagateException(e);
            }
        }


        @Override
        public void mute(BluetoothDevice device,  AttributionSource source,
                SynchronousResultReceiver receiver) {
@@ -1273,5 +1308,9 @@ public class VolumeControlService extends ProfileService {
            ProfileService.println(sb, "    Volume offset cnt: " + descriptor.size());
            descriptor.dump(sb);
        }
        for (Map.Entry<Integer, Integer> entry : mGroupVolumeCache.entrySet()) {
            ProfileService.println(sb, "    GroupId: " + entry.getKey() + " volume: "
                            + entry.getValue());
        }
    }
}
+54 −0
Original line number Diff line number Diff line
@@ -47,6 +47,7 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.media.AudioManager;
import android.media.BluetoothProfileConnectionInfo;
import android.os.Parcel;
import android.os.ParcelUuid;

import androidx.test.InstrumentationRegistry;
@@ -58,6 +59,7 @@ import com.android.bluetooth.R;
import com.android.bluetooth.TestUtils;
import com.android.bluetooth.btservice.AdapterService;
import com.android.bluetooth.btservice.storage.DatabaseManager;
import com.android.bluetooth.vc.VolumeControlService;

import org.junit.After;
import org.junit.Assume;
@@ -65,6 +67,7 @@ import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;
@@ -105,6 +108,7 @@ public class LeAudioServiceTest {
    @Mock private DatabaseManager mDatabaseManager;
    @Mock private LeAudioNativeInterface mNativeInterface;
    @Mock private AudioManager mAudioManager;
    @Mock private VolumeControlService mVolumeControlService;
    @Mock private LeAudioTmapGattServer mTmapGattServer;
    @Spy private LeAudioObjectsFactory mObjectsFactory = LeAudioObjectsFactory.getInstance();

@@ -179,6 +183,7 @@ public class LeAudioServiceTest {
        startService();
        mService.mLeAudioNativeInterface = mNativeInterface;
        mService.mAudioManager = mAudioManager;
        mService.mVolumeControlService = mVolumeControlService;

        LeAudioStackEvent stackEvent =
        new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_NATIVE_INITIALIZED);
@@ -1400,4 +1405,53 @@ public class LeAudioServiceTest {
        verify(mAudioManager, times(1)).handleBluetoothActiveDeviceChanged(eq(null), eq(leadDevice),
                any(BluetoothProfileConnectionInfo.class));
    }

    /**
     * Test volume caching for the group
     */
    @Test
    public void testVolumeCache() {
        int groupId = 1;
        int volume = 100;
        int availableContexts = 4;

        doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class));
        connectTestDevice(mLeftDevice, groupId);
        connectTestDevice(mRightDevice, groupId);

        assertThat(mService.setActiveDevice(mLeftDevice)).isTrue();

        ArgumentCaptor<BluetoothProfileConnectionInfo> profileInfo =
                        ArgumentCaptor.forClass(BluetoothProfileConnectionInfo.class);

        //Add location support.
        injectAudioConfChanged(groupId, availableContexts);

        doReturn(-1).when(mVolumeControlService).getGroupVolume(groupId);
        //Set group and device as active.
        injectGroupStatusChange(groupId, LeAudioStackEvent.GROUP_STATUS_ACTIVE);

        verify(mAudioManager, times(1)).handleBluetoothActiveDeviceChanged(any(), eq(null),
                        profileInfo.capture());
        assertThat(profileInfo.getValue().getVolume()).isEqualTo(-1);

        mService.setVolume(volume);
        verify(mVolumeControlService, times(1)).setGroupVolume(groupId, volume);

        // Set group to inactive.
        injectGroupStatusChange(groupId, LeAudioStackEvent.GROUP_STATUS_INACTIVE);

        verify(mAudioManager, times(1)).handleBluetoothActiveDeviceChanged(eq(null), any(),
                        any(BluetoothProfileConnectionInfo.class));

        doReturn(100).when(mVolumeControlService).getGroupVolume(groupId);

        //Set back to active and check if last volume is restored.
        injectGroupStatusChange(groupId, LeAudioStackEvent.GROUP_STATUS_ACTIVE);

        verify(mAudioManager, times(2)).handleBluetoothActiveDeviceChanged(any(), eq(null),
                        profileInfo.capture());

        assertThat(profileInfo.getValue().getVolume()).isEqualTo(volume);
    }
}
Loading