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

Commit 022e726f authored by Rahul Sabnis's avatar Rahul Sabnis Committed by Gerrit Code Review
Browse files

Merge changes from topic "dualmode-audio-flag"

* changes:
  Uses the dual mode audio system property throughout the dual mode audio codebase
  Updates active device policy to support dual mode audio
parents 38fea653 3b5b19f7
Loading
Loading
Loading
Loading
+10 −1
Original line number Diff line number Diff line
@@ -97,7 +97,7 @@ public final class Utils {

    private static final String ENABLE_DUAL_MODE_AUDIO =
            "persist.bluetooth.enable_dual_mode_audio";
    private static final boolean sDualModeEnabled =
    private static boolean sDualModeEnabled =
            SystemProperties.getBoolean(ENABLE_DUAL_MODE_AUDIO, false);;

    private static final String KEY_TEMP_ALLOW_LIST_DURATION_MS = "temp_allow_list_duration_ms";
@@ -148,6 +148,15 @@ public final class Utils {
        return sDualModeEnabled;
    }

    /**
     * Only exposed for testing, do not invoke this method outside of tests.
     * @param enabled true if the dual mode state is enabled, false otherwise
     */
    public static void setDualModeAudioStateForTesting(boolean enabled) {
        Log.i(TAG, "Updating dual mode audio state for testing to: " + enabled);
        sDualModeEnabled = enabled;
    }

    public static @Nullable String getName(@Nullable BluetoothDevice device) {
        final AdapterService service = AdapterService.getAdapterService();
        if (service != null && device != null) {
+99 −20
Original line number Diff line number Diff line
@@ -42,6 +42,7 @@ import android.os.Looper;
import android.util.ArraySet;
import android.util.Log;

import com.android.bluetooth.Utils;
import com.android.bluetooth.a2dp.A2dpService;
import com.android.bluetooth.btservice.storage.DatabaseManager;
import com.android.bluetooth.hearingaid.HearingAidService;
@@ -236,6 +237,17 @@ class ActiveDeviceManager {
        }
    }

    /**
     * Handles the active device logic for when A2DP is connected. Does the following:
     * 1. Check if a hearing aid device is active. We will always prefer hearing aid devices, so if
     * one is active, we will not make this A2DP device active.
     * 2. If there is no hearing aid device active, we will make this A2DP device active.
     * 3. We will make this device active for HFP if it's already connected to HFP
     * 4. If dual mode is disabled, we clear the LE Audio active device to ensure mutual exclusion
     * between classic and LE audio.
     *
     * @param device is the device that was connected to A2DP
     */
    private void handleA2dpConnected(BluetoothDevice device) {
        synchronized (mLock) {
            if (DBG) {
@@ -252,7 +264,9 @@ class ActiveDeviceManager {
                if (mHfpConnectedDevices.contains(device)) {
                    setA2dpActiveDevice(device);
                    setHfpActiveDevice(device);
                    if (!Utils.isDualModeAudioEnabled()) {
                        setLeAudioActiveDevice(null, true);
                    }
                    return;
                }
                DatabaseManager dbManager = mAdapterService.getDatabase();
@@ -260,12 +274,25 @@ class ActiveDeviceManager {
                if (dbManager.getProfileConnectionPolicy(device, BluetoothProfile.HEADSET)
                        != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
                    setA2dpActiveDevice(device);
                    if (!Utils.isDualModeAudioEnabled()) {
                        setLeAudioActiveDevice(null, true);
                    }
                }
            }
        }
    }

    /**
     * Handles the active device logic for when HFP is connected. Does the following:
     * 1. Check if a hearing aid device is active. We will always prefer hearing aid devices, so if
     * one is active, we will not make this HFP device active.
     * 2. If there is no hearing aid device active, we will make this HFP device active.
     * 3. We will make this device active for A2DP if it's already connected to A2DP
     * 4. If dual mode is disabled, we clear the LE Audio active device to ensure mutual exclusion
     * between classic and LE audio.
     *
     * @param device is the device that was connected to A2DP
     */
    private void handleHfpConnected(BluetoothDevice device) {
        synchronized (mLock) {
            if (DBG) {
@@ -281,7 +308,9 @@ class ActiveDeviceManager {
                if (mA2dpConnectedDevices.contains(device)) {
                    setA2dpActiveDevice(device);
                    setHfpActiveDevice(device);
                    if (!Utils.isDualModeAudioEnabled()) {
                        setLeAudioActiveDevice(null, true);
                    }
                    return;
                }
                DatabaseManager dbManager = mAdapterService.getDatabase();
@@ -289,11 +318,13 @@ class ActiveDeviceManager {
                if (dbManager.getProfileConnectionPolicy(device, BluetoothProfile.A2DP)
                        != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
                    setHfpActiveDevice(device);
                    if (!Utils.isDualModeAudioEnabled()) {
                        setLeAudioActiveDevice(null, true);
                    }
                }
            }
        }
    }

    private void handleHearingAidConnected(BluetoothDevice device) {
        synchronized (mLock) {
@@ -334,8 +365,10 @@ class ActiveDeviceManager {
                    && mPendingLeHearingAidActiveDevice.isEmpty()) {
                // New connected device: select it as active
                setLeAudioActiveDevice(device);
                if (!Utils.isDualModeAudioEnabled()) {
                    setA2dpActiveDevice(null, true);
                    setHfpActiveDevice(null);
                }
            } else if (mPendingLeHearingAidActiveDevice.contains(device)) {
                setLeHearingAidActiveDevice(device);
                setHearingAidActiveDevice(null);
@@ -450,15 +483,32 @@ class ActiveDeviceManager {
        }
    }

    /**
     * Handles the active device logic for when the A2DP active device changes. Does the following:
     * 1. Clear the active hearing aid.
     * 2. If dual mode is enabled and all supported classic audio profiles are enabled, makes this
     * device active for LE Audio. If not, clear the LE Audio active device.
     * 3. Make HFP active for this device if it is already connected to HFP.
     * 4. Stores the new A2DP active device.
     *
     * @param device is the device that was connected to A2DP
     */
    private void handleA2dpActiveDeviceChanged(BluetoothDevice device) {
        synchronized (mLock) {
            if (DBG) {
                Log.d(TAG, "handleA2dpActiveDeviceChanged: " + device);
            }
            if (device != null && !Objects.equals(mA2dpActiveDevice, device)) {
            if (!Objects.equals(mA2dpActiveDevice, device)) {
                if (device != null) {
                    setHearingAidActiveDevice(null);
                }
                if (Utils.isDualModeAudioEnabled()
                        && mAdapterService.isAllSupportedClassicAudioProfilesActive(device)) {
                    setLeAudioActiveDevice(device);
                } else {
                    setLeAudioActiveDevice(null, true);
                }
            }
            if (mHfpConnectedDevices.contains(device)) {
                setHfpActiveDevice(device);
            }
@@ -467,15 +517,32 @@ class ActiveDeviceManager {
        }
    }

    /**
     * Handles the active device logic for when the HFP active device changes. Does the following:
     * 1. Clear the active hearing aid.
     * 2. If dual mode is enabled and all supported classic audio profiles are enabled, makes this
     * device active for LE Audio. If not, clear the LE Audio active device.
     * 3. Make A2DP active for this device if it is already connected to A2DP.
     * 4. Stores the new HFP active device.
     *
     * @param device is the device that was connected to A2DP
     */
    private void handleHfpActiveDeviceChanged(BluetoothDevice device) {
        synchronized (mLock) {
            if (DBG) {
                Log.d(TAG, "handleHfpActiveDeviceChanged: " + device);
            }
            if (device != null && !Objects.equals(mHfpActiveDevice, device)) {
            if (!Objects.equals(mHfpActiveDevice, device)) {
                if (device != null) {
                    setHearingAidActiveDevice(null);
                }
                if (Utils.isDualModeAudioEnabled()
                        && mAdapterService.isAllSupportedClassicAudioProfilesActive(device)) {
                    setLeAudioActiveDevice(device);
                } else {
                    setLeAudioActiveDevice(null, true);
                }
            }
            if (mA2dpConnectedDevices.contains(device)) {
                setA2dpActiveDevice(device);
            }
@@ -519,8 +586,10 @@ class ActiveDeviceManager {
            }
            // Just assign locally the new value
            if (device != null && !Objects.equals(mLeAudioActiveDevice, device)) {
                if (!Utils.isDualModeAudioEnabled()) {
                    setA2dpActiveDevice(null, true);
                    setHfpActiveDevice(null);
                }
                setHearingAidActiveDevice(null);
            }

@@ -849,16 +918,22 @@ class ActiveDeviceManager {
                    setA2dpActiveDevice(device);
                    if (headsetFallbackDevice != null) {
                        setHfpActiveDevice(device);
                        /* If dual mode is enabled, LEA will be made active once all supported
                        classic audio profiles are made active for the device. */
                        if (!Utils.isDualModeAudioEnabled()) {
                            setLeAudioActiveDevice(null, true);
                        }
                    }
                } else {
                    if (DBG) {
                        Log.d(TAG, "set LE audio device active: " + device);
                    }
                    setLeAudioActiveDevice(device);
                    if (!Utils.isDualModeAudioEnabled()) {
                        setA2dpActiveDevice(null, true);
                        setHfpActiveDevice(null);
                    }
                }
            } else {
                if (Objects.equals(headsetFallbackDevice, device)) {
                    if (DBG) {
@@ -867,17 +942,21 @@ class ActiveDeviceManager {
                    setHfpActiveDevice(device);
                    if (a2dpFallbackDevice != null) {
                        setA2dpActiveDevice(a2dpFallbackDevice);
                        if (!Utils.isDualModeAudioEnabled()) {
                            setLeAudioActiveDevice(null, true);
                        }
                    }
                } else {
                    if (DBG) {
                        Log.d(TAG, "set LE audio device active: " + device);
                    }
                    setLeAudioActiveDevice(device);
                    if (!Utils.isDualModeAudioEnabled()) {
                        setA2dpActiveDevice(null, true);
                        setHfpActiveDevice(null);
                    }
                }
            }
            return true;
        }

+47 −3
Original line number Diff line number Diff line
@@ -29,6 +29,7 @@ import static com.android.bluetooth.Utils.enforceDumpPermission;
import static com.android.bluetooth.Utils.enforceLocalMacAddressPermission;
import static com.android.bluetooth.Utils.getBytesFromAddress;
import static com.android.bluetooth.Utils.hasBluetoothPrivilegedPermission;
import static com.android.bluetooth.Utils.isDualModeAudioEnabled;
import static com.android.bluetooth.Utils.isPackageNameAccurate;

import android.annotation.NonNull;
@@ -1284,8 +1285,9 @@ public class AdapterService extends Service {
     * @param profile           is the profile we are checking for support
     * @return true if the profile is supported by both the local and remote device, false otherwise
     */
    @VisibleForTesting
    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
    private boolean isProfileSupported(BluetoothDevice device, int profile) {
    boolean isProfileSupported(BluetoothDevice device, int profile) {
        ParcelUuid[] remoteDeviceUuids = getRemoteUuids(device);
        ParcelUuid[] localDeviceUuids = mAdapterProperties.getUuids();
        if (remoteDeviceUuids == null || remoteDeviceUuids.length == 0) {
@@ -4611,6 +4613,11 @@ public class AdapterService extends Service {
            }
            enforceBluetoothPrivilegedPermission(service);

            // If LE only mode is enabled, the dual mode audio feature is disabled
            if (!isDualModeAudioEnabled()) {
                return BluetoothStatusCodes.FEATURE_NOT_SUPPORTED;
            }

            service.mPreferredAudioProfilesCallbacks.register(callback);
            return BluetoothStatusCodes.SUCCESS;
        }
@@ -4764,8 +4771,9 @@ public class AdapterService extends Service {
     * @param device is the remote device whose preferences we want to fetch
     * @return a Bundle containing the preferred audio profiles for the device
     */
    private Bundle getPreferredAudioProfiles(BluetoothDevice device) {
        if (mLeAudioService == null || !isDualModeAudioSinkDevice(device)) {
    public Bundle getPreferredAudioProfiles(BluetoothDevice device) {
        if (!isDualModeAudioEnabled() || mLeAudioService == null
                || !isDualModeAudioSinkDevice(device)) {
            return Bundle.EMPTY;
        }
        // Gets the lead device in the CSIP group to set the preference
@@ -4812,10 +4820,17 @@ public class AdapterService extends Service {
     * @return whether the preferences were successfully requested
     */
    private int setPreferredAudioProfiles(BluetoothDevice device, Bundle modeToProfileBundle) {
        Log.i(TAG, "setPreferredAudioProfiles for device=" + device.getAddressForLogging());
        if (!isDualModeAudioEnabled()) {
            Log.e(TAG, "setPreferredAudioProfiles called while sysprop is disabled");
            return BluetoothStatusCodes.FEATURE_NOT_SUPPORTED;
        }
        if (mLeAudioService == null) {
            Log.e(TAG, "setPreferredAudioProfiles: LEA service is not up");
            return BluetoothStatusCodes.ERROR_PROFILE_NOT_CONNECTED;
        }
        if (!isDualModeAudioSinkDevice(device)) {
            Log.e(TAG, "setPreferredAudioProfiles: Not a dual mode audio device");
            return BluetoothStatusCodes.ERROR_NOT_DUAL_MODE_AUDIO_DEVICE;
        }
        // Gets the lead device in the CSIP group to set the preference
@@ -5346,6 +5361,35 @@ public class AdapterService extends Service {
        return true;
    }

    /**
     * Checks if all supported classic audio profiles are active on this LE Audio device.
     * @param leAudioDevice the remote device
     * @return {@code true} if all supported classic audio profiles are active on this device,
     * {@code false} otherwise
     */
    public boolean isAllSupportedClassicAudioProfilesActive(BluetoothDevice leAudioDevice) {
        if (mLeAudioService == null) {
            return false;
        }
        boolean a2dpSupported = isProfileSupported(leAudioDevice, BluetoothProfile.A2DP);
        boolean hfpSupported = isProfileSupported(leAudioDevice, BluetoothProfile.HEADSET);

        List<BluetoothDevice> groupDevices = mLeAudioService.getGroupDevices(leAudioDevice);
        if (hfpSupported && mHeadsetService != null) {
            BluetoothDevice activeHfpDevice = mHeadsetService.getActiveDevice();
            if (activeHfpDevice == null || !groupDevices.contains(activeHfpDevice)) {
                return false;
            }
        }
        if (a2dpSupported && mA2dpService != null) {
            BluetoothDevice activeA2dpDevice = mA2dpService.getActiveDevice();
            if (activeA2dpDevice == null || !groupDevices.contains(activeA2dpDevice)) {
                return false;
            }
        }
        return true;
    }

    /**
     * Get the active devices for the BluetoothProfile specified
     *
+9 −11
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@

package com.android.bluetooth.btservice;

import static com.android.bluetooth.Utils.isDualModeAudioEnabled;

import android.annotation.RequiresPermission;
import android.bluetooth.BluetoothA2dp;
import android.bluetooth.BluetoothAdapter;
@@ -96,8 +98,6 @@ class PhonePolicy {
    private static final int MESSAGE_PROFILE_ACTIVE_DEVICE_CHANGED = 5;
    private static final int MESSAGE_DEVICE_CONNECTED = 6;

    @VisibleForTesting static final String PREFER_LE_AUDIO_ONLY_MODE =
            "persist.bluetooth.prefer_le_audio_only_mode";
    @VisibleForTesting static final String AUTO_CONNECT_PROFILES_PROPERTY =
            "bluetooth.auto_connect_profiles.enabled";

@@ -111,8 +111,6 @@ class PhonePolicy {
    private final HashSet<BluetoothDevice> mHeadsetRetrySet = new HashSet<>();
    private final HashSet<BluetoothDevice> mA2dpRetrySet = new HashSet<>();
    private final HashSet<BluetoothDevice> mConnectOtherProfilesDeviceSet = new HashSet<>();

    @VisibleForTesting boolean mPreferLeAudioOnlyMode;
    @VisibleForTesting boolean mAutoConnectProfilesSupported;

    // Broadcast receiver for all changes to states of various profiles
@@ -293,7 +291,6 @@ class PhonePolicy {
                "DatabaseManager cannot be null when PhonePolicy starts");
        mFactory = factory;
        mHandler = new PhonePolicyHandler(service.getMainLooper());
        mPreferLeAudioOnlyMode = SystemProperties.getBoolean(PREFER_LE_AUDIO_ONLY_MODE, true);
        mAutoConnectProfilesSupported = SystemProperties.getBoolean(
                AUTO_CONNECT_PROFILES_PROPERTY, false);
    }
@@ -342,7 +339,7 @@ class PhonePolicy {
                || Utils.arrayContains(uuids, BluetoothUuid.HFP)) && (
                headsetService.getConnectionPolicy(device)
                        == BluetoothProfile.CONNECTION_POLICY_UNKNOWN))) {
            if (mPreferLeAudioOnlyMode && isLeAudioProfileAllowed) {
            if (!isDualModeAudioEnabled() && isLeAudioProfileAllowed) {
                debugLog("clear hfp profile priority for the le audio dual mode device "
                        + device);
                mAdapterService.getDatabase().setProfileConnectionPolicy(device,
@@ -362,7 +359,7 @@ class PhonePolicy {
                || Utils.arrayContains(uuids, BluetoothUuid.ADV_AUDIO_DIST)) && (
                a2dpService.getConnectionPolicy(device)
                        == BluetoothProfile.CONNECTION_POLICY_UNKNOWN)) {
            if (mPreferLeAudioOnlyMode && isLeAudioProfileAllowed) {
            if (!isDualModeAudioEnabled() && isLeAudioProfileAllowed) {
                debugLog("clear a2dp profile priority for the le audio dual mode device "
                        + device);
                mAdapterService.getDatabase().setProfileConnectionPolicy(device,
@@ -523,19 +520,20 @@ class PhonePolicy {

    /**
     * Updates the last connection date in the connection order database for the newly active device
     * if connected to a2dp profile. If the device is LE audio dual mode device,
     * mPreferLeAudioOnlyMode is true, and LEA is made active, A2DP/HFP will be disconnected.
     * if connected to the A2DP profile. If this is a dual mode audio device (supports classic and
     * LE Audio), LE Audio is made active, and {@link Utils#isDualModeAudioEnabled()} is false,
     * A2DP and HFP will be disconnected.
     *
     * @param device is the device we just made the active device
     */
    private void processActiveDeviceChanged(BluetoothDevice device, int profileId) {
        debugLog("processActiveDeviceChanged, device=" + device + ", profile=" + profileId
                + " mPreferLeAudioOnlyMode: " + mPreferLeAudioOnlyMode);
                + " isDualModeAudioEnabled=" + isDualModeAudioEnabled());

        if (device != null) {
            mDatabaseManager.setConnection(device, profileId == BluetoothProfile.A2DP);

            if (!mPreferLeAudioOnlyMode) return;
            if (isDualModeAudioEnabled()) return;
            if (profileId == BluetoothProfile.LE_AUDIO) {
                A2dpService a2dpService = mFactory.getA2dpService();
                HeadsetService hsService = mFactory.getHeadsetService();
+42 −2
Original line number Diff line number Diff line
@@ -740,7 +740,34 @@ public class LeAudioService extends ProfileService {
        return result;
    }

    private Integer getActiveGroupId() {
    /**
     * Get all the devices within a given group.
     * @param device the device for which we want to get all devices in its group
     * @return all devices within a given group or empty list
     */
    public List<BluetoothDevice> getGroupDevices(BluetoothDevice device) {
        List<BluetoothDevice> result = new ArrayList<>();
        int groupId = getGroupId(device);

        if (groupId == LE_AUDIO_GROUP_ID_INVALID) {
            return result;
        }

        synchronized (mGroupLock) {
            for (Map.Entry<BluetoothDevice, LeAudioDeviceDescriptor> entry
                    : mDeviceDescriptors.entrySet()) {
                if (entry.getValue().mGroupId == groupId) {
                    result.add(entry.getKey());
                }
            }
        }
        return result;
    }

    /**
     * Get the active device group id
     */
    public Integer getActiveGroupId() {
        synchronized (mGroupLock) {
            for (Map.Entry<Integer, LeAudioGroupDescriptor> entry : mGroupDescriptors.entrySet()) {
                LeAudioGroupDescriptor descriptor = entry.getValue();
@@ -1297,6 +1324,9 @@ public class LeAudioService extends ProfileService {
     * @return true on success, otherwise false
     */
    public boolean setActiveDevice(BluetoothDevice device) {
        Log.i(TAG, "setActiveDevice: device=" + device + ", current out="
                + mActiveAudioOutDevice + ", current in=" + mActiveAudioInDevice);
        /* Clear active group */
        if (device == null) {
            Log.e(TAG, "device should not be null!");
            return removeActiveDevice(false);
@@ -1306,6 +1336,14 @@ public class LeAudioService extends ProfileService {
                    + "connected");
            return false;
        }

        if (Utils.isDualModeAudioEnabled()) {
            if (!mAdapterService.isAllSupportedClassicAudioProfilesActive(device)) {
                Log.e(TAG, "setActiveDevice(" + device + "): failed because the device is not "
                                + "active for all supported classic audio profiles");
                return false;
            }
        }
        setActiveGroupWithDevice(device, false);
        return true;
    }
@@ -3420,6 +3458,7 @@ public class LeAudioService extends ProfileService {
    @Override
    public void dump(StringBuilder sb) {
        super.dump(sb);
        ProfileService.println(sb, "isDualModeAudioEnabled: " + Utils.isDualModeAudioEnabled());
        ProfileService.println(sb, "Active Groups information: ");
        ProfileService.println(sb, "  currentlyActiveGroupId: " + getActiveGroupId());
        ProfileService.println(sb, "  mActiveAudioOutDevice: " + mActiveAudioOutDevice);
@@ -3434,11 +3473,12 @@ public class LeAudioService extends ProfileService {
                                                : mGroupDescriptors.entrySet()) {
                LeAudioGroupDescriptor groupDescriptor = groupEntry.getValue();
                Integer groupId = groupEntry.getKey();
                BluetoothDevice leadDevice = getConnectedGroupLeadDevice(groupId);
                ProfileService.println(sb, "Group: " + groupId);
                ProfileService.println(sb, "  isActive: " + groupDescriptor.mIsActive);
                ProfileService.println(sb, "  isConnected: " + groupDescriptor.mIsConnected);
                ProfileService.println(sb, "  mDirection: " + groupDescriptor.mDirection);
                ProfileService.println(sb, "  group lead: " + getConnectedGroupLeadDevice(groupId));
                ProfileService.println(sb, "  group lead: " + leadDevice);
                ProfileService.println(sb, "  first device: " + getFirstDeviceFromGroup(groupId));
                ProfileService.println(sb, "  lost lead device: "
                        + groupDescriptor.mLostLeadDeviceWhileStreaming);
Loading