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

Commit d02c2808 authored by Łukasz Rymanowski's avatar Łukasz Rymanowski
Browse files

LeAudio: Make device inactive if Media is not available

When remote device makes MEDIA context unavailable, Bluetooth stack will
not allow to create the Le Audio stream to the device.

Since Audio Framework cannot yet choose audio device based on the
context type, whenever Bluetooth detects that Audio Framework tries to
open multiple times a stream which context is not allowed on the remote
side, the LeAudio device will become inactive and stream will be
redirected to another active audio device.

When MEDIA context will become available again for this device, the LeAudio headset will
be set as active device again.

Note, that if while Media context is unavailable for Device A, and another LeAudio
devive (Device B) will become active,  the Device A will not becaume
Active when Media context type will become available again.

Note: Feature is available only when flag is set:

$> adb shell setprop persist.device_config.aconfig_flags.bluetooth.com.\
android.bluetooth.flags.leaudio_enable_health_based_actions true

Bug: 295546903
Test: atest LeAudioServiceTest
Test: atest bluetooth_le_audio_client_test
Tag; #feature

Change-Id: Ida06877093c0aebadc169d20d287bce0e6b71061
parent 8fff8a98
Loading
Loading
Loading
Loading
+43 −5
Original line number Diff line number Diff line
@@ -186,13 +186,14 @@ public class LeAudioService extends ProfileService {
            mAvailableContexts = 0;
            mInputSelectableConfig = new ArrayList<>();
            mOutputSelectableConfig = new ArrayList<>();
            mInactivatedDueToContextType = false;
        }

        public Boolean mIsConnected;
        public Boolean mIsActive;
        public Boolean mHasFallbackDeviceWhenGettingInactive;
        public Integer mDirection;
        public BluetoothLeAudioCodecStatus mCodecStatus;
        Boolean mIsConnected;
        Boolean mIsActive;
        Boolean mHasFallbackDeviceWhenGettingInactive;
        Integer mDirection;
        BluetoothLeAudioCodecStatus mCodecStatus;
        /* This can be non empty only for the streaming time */
        BluetoothDevice mLostLeadDeviceWhileStreaming;
        BluetoothDevice mCurrentLeadDevice;
@@ -200,6 +201,7 @@ public class LeAudioService extends ProfileService {
        Integer mAvailableContexts;
        List<BluetoothLeAudioCodecConfig> mInputSelectableConfig;
        List<BluetoothLeAudioCodecConfig> mOutputSelectableConfig;
        Boolean mInactivatedDueToContextType;
    }

    private static class LeAudioDeviceDescriptor {
@@ -1522,6 +1524,21 @@ public class LeAudioService extends ProfileService {
        return mActiveAudioOutDevice != null;
    }

    private void clearInactiveDueToContextTypeFlags() {
        synchronized (mGroupLock) {
            for (Map.Entry<Integer, LeAudioGroupDescriptor> groupEntry :
                    mGroupDescriptors.entrySet()) {
                LeAudioGroupDescriptor groupDescriptor = groupEntry.getValue();
                if (groupDescriptor.mInactivatedDueToContextType) {
                    if (DBG) {
                        Log.d(TAG, "clearInactiveDueToContextTypeFlags " + groupEntry.getKey());
                    }
                    groupDescriptor.mInactivatedDueToContextType = false;
                }
            }
        }
    }

    /**
     * Set the active device group.
     *
@@ -1539,6 +1556,7 @@ public class LeAudioService extends ProfileService {
            }

            groupId = descriptor.mGroupId;
            clearInactiveDueToContextTypeFlags();
        }

        int currentlyActiveGroupId = getActiveGroupId();
@@ -1824,6 +1842,13 @@ public class LeAudioService extends ProfileService {
                                                .LE_AUDIO_NONALLOWLIST_GROUP_HEALTH_STATUS_TRENDING_BAD,
                                1);
                break;
            case LeAudioStackEvent.HEALTH_RECOMMENDATION_ACTION_INACTIVATE_GROUP:
                LeAudioGroupDescriptor groupDescriptor = getGroupDescriptor(groupId);
                if (groupDescriptor != null && groupDescriptor.mIsActive) {
                    Log.i(TAG, "Group " + groupId + " is inactivated due to blocked media context");
                    groupDescriptor.mInactivatedDueToContextType = true;
                    setActiveGroupWithDevice(null, true);
                }
            default:
                break;
        }
@@ -2238,6 +2263,15 @@ public class LeAudioService extends ProfileService {
                    descriptor.mDirection = direction;
                    descriptor.mAvailableContexts = available_contexts;
                    updateInbandRingtoneForTheGroup(groupId);

                    boolean mediaIsAvailable =
                            ((descriptor.mAvailableContexts & BluetoothLeAudio.CONTEXT_TYPE_MEDIA)
                                    != 0);

                    if (descriptor.mInactivatedDueToContextType && mediaIsAvailable) {
                        Log.i(TAG, " Media context type again available for " + groupId);
                        setActiveGroupWithDevice(getLeadDeviceForTheGroup(groupId), true);
                    }
                } else {
                    Log.e(TAG, "messageFromNative: no descriptors for group: " + groupId);
                }
@@ -4148,6 +4182,10 @@ public class LeAudioService extends ProfileService {
                        + groupDescriptor.mLostLeadDeviceWhileStreaming);
                ProfileService.println(sb, "  mInbandRingtoneEnabled: "
                        + groupDescriptor.mInbandRingtoneEnabled);
                ProfileService.println(
                        sb,
                        "mInactivatedDueToContextType: "
                                + groupDescriptor.mInactivatedDueToContextType);

                for (Map.Entry<BluetoothDevice, LeAudioDeviceDescriptor> deviceEntry
                        : mDeviceDescriptors.entrySet()) {
+3 −0
Original line number Diff line number Diff line
@@ -60,6 +60,7 @@ public class LeAudioStackEvent {
    static final int HEALTH_RECOMMENDATION_ACTION_NONE = 0;
    static final int HEALTH_RECOMMENDATION_ACTION_DISABLE = 1;
    static final int HEALTH_RECOMMENDATION_ACTION_CONSIDER_DISABLING = 2;
    static final int HEALTH_RECOMMENDATION_ACTION_INACTIVATE_GROUP = 3;

    static final int GROUP_STATUS_INACTIVE = 0;
    static final int GROUP_STATUS_ACTIVE = 1;
@@ -217,6 +218,8 @@ public class LeAudioStackEvent {
                        return "ACTION_DISABLE";
                    case HEALTH_RECOMMENDATION_ACTION_CONSIDER_DISABLING:
                        return "ACTION_CONSIDER_DISABLING";
                    case HEALTH_RECOMMENDATION_ACTION_INACTIVATE_GROUP:
                        return "ACTION_INACTIVATE_GROUP";
                    default:
                        return "UNKNOWN";
                }
+34 −0
Original line number Diff line number Diff line
@@ -1536,6 +1536,40 @@ public class LeAudioServiceTest {
        assertThat(mService.mLeAudioNativeIsInitialized).isTrue();
    }

    @Test
    public void testMediaContextUnavailableForAWhile() {
        doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class));
        connectTestDevice(mSingleDevice, testGroupId);

        String action = BluetoothLeAudio.ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED;
        Integer contexts = BluetoothLeAudio.CONTEXT_TYPE_MEDIA;
        injectAudioConfChanged(testGroupId, contexts, 1);

        // Set group and device as active.
        injectGroupStatusChange(testGroupId, LeAudioStackEvent.GROUP_STATUS_ACTIVE);

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

        LeAudioStackEvent healthBasedGroupAction =
                new LeAudioStackEvent(
                        LeAudioStackEvent.EVENT_TYPE_HEALTH_BASED_GROUP_RECOMMENDATION);
        healthBasedGroupAction.valueInt1 = testGroupId;
        healthBasedGroupAction.valueInt2 =
                LeAudioStackEvent.HEALTH_RECOMMENDATION_ACTION_INACTIVATE_GROUP;
        mService.messageFromNative(healthBasedGroupAction);

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

        injectAudioConfChanged(testGroupId, contexts, 1);
        verify(mAudioManager, times(1))
                .handleBluetoothActiveDeviceChanged(
                        eq(mSingleDevice), any(), any(BluetoothProfileConnectionInfo.class));
    }

    private void sendEventAndVerifyIntentForGroupStatusChanged(int groupId, int groupStatus) {

        onGroupStatusCallbackCalled = false;
+4 −0
Original line number Diff line number Diff line
@@ -3812,6 +3812,10 @@ class LeAudioClientImpl : public LeAudioClient {

    if (!remote_contexts.sink.any() && !remote_contexts.source.any()) {
      LOG_WARN("Requested context type not available on the remote side");
      if (leAudioHealthStatus_) {
        leAudioHealthStatus_->AddStatisticForGroup(
            group, LeAudioHealthGroupStatType::STREAM_CONTEXT_NOT_AVAILABLE);
      }
      return false;
    }

+13 −1
Original line number Diff line number Diff line
@@ -144,6 +144,9 @@ class LeAudioHealthStatusImpl : public LeAudioHealthStatus {
        group->stream_signaling_failures_cnt_++;
        group->stream_failures_cnt_++;
        break;
      case LeAudioHealthGroupStatType::STREAM_CONTEXT_NOT_AVAILABLE:
        group->stream_context_not_avail_cnt_++;
        break;
    }

    LeAudioHealthBasedAction action = LeAudioHealthBasedAction::NONE;
@@ -152,12 +155,20 @@ class LeAudioHealthStatusImpl : public LeAudioHealthStatus {
      if ((group->stream_failures_cnt_ >=
           MAX_ALLOWED_FAILURES_IN_A_ROW_WITHOUT_SUCCESS)) {
        action = LeAudioHealthBasedAction::DISABLE;
      } else if (group->stream_context_not_avail_cnt_ >=
                 MAX_ALLOWED_FAILURES_IN_A_ROW_WITHOUT_SUCCESS) {
        action = LeAudioHealthBasedAction::INACTIVATE_GROUP;
        group->stream_context_not_avail_cnt_ = 0;
      }
    } else {
      /* Had some success before */
      if ((100 * group->stream_failures_cnt_ / group->stream_success_cnt_) >=
          THRESHOLD_FOR_DISABLE_CONSIDERATION) {
        action = LeAudioHealthBasedAction::CONSIDER_DISABLING;
      } else if (group->stream_context_not_avail_cnt_ >=
                 MAX_ALLOWED_FAILURES_IN_A_ROW_WITHOUT_SUCCESS) {
        action = LeAudioHealthBasedAction::INACTIVATE_GROUP;
        group->stream_context_not_avail_cnt_ = 0;
      }
    }

@@ -195,7 +206,8 @@ class LeAudioHealthStatusImpl : public LeAudioHealthStatus {
           << ", success: " << group.stream_success_cnt_
           << ", fail total: " << group.stream_failures_cnt_
           << ", fail cis: " << group.stream_cis_failures_cnt_
           << ", fail signaling: " << group.stream_signaling_failures_cnt_;
           << ", fail signaling: " << group.stream_signaling_failures_cnt_
           << ", context not avail: " << group.stream_context_not_avail_cnt_;

    dprintf(fd, "%s", stream.str().c_str());
  }
Loading