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

Commit 571460cf authored by Treehugger Robot's avatar Treehugger Robot Committed by Gerrit Code Review
Browse files

Merge "Implement bluetooth auto connect logic without the need for...

Merge "Implement bluetooth auto connect logic without the need for PRIORITY_AUTO_CONNECT and update tests"
parents c27859dd ff0cc81b
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