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

Commit 1097fafc authored by Rahul Sabnis's avatar Rahul Sabnis
Browse files

Add APIs to set and get the preferred audio mode

Tag: #feature
Bug: 257881495
Test: atest DatabaseManagerTest
Change-Id: I603d639ae300adbf6c55ea5573452f8f179dbe2b
parent a60a5c5c
Loading
Loading
Loading
Loading
+67 −0
Original line number Diff line number Diff line
@@ -4062,6 +4062,73 @@ public class AdapterService extends Service {
            enforceBluetoothPrivilegedPermission(service);
            Utils.setForegroundUserId(userId);
        }

        @Override
        public void setPreferredAudioProfiles(BluetoothDevice device, Bundle modeToProfileBundle,
                AttributionSource source, SynchronousResultReceiver receiver) {
            try {
                receiver.send(setPreferredAudioProfiles(device, modeToProfileBundle, source));
            } catch (RuntimeException e) {
                receiver.propagateException(e);
            }
        }

        private int setPreferredAudioProfiles(BluetoothDevice device, Bundle modeToProfileBundle,
                AttributionSource source) {
            AdapterService service = getService();
            if (service == null) {
                return BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED;
            }
            if (!callerIsSystemOrActiveOrManagedUser(service, TAG, "setPreferredAudioProfiles")) {
                return BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ALLOWED;
            }
            if (device == null) {
                throw new IllegalArgumentException("device cannot be null");
            }
            if (!BluetoothAdapter.checkBluetoothAddress(device.getAddress())) {
                throw new IllegalArgumentException("device cannot have an invalid address");
            }
            if (!Utils.checkConnectPermissionForDataDelivery(service, source, TAG)) {
                return BluetoothStatusCodes.ERROR_MISSING_BLUETOOTH_CONNECT_PERMISSION;
            }

            enforceBluetoothPrivilegedPermission(service);
            return service.mDatabaseManager.setPreferredAudioProfiles(device, modeToProfileBundle);
        }

        @Override
        public void getPreferredAudioProfiles(BluetoothDevice device,
                AttributionSource source, SynchronousResultReceiver receiver) {
            try {
                receiver.send(getPreferredAudioProfiles(device, source));
            } catch (RuntimeException e) {
                receiver.propagateException(e);
            }
        }

        private Bundle getPreferredAudioProfiles(BluetoothDevice device,
                AttributionSource source) {
            AdapterService service = getService();
            if (service == null) {
                return Bundle.EMPTY;
            }
            if (!callerIsSystemOrActiveOrManagedUser(service, TAG, "getPreferredAudioProfiles")) {
                throw new IllegalStateException("Caller is not the system or part of the "
                        + "active/managed user");
            }
            if (device == null) {
                throw new IllegalArgumentException("device cannot be null");
            }
            if (!BluetoothAdapter.checkBluetoothAddress(device.getAddress())) {
                throw new IllegalArgumentException("device cannot have an invalid address");
            }
            if (!Utils.checkConnectPermissionForDataDelivery(service, source, TAG)) {
                return Bundle.EMPTY;
            }

            enforceBluetoothPrivilegedPermission(service);
            return service.mDatabaseManager.getPreferredAudioProfiles(device);
        }
    }

    // ----API Methods--------
+87 −0
Original line number Diff line number Diff line
@@ -24,12 +24,14 @@ import android.bluetooth.BluetoothAudioPolicy;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothProtoEnums;
import android.bluetooth.BluetoothStatusCodes;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
@@ -771,6 +773,91 @@ public class DatabaseManager {
        }
    }

    /**
     * Sets the preferred profile for the supplied audio modes. See
     * {@link BluetoothDevice#setPreferredAudioProfiles(Bundle)} for more details.
     *
     * @param device is the remote device for which we are setting the preferred audio profiles
     * @param modeToProfileBundle contains the preferred profile
     * @return
     */
    public int setPreferredAudioProfiles(BluetoothDevice device, Bundle modeToProfileBundle) {
        synchronized (mMetadataCache) {
            if (device == null) {
                Log.e(TAG, "setPreferredAudioProfiles: device is null");
                throw new IllegalArgumentException("setPreferredAudioProfiles: device is null");
            }

            String address = device.getAddress();

            if (!mMetadataCache.containsKey(address)) {
                return BluetoothStatusCodes.ERROR_DEVICE_NOT_BONDED;
            }

            // Updates preferred audio profiles for the device
            Metadata metadata = mMetadataCache.get(address);
            int outputProfile = modeToProfileBundle.getInt(BluetoothDevice.AUDIO_MODE_OUTPUT_ONLY);
            int duplexProfile = modeToProfileBundle.getInt(BluetoothDevice.AUDIO_MODE_DUPLEX);
            if (outputProfile != 0) {
                Log.i(TAG, "setPreferredAudioProfiles: Updating output only audio profile for "
                        + "device: " + device + " to "
                        + BluetoothProfile.getProfileName(outputProfile));
                metadata.preferred_output_only_profile = outputProfile;
            }
            if (duplexProfile != 0) {
                Log.i(TAG, "setPreferredAudioProfiles: Updating duplex audio profile for device: "
                        + device + " to " + BluetoothProfile.getProfileName(duplexProfile));
                metadata.preferred_duplex_profile = duplexProfile;
            }

            updateDatabase(metadata);
        }
        return BluetoothStatusCodes.SUCCESS;
    }

    /**
     * Sets the preferred profile for the supplied audio modes. See
     * {@link BluetoothDevice#getPreferredAudioProfiles()} for more details.
     *
     * @param device is the device for which we want to get the preferred audio profiles
     * @return a Bundle containing the preferred audio profiles
     */
    public Bundle getPreferredAudioProfiles(BluetoothDevice device) {
        synchronized (mMetadataCache) {
            if (device == null) {
                Log.e(TAG, "getPreferredAudioProfiles: device is null");
                throw new IllegalArgumentException("getPreferredAudioProfiles: device is null");
            }

            String address = device.getAddress();

            if (!mMetadataCache.containsKey(address)) {
                return Bundle.EMPTY;
            }

            // Gets the preferred audio profiles for each audio mode
            Metadata metadata = mMetadataCache.get(address);
            int outputOnlyProfile = metadata.preferred_output_only_profile;
            int duplexProfile = metadata.preferred_duplex_profile;

            // Checks if the default values are present (aka no explicit preference)
            if (outputOnlyProfile == 0 && duplexProfile == 0) {
                return Bundle.EMPTY;
            }

            Bundle modeToProfileBundle = new Bundle();
            if (outputOnlyProfile != 0) {
                modeToProfileBundle.putInt(BluetoothDevice.AUDIO_MODE_OUTPUT_ONLY,
                        outputOnlyProfile);
            }
            if (duplexProfile != 0) {
                modeToProfileBundle.putInt(BluetoothDevice.AUDIO_MODE_DUPLEX, duplexProfile);
            }

            return modeToProfileBundle;
        }
    }

    /**
     * Get the {@link Looper} for the handler thread. This is used in testing and helper
     * objects
+18 −0
Original line number Diff line number Diff line
@@ -54,6 +54,22 @@ class Metadata {
    @Embedded
    public AudioPolicyEntity audioPolicyMetadata;

    /**
     * The preferred profile to be used for {@link BluetoothDevice#AUDIO_MODE_OUTPUT_ONLY}. This can
     * be either {@link BluetoothProfile#A2DP} or {@link BluetoothProfile#LE_AUDIO}. This value is
     * only used if the remote device supports both A2DP and LE Audio and both transports are
     * connected and active.
     */
    public int preferred_output_only_profile;

    /**
     * The preferred profile to be used for {@link BluetoothDevice#AUDIO_MODE_DUPLEX}. This can
     * be either {@link BluetoothProfile#HEADSET} or {@link BluetoothProfile#LE_AUDIO}. This value
     * is only used if the remote device supports both HFP and LE Audio and both transports are
     * connected and active.
     */
    public int preferred_duplex_profile;

    Metadata(String address) {
        this.address = address;
        migrated = false;
@@ -64,6 +80,8 @@ class Metadata {
        last_active_time = MetadataDatabase.sCurrentConnectionNumber++;
        is_active_a2dp_device = true;
        audioPolicyMetadata = new AudioPolicyEntity();
        preferred_output_only_profile = 0;
        preferred_duplex_profile = 0;
    }

    String getAddress() {
+22 −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 = 115)
@Database(entities = {Metadata.class}, version = 116)
public abstract class MetadataDatabase extends RoomDatabase {
    /**
     * The metadata database file name
@@ -526,4 +526,25 @@ public abstract class MetadataDatabase extends RoomDatabase {
            }
        }
    };

    @VisibleForTesting
    static final Migration MIGRATION_115_116 = new Migration(115, 116) {
        @Override
        public void migrate(SupportSQLiteDatabase database) {
            try {
                database.execSQL("ALTER TABLE metadata ADD COLUMN `preferred_output_only_profile` "
                        + "INTEGER NOT NULL DEFAULT 0");
                database.execSQL("ALTER TABLE metadata ADD COLUMN `preferred_duplex_profile` "
                        + "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("preferred_output_only_profile") == -1
                        || cursor.getColumnIndex("preferred_duplex_profile") == -1) {
                    throw ex;
                }
            }
        }
    };
}
+31 −4
Original line number Diff line number Diff line
@@ -1158,7 +1158,7 @@ public final class DatabaseManagerTest {
    @Test
    public void testDatabaseMigration_111_112() throws IOException {
        String testString = "TEST STRING";
        // Create a database with version 109
        // Create a database with version 111
        SupportSQLiteDatabase db = testHelper.createDatabase(DB_NAME, 111);
        // insert a device to the database
        ContentValues device = new ContentValues();
@@ -1204,7 +1204,7 @@ public final class DatabaseManagerTest {

    @Test
    public void testDatabaseMigration_113_114() throws IOException {
        // Create a database with version 112
        // Create a database with version 113
        SupportSQLiteDatabase db = testHelper.createDatabase(DB_NAME, 113);
        // insert a device to the database
        ContentValues device = new ContentValues();
@@ -1226,7 +1226,7 @@ public final class DatabaseManagerTest {

    @Test
    public void testDatabaseMigration_114_115() throws IOException {
        // Create a database with version 112
        // Create a database with version 114
        SupportSQLiteDatabase db = testHelper.createDatabase(DB_NAME, 114);
        // insert a device to the database
        ContentValues device = new ContentValues();
@@ -1234,11 +1234,13 @@ public final class DatabaseManagerTest {
        device.put("migrated", false);
        assertThat(db.insert("metadata", SQLiteDatabase.CONFLICT_IGNORE, device),
                CoreMatchers.not(-1));
        // Migrate database from 112 to 113

        // Migrate database from 114 to 115
        db.close();
        db = testHelper.runMigrationsAndValidate(DB_NAME, 115, true,
                MetadataDatabase.MIGRATION_114_115);
        Cursor cursor = db.query("SELECT * FROM metadata");

        assertHasColumn(cursor, "call_establish_audio_policy", true);
        assertHasColumn(cursor, "connecting_time_audio_policy", true);
        assertHasColumn(cursor, "in_band_ringtone_audio_policy", true);
@@ -1250,6 +1252,31 @@ public final class DatabaseManagerTest {
        }
    }

    @Test
    public void testDatabaseMigration_115_116() throws IOException {
        // Create a database with version 115
        SupportSQLiteDatabase db = testHelper.createDatabase(DB_NAME, 115);
        // insert a device to the database
        ContentValues device = new ContentValues();
        device.put("address", TEST_BT_ADDR);
        device.put("migrated", false);
        assertThat(db.insert("metadata", SQLiteDatabase.CONFLICT_IGNORE, device),
                CoreMatchers.not(-1));

        // Migrate database from 115 to 116
        db.close();
        db = testHelper.runMigrationsAndValidate(DB_NAME, 116, true,
                MetadataDatabase.MIGRATION_115_116);
        Cursor cursor = db.query("SELECT * FROM metadata");
        assertHasColumn(cursor, "preferred_output_only_profile", true);
        assertHasColumn(cursor, "preferred_duplex_profile", true);
        while (cursor.moveToNext()) {
            // Check the new columns was added with default value
            assertColumnIntData(cursor, "preferred_output_only_profile", 0);
            assertColumnIntData(cursor, "preferred_duplex_profile", 0);
        }
    }

    /**
     * Helper function to check whether the database has the expected column
     */
Loading