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

Commit ff0cc81b authored by Rahul Sabnis's avatar Rahul Sabnis
Browse files

Implement bluetooth auto connect logic without the need for

PRIORITY_AUTO_CONNECT and update tests

Bug: 146036586
Test: atest PhonePolicyTest, atest DatabaseManagerTest
Change-Id: Idb25185e11dd077285921e817a2adda56f843546
parent befdf404
Loading
Loading
Loading
Loading
+32 −124
Original line number Diff line number Diff line
@@ -179,7 +179,7 @@ class PhonePolicy {
                    Intent intent = (Intent) msg.obj;
                    BluetoothDevice activeDevice =
                            intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                    processProfileActiveDeviceChanged(activeDevice, msg.arg1);
                    processActiveDeviceChanged(activeDevice, msg.arg1);
                }
                break;

@@ -291,47 +291,25 @@ class PhonePolicy {
                connectOtherProfile(device);
            }
            if (nextState == BluetoothProfile.STATE_DISCONNECTED) {
                handleAllProfilesDisconnected(device);
                if (prevState == BluetoothProfile.STATE_CONNECTING) {
                    HeadsetService hsService = mFactory.getHeadsetService();
                    boolean hsDisconnected = hsService == null
                            || hsService.getConnectionState(device)
                            == BluetoothProfile.STATE_DISCONNECTED;
                    A2dpService a2dpService = mFactory.getA2dpService();
                    boolean a2dpDisconnected = a2dpService == null
                            || a2dpService.getConnectionState(device)
                            == BluetoothProfile.STATE_DISCONNECTED;
                    debugLog("processProfileStateChanged, device=" + device + ", a2dpDisconnected="
                            + a2dpDisconnected + ", hsDisconnected=" + hsDisconnected);
                    if (hsDisconnected && a2dpDisconnected) {
                        removeAutoConnectFromA2dpSink(device);
                        removeAutoConnectFromHeadset(device);
                    }
                if (profileId == BluetoothProfile.A2DP) {
                    mAdapterService.getDatabase().setDisconnection(device);
                }
                handleAllProfilesDisconnected(device);
            }
        }
    }

    private void processProfileActiveDeviceChanged(BluetoothDevice activeDevice, int profileId) {
        debugLog("processProfileActiveDeviceChanged, activeDevice=" + activeDevice + ", profile="
                + profileId);
        switch (profileId) {
            // Tracking active device changed intent only for A2DP so that we always connect to a
            // single device after toggling Bluetooth
            case BluetoothProfile.A2DP:
                // Ignore null active device since we don't know if the change is triggered by
                // normal device disconnection during Bluetooth shutdown or user action
                if (activeDevice == null) {
                    warnLog("processProfileActiveDeviceChanged: ignore null A2DP active device");
                    return;
                }
                for (BluetoothDevice device : mAdapterService.getBondedDevices()) {
                    removeAutoConnectFromA2dpSink(device);
                    removeAutoConnectFromHeadset(device);
                }
                setAutoConnectForA2dpSink(activeDevice);
                setAutoConnectForHeadset(activeDevice);
                break;
    /**
     * Updates the last connection date in the connection order database for the newly active device
     * if connected to a2dp profile
     *
     * @param device is the device we just made the active device
     */
    private void processActiveDeviceChanged(BluetoothDevice device, int profileId) {
        debugLog("processActiveDeviceChanged, device=" + device + ", profile=" + profileId);

        if (device != null && profileId == BluetoothProfile.A2DP) {
            mAdapterService.getDatabase().setConnection(device);
        }
    }

@@ -387,15 +365,16 @@ class PhonePolicy {

        if (!mAdapterService.isQuietModeEnabled()) {
            debugLog("autoConnect: Initiate auto connection on BT on...");
            final BluetoothDevice[] bondedDevices = mAdapterService.getBondedDevices();
            if (bondedDevices == null) {
                errorLog("autoConnect: bondedDevices are null");
            final BluetoothDevice mostRecentlyActiveA2dpDevice =
                    mAdapterService.getDatabase().getMostRecentlyConnectedA2dpDevice();
            if (mostRecentlyActiveA2dpDevice == null) {
                errorLog("autoConnect: most recently active a2dp device is null");
                return;
            }
            for (BluetoothDevice device : bondedDevices) {
                autoConnectHeadset(device);
                autoConnectA2dp(device);
            }
            debugLog("autoConnect: Device " + mostRecentlyActiveA2dpDevice
                    + " attempting auto connection");
            autoConnectHeadset(mostRecentlyActiveA2dpDevice);
            autoConnectA2dp(mostRecentlyActiveA2dpDevice);
        } else {
            debugLog("autoConnect() - BT is in quiet mode. Not initiating auto connections");
        }
@@ -407,13 +386,13 @@ class PhonePolicy {
            warnLog("autoConnectA2dp: service is null, failed to connect to " + device);
            return;
        }
        int a2dpPriority = a2dpService.getConnectionPolicy(device);
        if (a2dpPriority == BluetoothProfile.PRIORITY_AUTO_CONNECT) {
        int a2dpConnectionPolicy = a2dpService.getConnectionPolicy(device);
        if (a2dpConnectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
            debugLog("autoConnectA2dp: connecting A2DP with " + device);
            a2dpService.connect(device);
        } else {
            debugLog("autoConnectA2dp: skipped auto-connect A2DP with device " + device
                    + " priority " + a2dpPriority);
                    + " connectionPolicy " + a2dpConnectionPolicy);
        }
    }

@@ -423,13 +402,13 @@ class PhonePolicy {
            warnLog("autoConnectHeadset: service is null, failed to connect to " + device);
            return;
        }
        int headsetPriority = hsService.getConnectionPolicy(device);
        if (headsetPriority == BluetoothProfile.PRIORITY_AUTO_CONNECT) {
        int headsetConnectionPolicy = hsService.getConnectionPolicy(device);
        if (headsetConnectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
            debugLog("autoConnectHeadset: Connecting HFP with " + device);
            hsService.connect(device);
        } else {
            debugLog("autoConnectHeadset: skipped auto-connect HFP with device " + device
                    + " priority " + headsetPriority);
                    + " connectionPolicy " + headsetConnectionPolicy);
        }
    }

@@ -469,7 +448,7 @@ class PhonePolicy {

        if (hsService != null) {
            if (!mHeadsetRetrySet.contains(device) && (hsService.getConnectionPolicy(device)
                    >= BluetoothProfile.CONNECTION_POLICY_ALLOWED)
                    == BluetoothProfile.CONNECTION_POLICY_ALLOWED)
                    && (hsService.getConnectionState(device)
                    == BluetoothProfile.STATE_DISCONNECTED)) {
                debugLog("Retrying connection to Headset with device " + device);
@@ -479,7 +458,7 @@ class PhonePolicy {
        }
        if (a2dpService != null) {
            if (!mA2dpRetrySet.contains(device) && (a2dpService.getConnectionPolicy(device)
                    >= BluetoothProfile.CONNECTION_POLICY_ALLOWED)
                    == BluetoothProfile.CONNECTION_POLICY_ALLOWED)
                    && (a2dpService.getConnectionState(device)
                    == BluetoothProfile.STATE_DISCONNECTED)) {
                debugLog("Retrying connection to A2DP with device " + device);
@@ -492,7 +471,7 @@ class PhonePolicy {
            // TODO: the panConnDevList.isEmpty() check below should be removed once
            // Multi-PAN is supported.
            if (panConnDevList.isEmpty() && (panService.getConnectionPolicy(device)
                    >= BluetoothProfile.CONNECTION_POLICY_ALLOWED)
                    == BluetoothProfile.CONNECTION_POLICY_ALLOWED)
                    && (panService.getConnectionState(device)
                    == BluetoothProfile.STATE_DISCONNECTED)) {
                debugLog("Retrying connection to PAN with device " + device);
@@ -501,77 +480,6 @@ class PhonePolicy {
        }
    }

    /**
     * Set a device's headset profile priority to PRIORITY_AUTO_CONNECT if device support that
     * profile
     *
     * @param device device whose headset profile priority should be PRIORITY_AUTO_CONNECT
     */
    private void setAutoConnectForHeadset(BluetoothDevice device) {
        HeadsetService hsService = mFactory.getHeadsetService();
        if (hsService == null) {
            warnLog("setAutoConnectForHeadset: HEADSET service is null");
            return;
        }
        if (hsService.getConnectionPolicy(device) >= BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
            debugLog("setAutoConnectForHeadset: device " + device + " PRIORITY_AUTO_CONNECT");
            hsService.setConnectionPolicy(device, BluetoothProfile.PRIORITY_AUTO_CONNECT);
        }
    }

    /**
     * Set a device's A2DP profile priority to PRIORITY_AUTO_CONNECT if device support that profile
     *
     * @param device device whose headset profile priority should be PRIORITY_AUTO_CONNECT
     */
    private void setAutoConnectForA2dpSink(BluetoothDevice device) {
        A2dpService a2dpService = mFactory.getA2dpService();
        if (a2dpService == null) {
            warnLog("setAutoConnectForA2dpSink: A2DP service is null");
            return;
        }
        if (a2dpService.getConnectionPolicy(device) >= BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
            debugLog("setAutoConnectForA2dpSink: device " + device + " PRIORITY_AUTO_CONNECT");
            a2dpService.setConnectionPolicy(device, BluetoothProfile.PRIORITY_AUTO_CONNECT);
        }
    }

    /**
     * Remove PRIORITY_AUTO_CONNECT from all headsets and set headset that used to have
     * PRIORITY_AUTO_CONNECT to PRIORITY_ON
     *
     * @param device device whose PRIORITY_AUTO_CONNECT priority should be removed
     */
    private void removeAutoConnectFromHeadset(BluetoothDevice device) {
        HeadsetService hsService = mFactory.getHeadsetService();
        if (hsService == null) {
            warnLog("removeAutoConnectFromHeadset: HEADSET service is null");
            return;
        }
        if (hsService.getConnectionPolicy(device) >= BluetoothProfile.PRIORITY_AUTO_CONNECT) {
            debugLog("removeAutoConnectFromHeadset: device " + device + " PRIORITY_ON");
            hsService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
        }
    }

    /**
     * Remove PRIORITY_AUTO_CONNECT from all A2DP sinks and set A2DP sink that used to have
     * PRIORITY_AUTO_CONNECT to PRIORITY_ON
     *
     * @param device device whose PRIORITY_AUTO_CONNECT priority should be removed
     */
    private void removeAutoConnectFromA2dpSink(BluetoothDevice device) {
        A2dpService a2dpService = mFactory.getA2dpService();
        if (a2dpService == null) {
            warnLog("removeAutoConnectFromA2dpSink: A2DP service is null");
            return;
        }
        if (a2dpService.getConnectionPolicy(device) >= BluetoothProfile.PRIORITY_AUTO_CONNECT) {
            debugLog("removeAutoConnectFromA2dpSink: device " + device + " PRIORITY_ON");
            a2dpService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
        }
    }

    private static void debugLog(String msg) {
        if (DBG) {
            Log.i(TAG, msg);
+121 −8
Original line number Diff line number Diff line
@@ -133,6 +133,7 @@ public class DatabaseManager {
                                    .createDatabaseWithoutMigration(mAdapterService);
                            list = mDatabase.load();
                        }
                        compactLastConnectionTime(list);
                        cacheMetadata(list);
                    }
                    break;
@@ -388,13 +389,6 @@ public class DatabaseManager {
            Metadata data = mMetadataCache.get(address);
            int connectionPolicy = data.getProfileConnectionPolicy(profile);

            // If result is PRIORITY_AUTO_CONNECT, return as CONNECTION_POLICY_ALLOWED
            if (connectionPolicy > BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
                setProfileConnectionPolicy(device, profile,
                        BluetoothProfile.CONNECTION_POLICY_ALLOWED);
                connectionPolicy = BluetoothProfile.CONNECTION_POLICY_ALLOWED;
            }

            Log.v(TAG, "getProfileConnectionPolicy: " + address + ", profile=" + profile
                    + ", connectionPolicy = " + connectionPolicy);
            return connectionPolicy;
@@ -543,6 +537,124 @@ public class DatabaseManager {
        }
    }

    /**
     * Updates the time this device was last connected
     *
     * @param device is the remote bluetooth device for which we are setting the connection time
     */
    public void setConnection(BluetoothDevice device) {
        synchronized (mMetadataCache) {
            if (device == null) {
                Log.e(TAG, "setConnection: device is null");
                return;
            }

            resetActiveA2dpDevice();
            String address = device.getAddress();

            if (!mMetadataCache.containsKey(address)) {
                Log.d(TAG, "setConnection: Creating new metadata entry for device: " + device);
                createMetadata(address);
                return;
            }
            // Updates last connected time to either current time if connected or -1 if disconnected
            Metadata metadata = mMetadataCache.get(address);
            metadata.last_active_time = MetadataDatabase.sCurrentConnectionNumber++;
            metadata.is_active_a2dp_device = true;

            Log.d(TAG, "Updating last connected time for device: " + device + " to "
                    + metadata.last_active_time);
            updateDatabase(metadata);
        }
    }

    /**
     * Sets is_active_device to false if currently true for device
     *
     * @param device is the remote bluetooth device with which we have disconnected a2dp
     */
    public void setDisconnection(BluetoothDevice device) {
        synchronized (mMetadataCache) {
            if (device == null) {
                Log.e(TAG, "setDisconnection: device is null");
                return;
            }

            String address = device.getAddress();

            if (!mMetadataCache.containsKey(address)) {
                return;
            }
            // Updates last connected time to either current time if connected or -1 if disconnected
            Metadata metadata = mMetadataCache.get(address);
            if (metadata.is_active_a2dp_device) {
                metadata.is_active_a2dp_device = false;
                Log.d(TAG, "setDisconnection: Updating is_active_device to false for device: "
                        + device);
                updateDatabase(metadata);
            }
        }
    }

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

    /**
     * Gets the last active a2dp device
     *
     * @return the most recently active a2dp device or null if the last a2dp device was null
     */
    public BluetoothDevice getMostRecentlyConnectedA2dpDevice() {
        synchronized (mMetadataCache) {
            for (Map.Entry<String, Metadata> entry : mMetadataCache.entrySet()) {
                Metadata metadata = entry.getValue();
                if (metadata.is_active_a2dp_device) {
                    try {
                        return BluetoothAdapter.getDefaultAdapter().getRemoteDevice(
                                metadata.getAddress());
                    } catch (IllegalArgumentException ex) {
                        Log.d(TAG, "getMostRecentlyConnectedA2dpDevice: Invalid address for "
                                + "device " + metadata.getAddress());
                    }
                }
            }
        }
        return null;
    }

    /**
     *
     * @param metadataList is the list of metadata
     */
    private void compactLastConnectionTime(List<Metadata> metadataList) {
        Log.d(TAG, "compactLastConnectionTime: Compacting metadata after load");
        MetadataDatabase.sCurrentConnectionNumber = 0;
        // Have to go in reverse order as list is ordered by descending last_active_time
        for (int index = metadataList.size() - 1; index >= 0; index--) {
            Metadata metadata = metadataList.get(index);
            if (metadata.last_active_time != MetadataDatabase.sCurrentConnectionNumber) {
                Log.d(TAG, "compactLastConnectionTime: Setting last_active_item for device: "
                        + metadata.getAddress() + " from " + metadata.last_active_time + " to "
                        + MetadataDatabase.sCurrentConnectionNumber);
                metadata.last_active_time = MetadataDatabase.sCurrentConnectionNumber;
                updateDatabase(metadata);
                MetadataDatabase.sCurrentConnectionNumber++;
            }
        }
    }

    /**
     * Get the {@link Looper} for the handler thread. This is used in testing and helper
     * objects
@@ -867,7 +979,8 @@ public class DatabaseManager {
        mHandler.sendMessage(message);
    }

    private void deleteDatabase(Metadata data) {
    @VisibleForTesting
    void deleteDatabase(Metadata data) {
        String address = data.getAddress();
        if (address == null) {
            Log.e(TAG, "deleteDatabase: address is null");
+5 −0
Original line number Diff line number Diff line
@@ -48,6 +48,9 @@ class Metadata {
    public @OptionalCodecsSupportStatus int a2dpSupportsOptionalCodecs;
    public @OptionalCodecsPreferenceStatus int a2dpOptionalCodecsEnabled;

    public long last_active_time;
    public boolean is_active_a2dp_device;

    Metadata(String address) {
        this.address = address;
        migrated = false;
@@ -55,6 +58,8 @@ class Metadata {
        publicMetadata = new CustomizedMetadataEntity();
        a2dpSupportsOptionalCodecs = BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN;
        a2dpOptionalCodecsEnabled = BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN;
        last_active_time = MetadataDatabase.sCurrentConnectionNumber++;
        is_active_a2dp_device = true;
    }

    String getAddress() {
+1 −1
Original line number Diff line number Diff line
@@ -28,7 +28,7 @@ interface MetadataDao {
    /**
     * Load all items in the database
     */
    @Query("SELECT * FROM metadata")
    @Query("SELECT * FROM metadata ORDER BY last_active_time DESC")
    List<Metadata> load();

    /**
+30 −6
Original line number Diff line number Diff line
@@ -33,13 +33,15 @@ import java.util.List;
/**
 * MetadataDatabase is a Room database stores Bluetooth persistence data
 */
@Database(entities = {Metadata.class}, version = 103)
@Database(entities = {Metadata.class}, version = 104)
public abstract class MetadataDatabase extends RoomDatabase {
    /**
     * The database file name
     * The metadata database file name
     */
    public static final String DATABASE_NAME = "bluetooth_db";

    static int sCurrentConnectionNumber = 0;

    protected abstract MetadataDao mMetadataDao();

    /**
@@ -54,6 +56,8 @@ public abstract class MetadataDatabase extends RoomDatabase {
                .addMigrations(MIGRATION_100_101)
                .addMigrations(MIGRATION_101_102)
                .addMigrations(MIGRATION_102_103)
                .addMigrations(MIGRATION_103_104)
                .allowMainThreadQueries()
                .build();
    }

@@ -68,11 +72,12 @@ public abstract class MetadataDatabase extends RoomDatabase {
        return Room.databaseBuilder(context,
                MetadataDatabase.class, DATABASE_NAME)
                .fallbackToDestructiveMigration()
                .allowMainThreadQueries()
                .build();
    }

    /**
     * Insert a {@link Metadata} to database
     * Insert a {@link Metadata} to metadata table
     *
     * @param metadata the data wish to put into storage
     */
@@ -81,7 +86,7 @@ public abstract class MetadataDatabase extends RoomDatabase {
    }

    /**
     * Load all data from database as a {@link List} of {@link Metadata}
     * Load all data from metadata table as a {@link List} of {@link Metadata}
     *
     * @return a {@link List} of {@link Metadata}
     */
@@ -90,7 +95,7 @@ public abstract class MetadataDatabase extends RoomDatabase {
    }

    /**
     * Delete one of the {@link Metadata} contains in database
     * Delete one of the {@link Metadata} contained in the metadata table
     *
     * @param address the address of Metadata to delete
     */
@@ -99,7 +104,7 @@ public abstract class MetadataDatabase extends RoomDatabase {
    }

    /**
     * Clear database.
     * Clear metadata table.
     */
    public void deleteAll() {
        mMetadataDao().deleteAll();
@@ -279,4 +284,23 @@ public abstract class MetadataDatabase extends RoomDatabase {
            }
        }
    };

    @VisibleForTesting
    static final Migration MIGRATION_103_104 = new Migration(103, 104) {
        @Override
        public void migrate(SupportSQLiteDatabase database) {
            try {
                database.execSQL("ALTER TABLE metadata ADD COLUMN `last_active_time` "
                        + "INTEGER NOT NULL DEFAULT -1");
                database.execSQL("ALTER TABLE metadata ADD COLUMN `is_active_a2dp_device` "
                        + "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("last_active_time") == -1) {
                    throw ex;
                }
            }
        }
    };
}
Loading