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

Commit f25c6a00 authored by William Escande's avatar William Escande
Browse files

if mostRecentlyActiveA2DPDevice is null, auto connect to mostRecentlyActiveHfpDevice device

Existing logic is to auto connect to most recently active A2DP device, which will have an issue when the device is HFP only.

Bug: 290553699
Test: atest PhonePolicyTest
Change-Id: I43862413e24209e401ba4712eca89fbc2399330a
parent d73a3168
Loading
Loading
Loading
Loading
+29 −9
Original line number Diff line number Diff line
@@ -77,6 +77,12 @@ class PhonePolicy implements AdapterService.BluetoothStateCallback {
    private static boolean sLeAudioEnabledByDefault = DeviceConfig.getBoolean(
            DeviceConfig.NAMESPACE_BLUETOOTH, CONFIG_LE_AUDIO_ENABLED_BY_DEFAULT, false);

    private static final String HFP_AUTO_CONNECT = "HFP_AUTO_CONNECT";

    @VisibleForTesting
    static boolean sIsHfpAutoConnectEnabled =
            DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_BLUETOOTH, HFP_AUTO_CONNECT, false);

    // Timeouts
    @VisibleForTesting static int sConnectOtherProfilesTimeoutMillis = 6000; // 6s

@@ -587,10 +593,11 @@ class PhonePolicy implements AdapterService.BluetoothStateCallback {
        mA2dpRetrySet.clear();
    }

    @VisibleForTesting
    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
    private void autoConnect() {
    void autoConnect() {
        if (mAdapterService.getState() != BluetoothAdapter.STATE_ON) {
            errorLog("autoConnect: BT is not ON. Exiting autoConnect");
            Log.e(TAG, "autoConnect: BT is not ON. Exiting autoConnect");
            return;
        }
        if (mAdapterService.isQuietModeEnabled()) {
@@ -598,16 +605,33 @@ class PhonePolicy implements AdapterService.BluetoothStateCallback {
            return;
        }

        Log.i(TAG, "autoConnect: Initiate auto connection on BT on...");
        final BluetoothDevice mostRecentlyActiveA2dpDevice =
                mDatabaseManager.getMostRecentlyConnectedA2dpDevice();
        if (mostRecentlyActiveA2dpDevice != null) {
            debugLog("autoConnect: Device " + mostRecentlyActiveA2dpDevice
            debugLog(
                    "autoConnect: Device "
                            + mostRecentlyActiveA2dpDevice
                            + " attempting auto connection");
            autoConnectHeadset(mostRecentlyActiveA2dpDevice);
            autoConnectA2dp(mostRecentlyActiveA2dpDevice);
            autoConnectHidHost(mostRecentlyActiveA2dpDevice);
            return;
        }

        if (!sIsHfpAutoConnectEnabled) {
            debugLog("HFP auto connect is not enabled");
            return;
        }

        // Try to autoConnect with Hfp only if there was no a2dp valid device
        final BluetoothDevice mostRecentlyConnectedHfpDevice =
                mDatabaseManager.getMostRecentlyActiveHfpDevice();
        if (mostRecentlyConnectedHfpDevice != null) {
            debugLog("autoConnect: Headset device: " + mostRecentlyConnectedHfpDevice);
            autoConnectHeadset(mostRecentlyConnectedHfpDevice);
            return;
        }
        Log.i(TAG, "autoConnect: No device to reconnect to");
    }

    private void autoConnectA2dp(BluetoothDevice device) {
@@ -838,8 +862,4 @@ class PhonePolicy implements AdapterService.BluetoothStateCallback {
    private static void warnLog(String msg) {
        Log.w(TAG, msg);
    }

    private static void errorLog(String msg) {
        Log.e(TAG, msg);
    }
}
+75 −9
Original line number Diff line number Diff line
@@ -586,7 +586,7 @@ public class DatabaseManager {
    }

    @GuardedBy("mMetadataCache")
    private void setConnection(BluetoothDevice device, boolean isActiveA2dp) {
    private void setConnection(BluetoothDevice device, boolean isActiveA2dp, boolean isActiveHfp) {
        if (device == null) {
            Log.e(TAG, "setConnection: device is null");
            return;
@@ -594,7 +594,7 @@ public class DatabaseManager {
        String address = device.getAddress();

        if (!mMetadataCache.containsKey(address)) {
            createMetadata(address, isActiveA2dp);
            createMetadata(address, isActiveA2dp, isActiveHfp);
            return;
        }
        // Updates last_active_time to the current counter value and increments the counter
@@ -608,6 +608,10 @@ public class DatabaseManager {
            metadata.is_active_a2dp_device = true;
        }

        if (isActiveHfp) {
            metadata.isActiveHfpDevice = true;
        }

        Log.d(
                TAG,
                "Updating last connected time for device: "
@@ -624,7 +628,7 @@ public class DatabaseManager {
     */
    public void setConnection(BluetoothDevice device) {
        synchronized (mMetadataCache) {
            setConnection(device, false);
            setConnection(device, false, false);
        }
    }

@@ -636,13 +640,17 @@ public class DatabaseManager {
     */
    public void setConnection(BluetoothDevice device, int profileId) {
        boolean isA2dpDevice = profileId == BluetoothProfile.A2DP;
        boolean isHfpDevice = profileId == BluetoothProfile.HEADSET;

        synchronized (mMetadataCache) {
            if (isA2dpDevice) {
                resetActiveA2dpDevice();
            }
            if (isHfpDevice) {
                resetActiveHfpDevice();
            }

            setConnection(device, isA2dpDevice);
            setConnection(device, isA2dpDevice, isHfpDevice);
        }
    }

@@ -668,8 +676,8 @@ public class DatabaseManager {
                        + "profileId: "
                        + BluetoothProfile.getProfileName(profileId));

        if (profileId != BluetoothProfile.A2DP) {
            // there is no change on metadata when profile is not A2DP
        if (profileId != BluetoothProfile.A2DP && profileId != BluetoothProfile.HEADSET) {
            // there is no change on metadata when profile is neither A2DP nor Headset
            return;
        }

@@ -689,6 +697,14 @@ public class DatabaseManager {
                                + device);
                updateDatabase(metadata);
            }
            if (profileId == BluetoothProfile.HEADSET && metadata.isActiveHfpDevice) {
                metadata.isActiveHfpDevice = false;
                Log.d(
                        TAG,
                        "setDisconnection: Updating isActiveHfpDevice to false for device: "
                                + device);
                updateDatabase(metadata);
            }
        }
    }

@@ -706,6 +722,20 @@ public class DatabaseManager {
        }
    }

    /** Remove hfpActiveDevice from the current active device in the connection order table */
    @GuardedBy("mMetadataCache")
    private void resetActiveHfpDevice() {
        Log.d(TAG, "resetActiveHfpDevice()");
        for (Map.Entry<String, Metadata> entry : mMetadataCache.entrySet()) {
            Metadata metadata = entry.getValue();
            if (metadata.isActiveHfpDevice) {
                Log.d(TAG, "resetActiveHfpDevice");
                metadata.isActiveHfpDevice = false;
                updateDatabase(metadata);
            }
        }
    }

    /**
     * Gets the most recently connected bluetooth devices in order with most recently connected
     * first and least recently connected last
@@ -785,6 +815,36 @@ public class DatabaseManager {
        return null;
    }

    /**
     * Gets the last active HFP device
     *
     * @return the most recently active HFP device or null if the last hfp device was null
     */
    public BluetoothDevice getMostRecentlyActiveHfpDevice() {
        Map.Entry<String, Metadata> entry;
        synchronized (mMetadataCache) {
            entry =
                    mMetadataCache.entrySet().stream()
                            .filter(x -> x.getValue().isActiveHfpDevice)
                            .findFirst()
                            .orElse(null);
        }
        if (entry != null) {
            try {
                return BluetoothAdapter.getDefaultAdapter()
                        .getRemoteDevice(entry.getValue().getAddress());
            } catch (IllegalArgumentException ex) {
                Log.d(
                        TAG,
                        "getMostRecentlyActiveHfpDevice: Invalid address for "
                                + "device "
                                + entry.getValue().getAnonymizedAddress());
            }
        }

        return null;
    }

    /**
     *
     * @param metadataList is the list of metadata
@@ -1001,9 +1061,7 @@ public class DatabaseManager {
        mHandler.sendMessage(message);
    }

    /**
     * Close and de-init the DatabaseManager
     */
    /** Close and de-init the DatabaseManager */
    public void cleanup() {
        removeUnusedMetadata();
        mAdapterService.unregisterReceiver(mReceiver);
@@ -1015,17 +1073,25 @@ public class DatabaseManager {
    }

    void createMetadata(String address, boolean isActiveA2dpDevice) {
        createMetadata(address, isActiveA2dpDevice, false);
    }

    void createMetadata(String address, boolean isActiveA2dpDevice, boolean isActiveHfpDevice) {
        Metadata.Builder dataBuilder = new Metadata.Builder(address);

        if (isActiveA2dpDevice) {
            dataBuilder.setActiveA2dp();
        }
        if (isActiveHfpDevice) {
            dataBuilder.setActiveHfp();
        }

        Metadata data = dataBuilder.build();
        Log.d(
                TAG,
                "createMetadata: "
                        + (" address=" + data.getAnonymizedAddress())
                        + (" isActiveHfpDevice=" + isActiveHfpDevice)
                        + (" isActiveA2dpDevice=" + isActiveA2dpDevice));
        mMetadataCache.put(address, data);
        updateDatabase(data);
+14 −3
Original line number Diff line number Diff line
@@ -58,6 +58,8 @@ public class Metadata {
    public long last_active_time;
    public boolean is_active_a2dp_device;

    public boolean isActiveHfpDevice;

    @Embedded
    public AudioPolicyEntity audioPolicyMetadata;

@@ -78,10 +80,10 @@ public class Metadata {
    public int preferred_duplex_profile;

    Metadata(String address) {
        this(address, false);
        this(address, false, false);
    }

    private Metadata(String address, boolean isActiveA2dp) {
    private Metadata(String address, boolean isActiveA2dp, boolean isActiveHfp) {
        this.address = address;
        migrated = false;
        profileConnectionPolicies = new ProfilePrioritiesEntity();
@@ -90,6 +92,7 @@ public class Metadata {
        a2dpOptionalCodecsEnabled = BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN;
        last_active_time = MetadataDatabase.sCurrentConnectionNumber++;
        is_active_a2dp_device = isActiveA2dp;
        isActiveHfpDevice = isActiveHfp;
        audioPolicyMetadata = new AudioPolicyEntity();
        preferred_output_only_profile = 0;
        preferred_duplex_profile = 0;
@@ -98,6 +101,7 @@ public class Metadata {
    static final class Builder {
        final String mAddress;
        boolean mIsActiveA2dpDevice = false;
        boolean mIsActiveHfpDevice = false;

        Builder(String address) {
            mAddress = address;
@@ -108,8 +112,13 @@ public class Metadata {
            return this;
        }

        Builder setActiveHfp() {
            mIsActiveHfpDevice = true;
            return this;
        }

        Metadata build() {
            return new Metadata(mAddress, mIsActiveA2dpDevice);
            return new Metadata(mAddress, mIsActiveA2dpDevice, mIsActiveHfpDevice);
        }
    }

@@ -461,6 +470,8 @@ public class Metadata {
                .append(a2dpSupportsOptionalCodecs)
                .append("|enabled=")
                .append(a2dpOptionalCodecsEnabled)
                .append("), isActiveHfpDevice (")
                .append(isActiveHfpDevice)
                .append("), custom metadata(")
                .append(publicMetadata)
                .append("), hfp client audio policy(")
+21 −1
Original line number Diff line number Diff line
@@ -33,7 +33,7 @@ import java.util.List;
/** MetadataDatabase is a Room database stores Bluetooth persistence data */
@Database(
        entities = {Metadata.class},
        version = 117)
        version = 118)
public abstract class MetadataDatabase extends RoomDatabase {
    /** The metadata database file name */
    public static final String DATABASE_NAME = "bluetooth_db";
@@ -67,6 +67,7 @@ public abstract class MetadataDatabase extends RoomDatabase {
                .addMigrations(MIGRATION_114_115)
                .addMigrations(MIGRATION_115_116)
                .addMigrations(MIGRATION_116_117)
                .addMigrations(MIGRATION_117_118)
                .allowMainThreadQueries()
                .build();
    }
@@ -610,4 +611,23 @@ public abstract class MetadataDatabase extends RoomDatabase {
                    }
                }
            };

    @VisibleForTesting
    static final Migration MIGRATION_117_118 =
            new Migration(117, 118) {
                @Override
                public void migrate(SupportSQLiteDatabase database) {
                    try {
                        database.execSQL(
                                "ALTER TABLE metadata ADD COLUMN `isActiveHfpDevice` "
                                        + "INTEGER NOT NULL DEFAULT 0");
                    } catch (SQLException ex) {
                        // Check if user has new schema, but is just missing the version update
                        Cursor cursor = database.query("SELECT * FROM metadata");
                        if (cursor == null || cursor.getColumnIndex("isActiveHfpDevice") == -1) {
                            throw ex;
                        }
                    }
                }
            };
}
+36 −2
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package com.android.bluetooth.btservice;

import static com.android.bluetooth.TestUtils.getTestDevice;
import static com.android.bluetooth.TestUtils.waitForLooperToFinishScheduledTask;
import static com.android.bluetooth.btservice.PhonePolicy.sIsHfpAutoConnectEnabled;

import static org.mockito.Mockito.*;

@@ -605,8 +606,41 @@ public class PhonePolicyTest {
                BluetoothProfile.STATE_DISCONNECTED);

        // Check that we get a call to A2DP connect again
        verify(mA2dpService, timeout(CONNECT_OTHER_PROFILES_TIMEOUT_WAIT_MILLIS).times(2)).connect(
                connectionOrder.get(0));
        verify(mA2dpService, timeout(CONNECT_OTHER_PROFILES_TIMEOUT_WAIT_MILLIS).times(2))
                .connect(connectionOrder.get(0));
    }

    /**
     * Test that when the adapter is turned ON then call auto connect on devices that only has HFP
     * enabled. NOTE that the assumption is that we have already done the pairing previously and
     * hence the priorities for the device is already set to AUTO_CONNECT over HFP (as part of post
     * pairing process).
     */
    @Test
    public void testAutoConnectHfpOnly() {
        sIsHfpAutoConnectEnabled = true;

        // Return desired values from the mocked object(s)
        when(mAdapterService.getState()).thenReturn(BluetoothAdapter.STATE_ON);
        when(mAdapterService.isQuietModeEnabled()).thenReturn(false);

        // Return a device that is HFP only
        BluetoothDevice bondedDevice = getTestDevice(mAdapter, 0);
        when(mDatabaseManager.getMostRecentlyConnectedA2dpDevice()).thenReturn(null);
        when(mDatabaseManager.getMostRecentlyConnectedDevices()).thenReturn(List.of(bondedDevice));
        when(mDatabaseManager.getMostRecentlyActiveHfpDevice()).thenReturn(bondedDevice);
        when(mAdapterService.getBondState(bondedDevice)).thenReturn(BluetoothDevice.BOND_BONDED);

        // Return CONNECTION_POLICY_ALLOWED over HFP
        when(mHeadsetService.getConnectionPolicy(bondedDevice))
                .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);

        mPhonePolicy.autoConnect();

        // Check that we got a request to connect over HFP
        verify(mHeadsetService).connect(eq(bondedDevice));

        sIsHfpAutoConnectEnabled = false;
    }

    /**
Loading