Loading android/app/jni/com_android_bluetooth_gatt.cpp +9 −0 Original line number Diff line number Diff line Loading @@ -25,6 +25,7 @@ #include <shared_mutex> #include "com_android_bluetooth.h" #include "com_android_bluetooth_flags.h" #include "common/init_flags.h" #include "hardware/bt_gatt.h" #include "hardware/bt_gatt_types.h" Loading Loading @@ -1279,6 +1280,10 @@ static void initializeNative(JNIEnv* env, jobject object) { return; } if (com::android::bluetooth::flags::scan_manager_refactor()) { btIf->start_rust_module(); } sGattIf->advertiser->RegisterCallbacks( JniAdvertisingCallbacks::GetInstance()); sGattIf->distance_measurement_manager->RegisterDistanceMeasurementCallbacks( Loading @@ -1292,6 +1297,10 @@ static void cleanupNative(JNIEnv* env, jobject /* object */) { if (!btIf) return; if (com::android::bluetooth::flags::scan_manager_refactor()) { btIf->stop_rust_module(); } if (sGattIf != NULL) { sGattIf->cleanup(); sGattIf = NULL; Loading android/app/src/com/android/bluetooth/btservice/ActiveDeviceManager.java +22 −21 Original line number Diff line number Diff line Loading @@ -19,7 +19,6 @@ package com.android.bluetooth.btservice; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SuppressLint; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; Loading Loading @@ -47,6 +46,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Objects; import java.util.Set; Loading Loading @@ -117,7 +117,7 @@ public class ActiveDeviceManager implements AdapterService.BluetoothStateCallbac private HandlerThread mHandlerThread = null; private Handler mHandler = null; private final AudioManager mAudioManager; private final AudioManagerAudioDeviceCallback mAudioManagerAudioDeviceCallback; @VisibleForTesting final AudioManagerAudioDeviceCallback mAudioManagerAudioDeviceCallback; private final Object mLock = new Object(); Loading Loading @@ -783,9 +783,9 @@ public class ActiveDeviceManager implements AdapterService.BluetoothStateCallbac } /** Notifications of audio device connection and disconnection events. */ @SuppressLint("AndroidFrameworkRequiresPermission") private class AudioManagerAudioDeviceCallback extends AudioDeviceCallback { private boolean isWiredAudioHeadset(AudioDeviceInfo deviceInfo) { @VisibleForTesting class AudioManagerAudioDeviceCallback extends AudioDeviceCallback { private static boolean isWiredAudioHeadset(AudioDeviceInfo deviceInfo) { switch (deviceInfo.getType()) { case AudioDeviceInfo.TYPE_WIRED_HEADSET: case AudioDeviceInfo.TYPE_WIRED_HEADPHONES: Loading @@ -800,26 +800,27 @@ public class ActiveDeviceManager implements AdapterService.BluetoothStateCallbac @Override public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) { Log.d(TAG, "onAudioDevicesAdded"); boolean hasAddedWiredDevice = false; for (AudioDeviceInfo deviceInfo : addedDevices) { Log.d( TAG, "Audio device added: " + deviceInfo.getProductName() + " type: " + deviceInfo.getType()); if (isWiredAudioHeadset(deviceInfo)) { hasAddedWiredDevice = true; break; } if (!Arrays.stream(addedDevices) .anyMatch(AudioManagerAudioDeviceCallback::isWiredAudioHeadset)) { return; } if (hasAddedWiredDevice) { wiredAudioDeviceConnected(); } } @Override public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) {} public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) { Log.d(TAG, "onAudioDevicesRemoved"); if (!Flags.fallbackWhenWiredAudioDisconnected()) { return; } if (!Arrays.stream(removedDevices) .anyMatch(AudioManagerAudioDeviceCallback::isWiredAudioHeadset)) { return; } synchronized (mLock) { setFallbackDeviceActiveLocked(); } } } ActiveDeviceManager(AdapterService service, ServiceFactory factory) { Loading android/app/src/com/android/bluetooth/opp/BluetoothOppTransferHistory.java +3 −0 Original line number Diff line number Diff line Loading @@ -270,6 +270,9 @@ public class BluetoothOppTransferHistory extends Activity /** Returns true if the device has finished transfers, including error and success. */ private boolean isTransferComplete() { if (mTransferCursor == null) { return false; } try { if (mTransferCursor.moveToFirst()) { while (!mTransferCursor.isAfterLast()) { Loading android/app/tests/unit/src/com/android/bluetooth/btservice/ActiveDeviceManagerTest.java +44 −0 Original line number Diff line number Diff line Loading @@ -40,8 +40,11 @@ import android.bluetooth.BluetoothLeBroadcastMetadata; import android.bluetooth.BluetoothProfile; import android.bluetooth.BluetoothSinkAudioPolicy; import android.content.Context; import android.media.AudioDeviceInfo; import android.media.AudioDevicePort; import android.media.AudioManager; import android.os.test.TestLooper; import android.platform.test.annotations.EnableFlags; import android.platform.test.flag.junit.SetFlagsRule; import android.util.ArrayMap; import android.util.SparseIntArray; Loading Loading @@ -1217,6 +1220,33 @@ public class ActiveDeviceManagerTest { verify(mHearingAidService, timeout(TIMEOUT_MS)).removeActiveDevice(false); } /** A wired audio device is disconnected. Check if falls back to connected A2DP. */ @Test @EnableFlags(Flags.FLAG_FALLBACK_WHEN_WIRED_AUDIO_DISCONNECTED) public void wiredAudioDeviceDisconnected_setFallbackDevice() throws Exception { AudioDeviceInfo[] testDevices = createAudioDeviceInfoTestDevices(); // Connect A2DP headphones a2dpConnected(mA2dpDevice, false); verify(mA2dpService, timeout(TIMEOUT_MS).times(1)).setActiveDevice(mA2dpDevice); verify(mLeAudioService, timeout(TIMEOUT_MS).times(1)).removeActiveDevice(true); // Connect wired audio device mActiveDeviceManager.mAudioManagerAudioDeviceCallback.onAudioDevicesAdded(testDevices); // Check wiredAudioDeviceConnected invoked properly verify(mA2dpService, timeout(TIMEOUT_MS)).removeActiveDevice(false); verify(mHeadsetService, timeout(TIMEOUT_MS)).setActiveDevice(isNull()); verify(mHearingAidService, timeout(TIMEOUT_MS)).removeActiveDevice(false); verify(mLeAudioService, timeout(TIMEOUT_MS).times(2)).removeActiveDevice(true); // Disconnect wired audio device mActiveDeviceManager.mAudioManagerAudioDeviceCallback.onAudioDevicesRemoved(testDevices); // Verify fallback to A2DP device verify(mA2dpService, timeout(TIMEOUT_MS).times(2)).setActiveDevice(mA2dpDevice); } /** * Verifies if Le Audio Broadcast is streaming, connected a2dp device should not be set as * active. Loading Loading @@ -1458,6 +1488,20 @@ public class ActiveDeviceManagerTest { BluetoothProfile.STATE_DISCONNECTED); } private AudioDeviceInfo[] createAudioDeviceInfoTestDevices() { AudioDevicePort a2dpPort = mock(AudioDevicePort.class); doReturn(AudioDeviceInfo.TYPE_BLUETOOTH_A2DP).when(a2dpPort).type(); doReturn("a2dp").when(a2dpPort).name(); AudioDevicePort usbPort = mock(AudioDevicePort.class); doReturn(AudioDeviceInfo.TYPE_USB_HEADSET).when(usbPort).type(); doReturn("usb").when(usbPort).name(); AudioDeviceInfo a2dpDevice = new AudioDeviceInfo(a2dpPort); AudioDeviceInfo usbDevice = new AudioDeviceInfo(usbPort); return new AudioDeviceInfo[] {a2dpDevice, usbDevice}; } private class TestDatabaseManager extends DatabaseManager { ArrayMap<BluetoothDevice, SparseIntArray> mProfileConnectionPolicy; Loading flags/bta_dm.aconfig +0 −14 Original line number Diff line number Diff line Loading @@ -8,20 +8,6 @@ flag { bug: "311196228" } flag { name: "connect_hid_after_service_discovery" namespace: "bluetooth" description: "Don't initiate HID connection before pairing and service discovery" bug: "314707251" } flag { name: "bta_dm_disc_stuck_in_cancelling_fix" namespace: "bluetooth" description: "Fix being stuck in BTA_DM_SEARCH_CANCELLING" bug: "319890673" } flag { name: "bta_dm_defer_device_discovery_state_change_until_rnr_complete" namespace: "bluetooth" Loading Loading
android/app/jni/com_android_bluetooth_gatt.cpp +9 −0 Original line number Diff line number Diff line Loading @@ -25,6 +25,7 @@ #include <shared_mutex> #include "com_android_bluetooth.h" #include "com_android_bluetooth_flags.h" #include "common/init_flags.h" #include "hardware/bt_gatt.h" #include "hardware/bt_gatt_types.h" Loading Loading @@ -1279,6 +1280,10 @@ static void initializeNative(JNIEnv* env, jobject object) { return; } if (com::android::bluetooth::flags::scan_manager_refactor()) { btIf->start_rust_module(); } sGattIf->advertiser->RegisterCallbacks( JniAdvertisingCallbacks::GetInstance()); sGattIf->distance_measurement_manager->RegisterDistanceMeasurementCallbacks( Loading @@ -1292,6 +1297,10 @@ static void cleanupNative(JNIEnv* env, jobject /* object */) { if (!btIf) return; if (com::android::bluetooth::flags::scan_manager_refactor()) { btIf->stop_rust_module(); } if (sGattIf != NULL) { sGattIf->cleanup(); sGattIf = NULL; Loading
android/app/src/com/android/bluetooth/btservice/ActiveDeviceManager.java +22 −21 Original line number Diff line number Diff line Loading @@ -19,7 +19,6 @@ package com.android.bluetooth.btservice; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SuppressLint; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; Loading Loading @@ -47,6 +46,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Objects; import java.util.Set; Loading Loading @@ -117,7 +117,7 @@ public class ActiveDeviceManager implements AdapterService.BluetoothStateCallbac private HandlerThread mHandlerThread = null; private Handler mHandler = null; private final AudioManager mAudioManager; private final AudioManagerAudioDeviceCallback mAudioManagerAudioDeviceCallback; @VisibleForTesting final AudioManagerAudioDeviceCallback mAudioManagerAudioDeviceCallback; private final Object mLock = new Object(); Loading Loading @@ -783,9 +783,9 @@ public class ActiveDeviceManager implements AdapterService.BluetoothStateCallbac } /** Notifications of audio device connection and disconnection events. */ @SuppressLint("AndroidFrameworkRequiresPermission") private class AudioManagerAudioDeviceCallback extends AudioDeviceCallback { private boolean isWiredAudioHeadset(AudioDeviceInfo deviceInfo) { @VisibleForTesting class AudioManagerAudioDeviceCallback extends AudioDeviceCallback { private static boolean isWiredAudioHeadset(AudioDeviceInfo deviceInfo) { switch (deviceInfo.getType()) { case AudioDeviceInfo.TYPE_WIRED_HEADSET: case AudioDeviceInfo.TYPE_WIRED_HEADPHONES: Loading @@ -800,26 +800,27 @@ public class ActiveDeviceManager implements AdapterService.BluetoothStateCallbac @Override public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) { Log.d(TAG, "onAudioDevicesAdded"); boolean hasAddedWiredDevice = false; for (AudioDeviceInfo deviceInfo : addedDevices) { Log.d( TAG, "Audio device added: " + deviceInfo.getProductName() + " type: " + deviceInfo.getType()); if (isWiredAudioHeadset(deviceInfo)) { hasAddedWiredDevice = true; break; } if (!Arrays.stream(addedDevices) .anyMatch(AudioManagerAudioDeviceCallback::isWiredAudioHeadset)) { return; } if (hasAddedWiredDevice) { wiredAudioDeviceConnected(); } } @Override public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) {} public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) { Log.d(TAG, "onAudioDevicesRemoved"); if (!Flags.fallbackWhenWiredAudioDisconnected()) { return; } if (!Arrays.stream(removedDevices) .anyMatch(AudioManagerAudioDeviceCallback::isWiredAudioHeadset)) { return; } synchronized (mLock) { setFallbackDeviceActiveLocked(); } } } ActiveDeviceManager(AdapterService service, ServiceFactory factory) { Loading
android/app/src/com/android/bluetooth/opp/BluetoothOppTransferHistory.java +3 −0 Original line number Diff line number Diff line Loading @@ -270,6 +270,9 @@ public class BluetoothOppTransferHistory extends Activity /** Returns true if the device has finished transfers, including error and success. */ private boolean isTransferComplete() { if (mTransferCursor == null) { return false; } try { if (mTransferCursor.moveToFirst()) { while (!mTransferCursor.isAfterLast()) { Loading
android/app/tests/unit/src/com/android/bluetooth/btservice/ActiveDeviceManagerTest.java +44 −0 Original line number Diff line number Diff line Loading @@ -40,8 +40,11 @@ import android.bluetooth.BluetoothLeBroadcastMetadata; import android.bluetooth.BluetoothProfile; import android.bluetooth.BluetoothSinkAudioPolicy; import android.content.Context; import android.media.AudioDeviceInfo; import android.media.AudioDevicePort; import android.media.AudioManager; import android.os.test.TestLooper; import android.platform.test.annotations.EnableFlags; import android.platform.test.flag.junit.SetFlagsRule; import android.util.ArrayMap; import android.util.SparseIntArray; Loading Loading @@ -1217,6 +1220,33 @@ public class ActiveDeviceManagerTest { verify(mHearingAidService, timeout(TIMEOUT_MS)).removeActiveDevice(false); } /** A wired audio device is disconnected. Check if falls back to connected A2DP. */ @Test @EnableFlags(Flags.FLAG_FALLBACK_WHEN_WIRED_AUDIO_DISCONNECTED) public void wiredAudioDeviceDisconnected_setFallbackDevice() throws Exception { AudioDeviceInfo[] testDevices = createAudioDeviceInfoTestDevices(); // Connect A2DP headphones a2dpConnected(mA2dpDevice, false); verify(mA2dpService, timeout(TIMEOUT_MS).times(1)).setActiveDevice(mA2dpDevice); verify(mLeAudioService, timeout(TIMEOUT_MS).times(1)).removeActiveDevice(true); // Connect wired audio device mActiveDeviceManager.mAudioManagerAudioDeviceCallback.onAudioDevicesAdded(testDevices); // Check wiredAudioDeviceConnected invoked properly verify(mA2dpService, timeout(TIMEOUT_MS)).removeActiveDevice(false); verify(mHeadsetService, timeout(TIMEOUT_MS)).setActiveDevice(isNull()); verify(mHearingAidService, timeout(TIMEOUT_MS)).removeActiveDevice(false); verify(mLeAudioService, timeout(TIMEOUT_MS).times(2)).removeActiveDevice(true); // Disconnect wired audio device mActiveDeviceManager.mAudioManagerAudioDeviceCallback.onAudioDevicesRemoved(testDevices); // Verify fallback to A2DP device verify(mA2dpService, timeout(TIMEOUT_MS).times(2)).setActiveDevice(mA2dpDevice); } /** * Verifies if Le Audio Broadcast is streaming, connected a2dp device should not be set as * active. Loading Loading @@ -1458,6 +1488,20 @@ public class ActiveDeviceManagerTest { BluetoothProfile.STATE_DISCONNECTED); } private AudioDeviceInfo[] createAudioDeviceInfoTestDevices() { AudioDevicePort a2dpPort = mock(AudioDevicePort.class); doReturn(AudioDeviceInfo.TYPE_BLUETOOTH_A2DP).when(a2dpPort).type(); doReturn("a2dp").when(a2dpPort).name(); AudioDevicePort usbPort = mock(AudioDevicePort.class); doReturn(AudioDeviceInfo.TYPE_USB_HEADSET).when(usbPort).type(); doReturn("usb").when(usbPort).name(); AudioDeviceInfo a2dpDevice = new AudioDeviceInfo(a2dpPort); AudioDeviceInfo usbDevice = new AudioDeviceInfo(usbPort); return new AudioDeviceInfo[] {a2dpDevice, usbDevice}; } private class TestDatabaseManager extends DatabaseManager { ArrayMap<BluetoothDevice, SparseIntArray> mProfileConnectionPolicy; Loading
flags/bta_dm.aconfig +0 −14 Original line number Diff line number Diff line Loading @@ -8,20 +8,6 @@ flag { bug: "311196228" } flag { name: "connect_hid_after_service_discovery" namespace: "bluetooth" description: "Don't initiate HID connection before pairing and service discovery" bug: "314707251" } flag { name: "bta_dm_disc_stuck_in_cancelling_fix" namespace: "bluetooth" description: "Fix being stuck in BTA_DM_SEARCH_CANCELLING" bug: "319890673" } flag { name: "bta_dm_defer_device_discovery_state_change_until_rnr_complete" namespace: "bluetooth" Loading