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

Commit 5eafe734 authored by Łukasz Rymanowski's avatar Łukasz Rymanowski
Browse files

leaudio: Notify AF about configuration change on codec and freq change

When AF is notified about codec change it pauses a music for a sec.
With this change, AF will be notified about new configuration if
codec or sampling rate did change.

Also Bluetooth sends update to upper layer when reconfiguration is
scheduled or stream is starting. Previously it was done after stream was
started.

Bug: 333993332
Bug: 326442537
Test: mmm packages/modules/Bluetooth
Test: manual test make a voip while streaming and drop the call
Flag: com.android.bluetooth.flags.leaudio_codec_config_callback_order_fix
Change-Id: I47a3610824bd85b73594bd62cfee86c708ed85e1
parent 03a60e64
Loading
Loading
Loading
Loading
+125 −14
Original line number Diff line number Diff line
@@ -3019,7 +3019,11 @@ public class LeAudioService extends ProfileService {
        mDialingOutTimeoutEvent = null;
    }

    void notifyAudioFrameworkForCodecConfigUpdate(int groupId, LeAudioGroupDescriptor descriptor) {
    void notifyAudioFrameworkForCodecConfigUpdate(
            int groupId,
            LeAudioGroupDescriptor descriptor,
            boolean outputCodecOrFreqChanged,
            boolean inputCodecOrFreqChanged) {
        Log.i(TAG, "notifyAudioFrameworkForCodecConfigUpdate groupId: " + groupId);

        if (!Flags.leaudioCodecConfigCallbackOrderFix()) {
@@ -3027,7 +3031,7 @@ public class LeAudioService extends ProfileService {
            return;
        }

        if (mActiveAudioOutDevice != null) {
        if (mActiveAudioOutDevice != null && outputCodecOrFreqChanged) {
            int volume = getAudioDeviceGroupVolume(groupId);

            final BluetoothProfileConnectionInfo connectionInfo;
@@ -3042,14 +3046,115 @@ public class LeAudioService extends ProfileService {
                    mActiveAudioOutDevice, mActiveAudioOutDevice, connectionInfo);
        }

        if (mActiveAudioInDevice != null) {
        if (mActiveAudioInDevice != null && inputCodecOrFreqChanged) {
            mAudioManager.handleBluetoothActiveDeviceChanged(
                    mActiveAudioOutDevice,
                    mActiveAudioOutDevice,
                    mActiveAudioInDevice,
                    mActiveAudioInDevice,
                    BluetoothProfileConnectionInfo.createLeAudioInfo(false, false));
        }
    }

    boolean isOutputCodecOfSampleFrequencyChanged(
            BluetoothLeAudioCodecStatus previous, BluetoothLeAudioCodecStatus next) {
        if ((previous == null) && (next == null)) {
            return false;
        }

        if ((previous == null) || (next == null)) {
            Log.d(TAG, previous + " != " + next);
            return true;
        }

        if ((previous.getOutputCodecConfig() == null) && (next.getOutputCodecConfig() == null)) {
            /* Nothing changed here.*/
            return false;
        }

        if ((previous.getOutputCodecConfig() == null || next.getOutputCodecConfig() == null)) {
            Log.d(
                    TAG,
                    "New output codec: "
                            + (previous.getOutputCodecConfig()
                                    + " != "
                                    + next.getOutputCodecConfig()));
            return true;
        }

        if (previous.getOutputCodecConfig().getCodecType()
                != next.getOutputCodecConfig().getCodecType()) {
            Log.d(
                    TAG,
                    "Different output codec type: "
                            + (previous.getOutputCodecConfig().getCodecType()
                                    + " != "
                                    + next.getOutputCodecConfig().getCodecType()));
            return true;
        }
        if (previous.getOutputCodecConfig().getSampleRate()
                != next.getOutputCodecConfig().getSampleRate()) {
            Log.d(
                    TAG,
                    "Different output samplerate: "
                            + (previous.getOutputCodecConfig().getSampleRate()
                                    + " != "
                                    + next.getOutputCodecConfig().getSampleRate()));
            return true;
        }

        return false;
    }

    boolean isInputCodecOfSampleFrequencyChanged(
            BluetoothLeAudioCodecStatus previous, BluetoothLeAudioCodecStatus next) {
        if ((previous == null) && (next == null)) {
            return false;
        }

        if ((previous == null) || (next == null)) {
            Log.d(TAG, previous + " != " + next);
            return true;
        }

        if ((previous.getInputCodecConfig() == null) && (next.getInputCodecConfig() == null)) {
            /* Nothing changed here.*/
            return false;
        }

        if ((previous.getInputCodecConfig() == null) || (next.getInputCodecConfig() == null)) {
            Log.d(
                    TAG,
                    "New input codec: "
                            + (previous.getInputCodecConfig()
                                    + " != "
                                    + next.getInputCodecConfig()));
            return true;
        }

        if (previous.getInputCodecConfig().getCodecType()
                != next.getInputCodecConfig().getCodecType()) {
            Log.d(
                    TAG,
                    "Different input codec type: "
                            + (previous.getInputCodecConfig().getCodecType()
                                    + " != "
                                    + next.getInputCodecConfig().getCodecType()));
            return true;
        }

        if (previous.getInputCodecConfig().getSampleRate()
                != next.getInputCodecConfig().getSampleRate()) {
            Log.d(
                    TAG,
                    "Different input samplerate: "
                            + (previous.getInputCodecConfig().getSampleRate()
                                    + " != "
                                    + next.getInputCodecConfig().getSampleRate()));
            return true;
        }

        return false;
    }

    // Suppressed since this is part of a local process
    @SuppressLint("AndroidFrameworkRequiresPermission")
    void messageFromNative(LeAudioStackEvent stackEvent) {
@@ -3199,18 +3304,24 @@ public class LeAudioService extends ProfileService {
                            descriptor.mInputSelectableConfig,
                            descriptor.mOutputSelectableConfig);

            if (descriptor.mCodecStatus != null) {
                Log.d(TAG, " Replacing codec status for group: " + groupId);
            } else {
                Log.d(TAG, " New codec status for group: " + groupId);
            }
            boolean outputCodecOrFreqChanged =
                    isOutputCodecOfSampleFrequencyChanged(descriptor.mCodecStatus, status);
            boolean inputCodecOrFreqChanged =
                    isInputCodecOfSampleFrequencyChanged(descriptor.mCodecStatus, status);

            Log.d(
                    TAG,
                    ("Codec update for group:" + groupId)
                            + (", outputCodecOrFreqChanged: " + outputCodecOrFreqChanged)
                            + (", inputCodecOrFreqChanged: " + inputCodecOrFreqChanged));

            descriptor.mCodecStatus = status;
            mHandler.post(() -> notifyUnicastCodecConfigChanged(groupId, status));

            if (descriptor.isActive()) {
            if (descriptor.isActive() && (outputCodecOrFreqChanged || inputCodecOrFreqChanged)) {
                // Audio framework needs to be notified so it get new codec config
                notifyAudioFrameworkForCodecConfigUpdate(groupId, descriptor);
                notifyAudioFrameworkForCodecConfigUpdate(
                        groupId, descriptor, outputCodecOrFreqChanged, inputCodecOrFreqChanged);
            }
        } else if (stackEvent.type == LeAudioStackEvent.EVENT_TYPE_AUDIO_CONF_CHANGED) {
            int direction = stackEvent.valueInt1;
+123 −2
Original line number Diff line number Diff line
@@ -52,8 +52,8 @@ import android.media.BluetoothProfileConnectionInfo;
import android.os.Handler;
import android.os.Looper;
import android.os.ParcelUuid;
import android.sysprop.BluetoothProperties;
import android.platform.test.flag.junit.SetFlagsRule;
import android.sysprop.BluetoothProperties;

import androidx.test.filters.MediumTest;
import androidx.test.platform.app.InstrumentationRegistry;
@@ -2171,7 +2171,8 @@ public class LeAudioServiceTest {

    /** Test native interface group status message handling */
    @Test
    public void testMessageFromNativeGroupCodecConfigChanged() {
    public void testMessageFromNativeGroupCodecConfigChangedNonActiveDevice() {
        mSetFlagsRule.enableFlags(Flags.FLAG_LEAUDIO_CODEC_CONFIG_CALLBACK_ORDER_FIX);
        onGroupCodecConfChangedCallbackCalled = false;

        injectLocalCodecConfigCapaChanged(INPUT_CAPABILITIES_CONFIG, OUTPUT_CAPABILITIES_CONFIG);
@@ -2213,15 +2214,135 @@ public class LeAudioServiceTest {

        injectGroupSelectableCodecConfigChanged(
                testGroupId, INPUT_SELECTABLE_CONFIG, OUTPUT_SELECTABLE_CONFIG);
        // Inject configuration and check that AF is NOT notified.
        injectGroupCurrentCodecConfigChanged(testGroupId, LC3_16KHZ_CONFIG, LC3_48KHZ_CONFIG);

        TestUtils.waitForLooperToFinishScheduledTask(mService.getMainLooper());
        assertThat(onGroupCodecConfChangedCallbackCalled).isTrue();

        verify(mAudioManager, times(0))
                .handleBluetoothActiveDeviceChanged(
                        any(), any(), any(BluetoothProfileConnectionInfo.class));

        onGroupCodecConfChangedCallbackCalled = false;

        // Now inject again new configuration and check that AF is not notified.
        testCodecStatus =
                new BluetoothLeAudioCodecStatus(
                        LC3_16KHZ_CONFIG,
                        LC3_16KHZ_CONFIG,
                        INPUT_CAPABILITIES_CONFIG,
                        OUTPUT_CAPABILITIES_CONFIG,
                        INPUT_SELECTABLE_CONFIG,
                        OUTPUT_SELECTABLE_CONFIG);

        injectGroupCurrentCodecConfigChanged(testGroupId, LC3_16KHZ_CONFIG, LC3_16KHZ_CONFIG);

        TestUtils.waitForLooperToFinishScheduledTask(mService.getMainLooper());
        assertThat(onGroupCodecConfChangedCallbackCalled).isTrue();

        verify(mAudioManager, times(0))
                .handleBluetoothActiveDeviceChanged(
                        any(), any(), any(BluetoothProfileConnectionInfo.class));

        onGroupCodecConfChangedCallbackCalled = false;
        mService.mLeAudioCallbacks.unregister(leAudioCallbacks);
    }

    /** Test native interface group status message handling */
    @Test
    public void testMessageFromNativeGroupCodecConfigChangedActiveDevice_DifferentConfiguration() {
        mSetFlagsRule.enableFlags(Flags.FLAG_LEAUDIO_CODEC_CONFIG_CALLBACK_ORDER_FIX);
        onGroupCodecConfChangedCallbackCalled = false;

        injectLocalCodecConfigCapaChanged(INPUT_CAPABILITIES_CONFIG, OUTPUT_CAPABILITIES_CONFIG);

        doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class));
        connectTestDevice(mSingleDevice, testGroupId);

        testCodecStatus =
                new BluetoothLeAudioCodecStatus(
                        LC3_16KHZ_CONFIG,
                        LC3_48KHZ_CONFIG,
                        INPUT_CAPABILITIES_CONFIG,
                        OUTPUT_CAPABILITIES_CONFIG,
                        INPUT_SELECTABLE_CONFIG,
                        OUTPUT_SELECTABLE_CONFIG);

        IBluetoothLeAudioCallback leAudioCallbacks =
                new IBluetoothLeAudioCallback.Stub() {
                    @Override
                    public void onCodecConfigChanged(int gid, BluetoothLeAudioCodecStatus status) {
                        onGroupCodecConfChangedCallbackCalled = true;
                        assertThat(status.equals(testCodecStatus)).isTrue();
                    }

                    @Override
                    public void onGroupStatusChanged(int gid, int gStatus) {}

                    @Override
                    public void onGroupNodeAdded(BluetoothDevice device, int gid) {}

                    @Override
                    public void onGroupNodeRemoved(BluetoothDevice device, int gid) {}

                    @Override
                    public void onGroupStreamStatusChanged(int groupId, int groupStreamStatus) {}
                };

        synchronized (this.mService.mLeAudioCallbacks) {
            mService.mLeAudioCallbacks.register(leAudioCallbacks);
        }

        injectGroupSelectableCodecConfigChanged(
                testGroupId, INPUT_SELECTABLE_CONFIG, OUTPUT_SELECTABLE_CONFIG);

        injectGroupCurrentCodecConfigChanged(testGroupId, LC3_16KHZ_CONFIG, LC3_48KHZ_CONFIG);

        injectAudioConfChanged(
                testGroupId,
                BluetoothLeAudio.CONTEXT_TYPE_MEDIA | BluetoothLeAudio.CONTEXT_TYPE_CONVERSATIONAL,
                3);

        injectGroupStatusChange(testGroupId, LeAudioStackEvent.GROUP_STATUS_ACTIVE);

        TestUtils.waitForLooperToFinishScheduledTask(mService.getMainLooper());
        assertThat(onGroupCodecConfChangedCallbackCalled).isTrue();

        verify(mAudioManager, times(2))
                .handleBluetoothActiveDeviceChanged(
                        any(), any(), any(BluetoothProfileConnectionInfo.class));

        onGroupCodecConfChangedCallbackCalled = false;
        reset(mAudioManager);

        // Now inject configuration different sample rate on one direction
        testCodecStatus =
                new BluetoothLeAudioCodecStatus(
                        LC3_16KHZ_CONFIG,
                        LC3_16KHZ_CONFIG,
                        INPUT_CAPABILITIES_CONFIG,
                        OUTPUT_CAPABILITIES_CONFIG,
                        INPUT_SELECTABLE_CONFIG,
                        OUTPUT_SELECTABLE_CONFIG);

        injectGroupCurrentCodecConfigChanged(testGroupId, LC3_16KHZ_CONFIG, LC3_16KHZ_CONFIG);

        TestUtils.waitForLooperToFinishScheduledTask(mService.getMainLooper());
        assertThat(onGroupCodecConfChangedCallbackCalled).isTrue();

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

        synchronized (this.mService.mLeAudioCallbacks) {
            mService.mLeAudioCallbacks.unregister(leAudioCallbacks);
        }

        onGroupCodecConfChangedCallbackCalled = false;
        reset(mAudioManager);
    }

    /** Test native interface group status message handling */
    @Test
    public void testMessageFromNativeGroupCodecConfigChanged_OneDirectionOnly() {
+12 −12
Original line number Diff line number Diff line
@@ -856,13 +856,15 @@ public:
      ccid_contexts.source.unset(LeAudioContextType::CONVERSATIONAL);
    }

    bool group_is_streaming = group->IsStreaming();

    BidirectionalPair<std::vector<uint8_t>> ccids = {
            .sink = ContentControlIdKeeper::GetInstance()->GetAllCcids(ccid_contexts.sink),
            .source = ContentControlIdKeeper::GetInstance()->GetAllCcids(ccid_contexts.source)};
    if (group->IsPendingConfiguration()) {
      return groupStateMachine_->ConfigureStream(group, configuration_context_type_,
                                                 remote_contexts, ccids);
    } else if (group->GetState() != AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) {
    } else if (!group_is_streaming) {
      stream_setup_start_timestamp_ = bluetooth::common::time_get_os_boottime_us();
    }

@@ -870,7 +872,7 @@ public:
     * when there would be request to stream unicast.
     */
    if (com::android::bluetooth::flags::leaudio_broadcast_audio_handover_policies() &&
        !sink_monitor_mode_ && source_monitor_mode_ && !group->IsStreaming()) {
        !sink_monitor_mode_ && source_monitor_mode_ && !group_is_streaming) {
      callbacks_->OnUnicastMonitorModeStatus(bluetooth::le_audio::types::kLeAudioDirectionSource,
                                             UnicastMonitorModeStatus::STREAMING_REQUESTED);
    }
@@ -878,6 +880,13 @@ public:
    bool result = groupStateMachine_->StartStream(group, configuration_context_type,
                                                  remote_contexts, ccids);

    if (result && !group_is_streaming) {
      /* Notify Java about new configuration when start stream has been accepted and
       * it is not metadata update
       */
      SendAudioGroupCurrentCodecConfigChanged(group);
    }

    return result;
  }

@@ -4374,6 +4383,7 @@ public:
    /* Need to reconfigure stream */
    group->SetPendingConfiguration();
    groupStateMachine_->StopStream(group);
    SendAudioGroupCurrentCodecConfigChanged(group);
    return true;
  }

@@ -5280,22 +5290,12 @@ public:
                std::bind(&LeAudioClientImpl::UpdateAudioConfigToHal, weak_factory_.GetWeakPtr(),
                          std::placeholders::_1, std::placeholders::_2));

        /* When at least one direction is started we can assume new
         * configuration here */
        bool new_configuration = false;
        if (audio_sender_state_ == AudioState::READY_TO_START) {
          StartSendingAudio(group_id);
          new_configuration = true;
        }

        if (audio_receiver_state_ == AudioState::READY_TO_START) {
          StartReceivingAudio(group_id);
          new_configuration = true;
        }

        if (new_configuration) {
          /* Notify Java about new configuration */
          SendAudioGroupCurrentCodecConfigChanged(group);
        }
        break;
      }