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

Commit 14be8f71 authored by Łukasz Rymanowski's avatar Łukasz Rymanowski
Browse files

leaudio: Workaround for the CIS->SCO handover

Some controllers have an issue when doing the handover from CIS to SCO.
For this purpose let's sync a bit and notify upper layer when LeAudio
group turned idle if it is on call.

This functionallity is hidden behind
persist.bluetooth.leaudio.notify.idle.during.call

for the moment. To enable notification, the above shall be set to true.

Bug: 242908683
Test: atest BluetoothInstrumentationTests
manual test: Make phone call, switch between classic and LE Audio headset multiple times
Tag: #feature
Merged-In: I2bdd81612f0dd06d77ed44a08c17545121bf6c33
Change-Id: I2bdd81612f0dd06d77ed44a08c17545121bf6c33

(cherry picked from commit 305cb612)
Change-Id: I2f152493f1a6ae897a04df07d178d470b2d2fc07
parent 566be5b9
Loading
Loading
Loading
Loading
+3 −2
Original line number Diff line number Diff line
@@ -1449,8 +1449,9 @@ public class HeadsetService extends ProfileService {
                LeAudioService leAudioService = mFactory.getLeAudioService();
                if (leAudioService != null) {
                    Log.i(TAG, "Make sure there is no le audio device active.");
                    leAudioService.setActiveDevice(null);
                    leAudioService.setInactiveForHfpHandover(mActiveDevice);
                }

                broadcastActiveDevice(mActiveDevice);
                int connectStatus = connectAudio(mActiveDevice);
                if (connectStatus != BluetoothStatusCodes.SUCCESS) {
@@ -1482,7 +1483,7 @@ public class HeadsetService extends ProfileService {
        }
    }

    int connectAudio() {
    public int connectAudio() {
        synchronized (mStateMachines) {
            BluetoothDevice device = mActiveDevice;
            if (device == null) {
+72 −0
Original line number Diff line number Diff line
@@ -60,6 +60,7 @@ import com.android.bluetooth.btservice.AdapterService;
import com.android.bluetooth.btservice.ProfileService;
import com.android.bluetooth.btservice.ServiceFactory;
import com.android.bluetooth.btservice.storage.DatabaseManager;
import com.android.bluetooth.hfp.HeadsetService;
import com.android.bluetooth.mcp.McpService;
import com.android.bluetooth.tbs.TbsGatt;
import com.android.bluetooth.vc.VolumeControlService;
@@ -117,6 +118,7 @@ public class LeAudioService extends ProfileService {

    LeAudioNativeInterface mLeAudioNativeInterface;
    boolean mLeAudioNativeIsInitialized = false;
    BluetoothDevice mHfpHandoverDevice = null;
    LeAudioBroadcasterNativeInterface mLeAudioBroadcasterNativeInterface = null;
    @VisibleForTesting
    AudioManager mAudioManager;
@@ -331,6 +333,7 @@ public class LeAudioService extends ProfileService {
        mLeAudioNativeInterface.cleanup();
        mLeAudioNativeInterface = null;
        mLeAudioNativeIsInitialized = false;
        mHfpHandoverDevice = null;

        // Set the service and BLE devices as inactive
        setLeAudioService(null);
@@ -1173,6 +1176,36 @@ public class LeAudioService extends ProfileService {
        }
    }

    private void handleGroupIdleDuringCall() {
        if (mHfpHandoverDevice == null) {
            if (DBG) {
                Log.d(TAG, "There is no HFP handover");
            }
            return;
        }
        HeadsetService headsetService = mServiceFactory.getHeadsetService();
        if (headsetService == null) {
            if (DBG) {
                Log.d(TAG, "There is no HFP service available");
            }
            return;
        }

        BluetoothDevice activeHfpDevice = headsetService.getActiveDevice();
        if (activeHfpDevice == null) {
            if (DBG) {
                Log.d(TAG, "Make " + mHfpHandoverDevice + " active again ");
            }
            headsetService.setActiveDevice(mHfpHandoverDevice);
        } else {
            if (DBG) {
                Log.d(TAG, "Connect audio to " + activeHfpDevice);
            }
            headsetService.connectAudio();
        }
        mHfpHandoverDevice = null;
    }

    // Suppressed since this is part of a local process
    @SuppressLint("AndroidFrameworkRequiresPermission")
    void messageFromNative(LeAudioStackEvent stackEvent) {
@@ -1338,6 +1371,10 @@ public class LeAudioService extends ProfileService {
                    handleGroupTransitToInactive(groupId);
                    break;
                }
                case LeAudioStackEvent.GROUP_STATUS_TURNED_IDLE_DURING_CALL: {
                    handleGroupIdleDuringCall();
                    break;
                }
                default:
                    break;
            }
@@ -1746,6 +1783,20 @@ public class LeAudioService extends ProfileService {
        mLeAudioNativeInterface.setInCall(inCall);
    }

    /**
     * Set Inactive by HFP during handover
     */
    public void setInactiveForHfpHandover(BluetoothDevice hfpHandoverDevice) {
        if (!mLeAudioNativeIsInitialized) {
            Log.e(TAG, "Le Audio not initialized properly.");
            return;
        }
        if (getActiveGroupId() != LE_AUDIO_GROUP_ID_INVALID) {
            mHfpHandoverDevice = hfpHandoverDevice;
            setActiveDevice(null);
        }
    }

    /**
     * Set connection policy of the profile and connects it if connectionPolicy is
     * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED} or disconnects if connectionPolicy is
@@ -2467,6 +2518,26 @@ public class LeAudioService extends ProfileService {
            }
        }

        @Override
        public void setInactiveForHfpHandover(BluetoothDevice hfpHandoverDevice,
                AttributionSource source,
                SynchronousResultReceiver receiver) {
            try {
                Objects.requireNonNull(source, "source cannot be null");
                Objects.requireNonNull(receiver, "receiver cannot be null");

                LeAudioService service = getService(source);
                if (service == null) {
                    throw new IllegalStateException("service is null");
                }
                enforceBluetoothPrivilegedPermission(service);
                service.setInactiveForHfpHandover(hfpHandoverDevice);
                receiver.send(null);
            } catch (RuntimeException e) {
                receiver.propagateException(e);
            }
        }

        @Override
        public void groupRemoveNode(int groupId, BluetoothDevice device,
                AttributionSource source, SynchronousResultReceiver receiver) {
@@ -2712,6 +2783,7 @@ public class LeAudioService extends ProfileService {
        ProfileService.println(sb, "  currentlyActiveGroupId: " + getActiveGroupId());
        ProfileService.println(sb, "  mActiveAudioOutDevice: " + mActiveAudioOutDevice);
        ProfileService.println(sb, "  mActiveAudioInDevice: " + mActiveAudioInDevice);
        ProfileService.println(sb, "  mHfpHandoverDevice:" + mHfpHandoverDevice);

        for (Map.Entry<Integer, LeAudioGroupDescriptor> entry : mGroupDescriptors.entrySet()) {
            LeAudioGroupDescriptor descriptor = entry.getValue();
+3 −0
Original line number Diff line number Diff line
@@ -55,6 +55,7 @@ public class LeAudioStackEvent {

    static final int GROUP_STATUS_INACTIVE = 0;
    static final int GROUP_STATUS_ACTIVE = 1;
    static final int GROUP_STATUS_TURNED_IDLE_DURING_CALL = 2;

    static final int GROUP_NODE_ADDED = 1;
    static final int GROUP_NODE_REMOVED = 2;
@@ -192,6 +193,8 @@ public class LeAudioStackEvent {
                        return "GROUP_STATUS_ACTIVE";
                    case GROUP_STATUS_INACTIVE:
                        return "GROUP_STATUS_INACTIVE";
                    case GROUP_STATUS_TURNED_IDLE_DURING_CALL:
                        return "GROUP_STATUS_TURNED_IDLE_DURING_CALL";
                    default:
                        break;
                }
+3 −0
Original line number Diff line number Diff line
@@ -67,12 +67,15 @@ oneway interface IBluetoothLeAudio {
    void setCcidInformation(in ParcelUuid userUuid, in int ccid, in int contextType, in AttributionSource attributionSource, in SynchronousResultReceiver receiver);
    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT,android.Manifest.permission.BLUETOOTH_PRIVILEGED})")
    void setInCall(in boolean inCall, in AttributionSource attributionSource, in SynchronousResultReceiver receiver);
    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT,android.Manifest.permission.BLUETOOTH_PRIVILEGED})")
    void setInactiveForHfpHandover(in BluetoothDevice device, in AttributionSource attributionSource, in SynchronousResultReceiver receiver);

    /* Same value as bluetooth::groups::kGroupUnknown */
    const int LE_AUDIO_GROUP_ID_INVALID = -1;

    const int GROUP_STATUS_INACTIVE = 0;
    const int GROUP_STATUS_ACTIVE = 1;
    const int GROUP_STATUS_TURNED_IDLE_DURING_CALL = 2;

    const int GROUP_NODE_ADDED = 1;
    const int GROUP_NODE_REMOVED = 2;
+22 −4
Original line number Diff line number Diff line
@@ -827,15 +827,16 @@ class LeAudioClientImpl : public LeAudioClient {
        return;
      }

      auto group_id_to_close = active_group_id_;
      active_group_id_ = bluetooth::groups::kGroupUnknown;

      if (alarm_is_scheduled(suspend_timeout_)) alarm_cancel(suspend_timeout_);

      StopAudio();
      ClientAudioIntefraceRelease();

      GroupStop(active_group_id_);
      callbacks_->OnGroupStatus(active_group_id_, GroupStatus::INACTIVE);
      active_group_id_ = group_id;

      GroupStop(group_id_to_close);
      callbacks_->OnGroupStatus(group_id_to_close, GroupStatus::INACTIVE);
      return;
    }

@@ -3669,6 +3670,20 @@ class LeAudioClientImpl : public LeAudioClient {
    }
  }

  void NotifyUpperLayerGroupTurnedIdleDuringCall(int group_id) {
    if (!osi_property_get_bool(kNotifyUpperLayerAboutGroupBeingInIdleDuringCall,
                               false)) {
      return;
    }
    /* If group is inactive, phone is in call and Group is not having CIS
     * connected, notify upper layer about it, so it can decide to create SCO if
     * it is in the handover case
     */
    if (in_call_ && active_group_id_ == bluetooth::groups::kGroupUnknown) {
      callbacks_->OnGroupStatus(group_id, GroupStatus::TURNED_IDLE_DURING_CALL);
    }
  }

  void StatusReportCb(int group_id, GroupStreamStatus status) {
    LOG_INFO("status: %d , audio_sender_state %s, audio_receiver_state %s",
             static_cast<int>(status),
@@ -3739,6 +3754,7 @@ class LeAudioClientImpl : public LeAudioClient {
        }
        CancelStreamingRequest();
        if (group) {
          NotifyUpperLayerGroupTurnedIdleDuringCall(group->group_id_);
          HandlePendingAvailableContexts(group);
          HandlePendingDeviceDisconnection(group);
        }
@@ -3776,6 +3792,8 @@ class LeAudioClientImpl : public LeAudioClient {
  AudioState audio_sender_state_;
  /* Keep in call state. */
  bool in_call_;
  static constexpr char kNotifyUpperLayerAboutGroupBeingInIdleDuringCall[] =
      "persist.bluetooth.leaudio.notify.idle.during.call";

  /* Current stream configuration */
  LeAudioCodecConfiguration current_source_codec_config;
Loading