diff --git a/audioswitch/src/main/java/com/twilio/audioswitch/android/BroadcastReceiverCompat.kt b/audioswitch/src/main/java/com/twilio/audioswitch/android/BroadcastReceiverCompat.kt new file mode 100644 index 0000000..8ed1117 --- /dev/null +++ b/audioswitch/src/main/java/com/twilio/audioswitch/android/BroadcastReceiverCompat.kt @@ -0,0 +1,21 @@ +package com.twilio.audioswitch.android + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.IntentFilter +import android.os.Build + +internal fun registerReceiverCompat( + context: Context, + broadcastReceiver: BroadcastReceiver, + intentFilter: IntentFilter, + exported: Boolean, + broadcastPermission: String?, +) { + if (Build.VERSION.SDK_INT >= 34 && context.applicationInfo.targetSdkVersion >= 34) { + val flag = if (exported) Context.RECEIVER_EXPORTED else Context.RECEIVER_NOT_EXPORTED + context.registerReceiver(broadcastReceiver, intentFilter, broadcastPermission, null, flag) + } else { + context.registerReceiver(broadcastReceiver, intentFilter, broadcastPermission, null) + } +} diff --git a/audioswitch/src/main/java/com/twilio/audioswitch/bluetooth/BluetoothHeadsetManager.kt b/audioswitch/src/main/java/com/twilio/audioswitch/bluetooth/BluetoothHeadsetManager.kt index 1ceebb7..a8d2ee7 100644 --- a/audioswitch/src/main/java/com/twilio/audioswitch/bluetooth/BluetoothHeadsetManager.kt +++ b/audioswitch/src/main/java/com/twilio/audioswitch/bluetooth/BluetoothHeadsetManager.kt @@ -1,5 +1,6 @@ package com.twilio.audioswitch.bluetooth +import android.Manifest import android.annotation.SuppressLint import android.bluetooth.BluetoothAdapter import android.bluetooth.BluetoothClass @@ -20,6 +21,7 @@ import android.media.AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED import android.media.AudioManager.SCO_AUDIO_STATE_CONNECTED import android.media.AudioManager.SCO_AUDIO_STATE_CONNECTING import android.media.AudioManager.SCO_AUDIO_STATE_DISCONNECTED +import android.os.Build import android.os.Handler import android.os.Looper import androidx.annotation.VisibleForTesting @@ -30,6 +32,7 @@ import com.twilio.audioswitch.android.BluetoothIntentProcessor import com.twilio.audioswitch.android.BluetoothIntentProcessorImpl import com.twilio.audioswitch.android.Logger import com.twilio.audioswitch.android.SystemClockWrapper +import com.twilio.audioswitch.android.registerReceiverCompat import com.twilio.audioswitch.bluetooth.BluetoothHeadsetManager.HeadsetState.AudioActivated import com.twilio.audioswitch.bluetooth.BluetoothHeadsetManager.HeadsetState.AudioActivating import com.twilio.audioswitch.bluetooth.BluetoothHeadsetManager.HeadsetState.AudioActivationError @@ -204,17 +207,33 @@ internal constructor( BluetoothProfile.HEADSET, ) if (!hasRegisteredReceivers) { - context.registerReceiver( + val broadcastPermission = if (context.applicationInfo.targetSdkVersion <= Build.VERSION_CODES.R || + Build.VERSION.SDK_INT <= Build.VERSION_CODES.R + ) { + Manifest.permission.BLUETOOTH + } else { + Manifest.permission.BLUETOOTH_CONNECT + } + registerReceiverCompat( + context, this, IntentFilter(ACTION_CONNECTION_STATE_CHANGED), + true, + broadcastPermission, ) - context.registerReceiver( + registerReceiverCompat( + context, this, IntentFilter(ACTION_AUDIO_STATE_CHANGED), + true, + broadcastPermission, ) - context.registerReceiver( + registerReceiverCompat( + context, this, IntentFilter(ACTION_SCO_AUDIO_STATE_UPDATED), + true, + broadcastPermission, ) hasRegisteredReceivers = true } diff --git a/audioswitch/src/main/java/com/twilio/audioswitch/wired/WiredHeadsetReceiver.kt b/audioswitch/src/main/java/com/twilio/audioswitch/wired/WiredHeadsetReceiver.kt index 53ce6a0..60c0b39 100644 --- a/audioswitch/src/main/java/com/twilio/audioswitch/wired/WiredHeadsetReceiver.kt +++ b/audioswitch/src/main/java/com/twilio/audioswitch/wired/WiredHeadsetReceiver.kt @@ -5,6 +5,7 @@ import android.content.Context import android.content.Intent import android.content.IntentFilter import com.twilio.audioswitch.android.Logger +import com.twilio.audioswitch.android.registerReceiverCompat private const val TAG = "WiredHeadsetReceiver" internal const val STATE_UNPLUGGED = 0 @@ -37,7 +38,13 @@ internal class WiredHeadsetReceiver( fun start(deviceListener: WiredDeviceConnectionListener) { this.deviceListener = deviceListener - context.registerReceiver(this, IntentFilter(Intent.ACTION_HEADSET_PLUG)) + registerReceiverCompat( + context, + this, + IntentFilter(Intent.ACTION_HEADSET_PLUG), + true, + null, + ) } fun stop() { diff --git a/audioswitch/src/test/java/com/twilio/audioswitch/AudioSwitchJavaTest.java b/audioswitch/src/test/java/com/twilio/audioswitch/AudioSwitchJavaTest.java index 2a54ea9..55eb6ba 100644 --- a/audioswitch/src/test/java/com/twilio/audioswitch/AudioSwitchJavaTest.java +++ b/audioswitch/src/test/java/com/twilio/audioswitch/AudioSwitchJavaTest.java @@ -7,6 +7,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.when; +import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import com.twilio.audioswitch.AudioDevice.BluetoothHeadset; import com.twilio.audioswitch.AudioDevice.Earpiece; @@ -26,11 +27,13 @@ public class AudioSwitchJavaTest extends BaseTest { private AudioSwitch javaAudioSwitch; @Mock PackageManager packageManager; + @Mock ApplicationInfo applicationInfo; @Before public void setUp() { when(packageManager.hasSystemFeature(any())).thenReturn(true); when(getContext$audioswitch_debug().getPackageManager()).thenReturn(packageManager); + when(getContext$audioswitch_debug().getApplicationInfo()).thenReturn(applicationInfo); javaAudioSwitch = new AudioSwitch( getContext$audioswitch_debug(), diff --git a/audioswitch/src/test/java/com/twilio/audioswitch/AudioSwitchTest.kt b/audioswitch/src/test/java/com/twilio/audioswitch/AudioSwitchTest.kt index 16a9c3e..1b152c1 100644 --- a/audioswitch/src/test/java/com/twilio/audioswitch/AudioSwitchTest.kt +++ b/audioswitch/src/test/java/com/twilio/audioswitch/AudioSwitchTest.kt @@ -33,6 +33,7 @@ import org.junit.runner.RunWith import org.mockito.kotlin.any import org.mockito.kotlin.eq import org.mockito.kotlin.isA +import org.mockito.kotlin.isNull import org.mockito.kotlin.mock import org.mockito.kotlin.times import org.mockito.kotlin.verify @@ -49,6 +50,7 @@ class AudioSwitchTest : BaseTest() { @Before fun setUp() { whenever(context.packageManager).thenReturn(packageManager) + whenever(context.applicationInfo).thenReturn(applicationInfo) } @Test @@ -58,7 +60,7 @@ class AudioSwitchTest : BaseTest() { assertBluetoothHeadsetSetup() assertThat(wiredHeadsetReceiver.deviceListener, equalTo(audioSwitch.wiredDeviceConnectionListener)) - verify(context).registerReceiver(eq(wiredHeadsetReceiver), isA()) + verify(context).registerReceiver(eq(wiredHeadsetReceiver), isA(), isNull(), isNull()) } @Test diff --git a/audioswitch/src/test/java/com/twilio/audioswitch/BaseTest.kt b/audioswitch/src/test/java/com/twilio/audioswitch/BaseTest.kt index 34e88fe..fec7c8b 100644 --- a/audioswitch/src/test/java/com/twilio/audioswitch/BaseTest.kt +++ b/audioswitch/src/test/java/com/twilio/audioswitch/BaseTest.kt @@ -7,6 +7,7 @@ import android.bluetooth.BluetoothHeadset import android.bluetooth.BluetoothProfile import android.content.Context import android.content.Intent +import android.content.pm.ApplicationInfo import android.media.AudioManager.OnAudioFocusChangeListener import com.twilio.audioswitch.AudioDevice.Earpiece import com.twilio.audioswitch.AudioDevice.Speakerphone @@ -30,6 +31,7 @@ open class BaseTest { whenever(mock.bluetoothClass).thenReturn(bluetoothClass) } internal val context = mock() + internal val applicationInfo = mock() internal val bluetoothListener = mock() internal val logger = UnitTestLogger() internal val audioManager = setupAudioManagerMock() diff --git a/audioswitch/src/test/java/com/twilio/audioswitch/TestUtil.kt b/audioswitch/src/test/java/com/twilio/audioswitch/TestUtil.kt index 6591f26..1ccc9f9 100644 --- a/audioswitch/src/test/java/com/twilio/audioswitch/TestUtil.kt +++ b/audioswitch/src/test/java/com/twilio/audioswitch/TestUtil.kt @@ -9,6 +9,7 @@ import com.twilio.audioswitch.bluetooth.BluetoothScoJob import org.mockito.kotlin.any import org.mockito.kotlin.eq import org.mockito.kotlin.isA +import org.mockito.kotlin.isNull import org.mockito.kotlin.mock import org.mockito.kotlin.times import org.mockito.kotlin.verify @@ -43,7 +44,7 @@ internal fun BaseTest.assertBluetoothHeadsetSetup() { headsetManager, BluetoothProfile.HEADSET, ) - verify(context, times(3)).registerReceiver(eq(headsetManager), isA()) + verify(context, times(3)).registerReceiver(eq(headsetManager), isA(), isA(), isNull()) } internal fun setupPermissionsCheckStrategy() = diff --git a/audioswitch/src/test/java/com/twilio/audioswitch/bluetooth/BluetoothHeadsetManagerTest.kt b/audioswitch/src/test/java/com/twilio/audioswitch/bluetooth/BluetoothHeadsetManagerTest.kt index 987c2cb..6204989 100644 --- a/audioswitch/src/test/java/com/twilio/audioswitch/bluetooth/BluetoothHeadsetManagerTest.kt +++ b/audioswitch/src/test/java/com/twilio/audioswitch/bluetooth/BluetoothHeadsetManagerTest.kt @@ -39,6 +39,7 @@ class BluetoothHeadsetManagerTest : BaseTest() { @Before fun setUp() { initializeManagerWithMocks() + whenever(context.applicationInfo).thenReturn(applicationInfo) } @Test diff --git a/audioswitch/src/test/java/com/twilio/audioswitch/wired/WiredHeadsetReceiverTest.kt b/audioswitch/src/test/java/com/twilio/audioswitch/wired/WiredHeadsetReceiverTest.kt index a336bf2..4ba06ca 100644 --- a/audioswitch/src/test/java/com/twilio/audioswitch/wired/WiredHeadsetReceiverTest.kt +++ b/audioswitch/src/test/java/com/twilio/audioswitch/wired/WiredHeadsetReceiverTest.kt @@ -11,6 +11,7 @@ import org.junit.Assert.fail import org.junit.Test import org.mockito.kotlin.eq import org.mockito.kotlin.isA +import org.mockito.kotlin.isNull import org.mockito.kotlin.mock import org.mockito.kotlin.verify import org.mockito.kotlin.whenever @@ -92,7 +93,7 @@ class WiredHeadsetReceiverTest { fun `start should register the broadcast receiver`() { wiredHeadsetReceiver.start(wiredDeviceConnectionListener) - verify(context).registerReceiver(eq(wiredHeadsetReceiver), isA()) + verify(context).registerReceiver(eq(wiredHeadsetReceiver), isA(), isNull(), isNull()) } @Test