BLUETOOTH_CONNECT permission is required starting Android 12
This commit is contained in:
parent
9c881ca00e
commit
34e000e3bb
9 changed files with 73 additions and 25 deletions
|
@ -16,6 +16,7 @@ Group changes to describe their impact on the project, as follows:
|
||||||
## [4.6.2] - Unreleased
|
## [4.6.2] - Unreleased
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
- Request BLUETOOTH_CONNECT permission on Android 12+ devices, if not we won't be notified when a BT device is being connected/disconnected while app is alive.
|
||||||
- LDAP settings if SDK is built with OpenLDAP (requires 5.1.1 or higher linphone-sdk), will add contacts if any
|
- LDAP settings if SDK is built with OpenLDAP (requires 5.1.1 or higher linphone-sdk), will add contacts if any
|
||||||
- SIP addresses & phone numbers can be selected in history & contact details view
|
- SIP addresses & phone numbers can be selected in history & contact details view
|
||||||
- Text can be selected in file viewer & config viewer
|
- Text can be selected in file viewer & config viewer
|
||||||
|
|
|
@ -32,6 +32,11 @@
|
||||||
<!-- Needed for overlay -->
|
<!-- Needed for overlay -->
|
||||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
|
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
|
||||||
|
|
||||||
|
<!-- Needed starting Android 12 for broadcast receiver
|
||||||
|
to be triggered when BT device is connected / disconnected
|
||||||
|
(https://developer.android.com/guide/topics/connectivity/bluetooth/permissions) -->
|
||||||
|
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name=".LinphoneApplication"
|
android:name=".LinphoneApplication"
|
||||||
android:allowBackup="false"
|
android:allowBackup="false"
|
||||||
|
|
|
@ -30,6 +30,7 @@ import org.linphone.R
|
||||||
import org.linphone.activities.GenericFragment
|
import org.linphone.activities.GenericFragment
|
||||||
import org.linphone.activities.main.conference.viewmodels.ConferenceWaitingRoomViewModel
|
import org.linphone.activities.main.conference.viewmodels.ConferenceWaitingRoomViewModel
|
||||||
import org.linphone.activities.navigateToDialer
|
import org.linphone.activities.navigateToDialer
|
||||||
|
import org.linphone.compatibility.Compatibility
|
||||||
import org.linphone.core.tools.Log
|
import org.linphone.core.tools.Log
|
||||||
import org.linphone.databinding.ConferenceWaitingRoomFragmentBinding
|
import org.linphone.databinding.ConferenceWaitingRoomFragmentBinding
|
||||||
import org.linphone.mediastream.Version
|
import org.linphone.mediastream.Version
|
||||||
|
@ -129,14 +130,22 @@ class ConferenceWaitingRoomFragment : GenericFragment<ConferenceWaitingRoomFragm
|
||||||
@TargetApi(Version.API23_MARSHMALLOW_60)
|
@TargetApi(Version.API23_MARSHMALLOW_60)
|
||||||
private fun checkPermissions() {
|
private fun checkPermissions() {
|
||||||
val permissionsRequiredList = arrayListOf<String>()
|
val permissionsRequiredList = arrayListOf<String>()
|
||||||
|
|
||||||
if (!PermissionHelper.get().hasRecordAudioPermission()) {
|
if (!PermissionHelper.get().hasRecordAudioPermission()) {
|
||||||
Log.i("[Conference Waiting Room] Asking for RECORD_AUDIO permission")
|
Log.i("[Conference Waiting Room] Asking for RECORD_AUDIO permission")
|
||||||
permissionsRequiredList.add(Manifest.permission.RECORD_AUDIO)
|
permissionsRequiredList.add(Manifest.permission.RECORD_AUDIO)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!PermissionHelper.get().hasCameraPermission()) {
|
if (!PermissionHelper.get().hasCameraPermission()) {
|
||||||
Log.i("[Conference Waiting Room] Asking for CAMERA permission")
|
Log.i("[Conference Waiting Room] Asking for CAMERA permission")
|
||||||
permissionsRequiredList.add(Manifest.permission.CAMERA)
|
permissionsRequiredList.add(Manifest.permission.CAMERA)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Version.sdkAboveOrEqual(Version.API31_ANDROID_12) && !PermissionHelper.get().hasBluetoothConnectPermission()) {
|
||||||
|
Log.i("[Conference Waiting Room] Asking for BLUETOOTH_CONNECT permission")
|
||||||
|
permissionsRequiredList.add(Compatibility.BLUETOOTH_CONNECT)
|
||||||
|
}
|
||||||
|
|
||||||
if (permissionsRequiredList.isNotEmpty()) {
|
if (permissionsRequiredList.isNotEmpty()) {
|
||||||
val permissionsRequired = arrayOfNulls<String>(permissionsRequiredList.size)
|
val permissionsRequired = arrayOfNulls<String>(permissionsRequiredList.size)
|
||||||
permissionsRequiredList.toArray(permissionsRequired)
|
permissionsRequiredList.toArray(permissionsRequired)
|
||||||
|
@ -161,6 +170,9 @@ class ConferenceWaitingRoomFragment : GenericFragment<ConferenceWaitingRoomFragm
|
||||||
coreContext.core.reloadVideoDevices()
|
coreContext.core.reloadVideoDevices()
|
||||||
viewModel.enableVideo()
|
viewModel.enableVideo()
|
||||||
}
|
}
|
||||||
|
Compatibility.BLUETOOTH_CONNECT -> if (grantResults[i] == PackageManager.PERMISSION_GRANTED) {
|
||||||
|
Log.i("[Conference Waiting Room] BLUETOOTH_CONNECT permission has been granted")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -119,7 +119,7 @@ class ConferenceWaitingRoomViewModel : ViewModel() {
|
||||||
} else {
|
} else {
|
||||||
setEarpieceAudioRoute()
|
setEarpieceAudioRoute()
|
||||||
}
|
}
|
||||||
updateAudioRouteState()
|
onAudioDevicesListUpdated()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCleared() {
|
override fun onCleared() {
|
||||||
|
|
|
@ -195,16 +195,24 @@ class CallActivity : ProximitySensorActivity() {
|
||||||
@TargetApi(Version.API23_MARSHMALLOW_60)
|
@TargetApi(Version.API23_MARSHMALLOW_60)
|
||||||
private fun checkPermissions() {
|
private fun checkPermissions() {
|
||||||
val permissionsRequiredList = arrayListOf<String>()
|
val permissionsRequiredList = arrayListOf<String>()
|
||||||
|
|
||||||
if (!PermissionHelper.get().hasRecordAudioPermission()) {
|
if (!PermissionHelper.get().hasRecordAudioPermission()) {
|
||||||
Log.i("[Call] Asking for RECORD_AUDIO permission")
|
Log.i("[Call] Asking for RECORD_AUDIO permission")
|
||||||
permissionsRequiredList.add(Manifest.permission.RECORD_AUDIO)
|
permissionsRequiredList.add(Manifest.permission.RECORD_AUDIO)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (callsViewModel.currentCallData.value?.call?.currentParams?.isVideoEnabled == true &&
|
if (callsViewModel.currentCallData.value?.call?.currentParams?.isVideoEnabled == true &&
|
||||||
!PermissionHelper.get().hasCameraPermission()
|
!PermissionHelper.get().hasCameraPermission()
|
||||||
) {
|
) {
|
||||||
Log.i("[Call] Asking for CAMERA permission")
|
Log.i("[Call] Asking for CAMERA permission")
|
||||||
permissionsRequiredList.add(Manifest.permission.CAMERA)
|
permissionsRequiredList.add(Manifest.permission.CAMERA)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Version.sdkAboveOrEqual(Version.API31_ANDROID_12) && !PermissionHelper.get().hasBluetoothConnectPermission()) {
|
||||||
|
Log.i("[Call] Asking for BLUETOOTH_CONNECT permission")
|
||||||
|
permissionsRequiredList.add(Compatibility.BLUETOOTH_CONNECT)
|
||||||
|
}
|
||||||
|
|
||||||
if (permissionsRequiredList.isNotEmpty()) {
|
if (permissionsRequiredList.isNotEmpty()) {
|
||||||
val permissionsRequired = arrayOfNulls<String>(permissionsRequiredList.size)
|
val permissionsRequired = arrayOfNulls<String>(permissionsRequiredList.size)
|
||||||
permissionsRequiredList.toArray(permissionsRequired)
|
permissionsRequiredList.toArray(permissionsRequired)
|
||||||
|
@ -228,6 +236,9 @@ class CallActivity : ProximitySensorActivity() {
|
||||||
Log.i("[Call] CAMERA permission has been granted")
|
Log.i("[Call] CAMERA permission has been granted")
|
||||||
coreContext.core.reloadVideoDevices()
|
coreContext.core.reloadVideoDevices()
|
||||||
}
|
}
|
||||||
|
Compatibility.BLUETOOTH_CONNECT -> if (grantResults[i] == PackageManager.PERMISSION_GRANTED) {
|
||||||
|
Log.i("[Call] BLUETOOTH_CONNECT permission has been granted")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
*/
|
*/
|
||||||
package org.linphone.compatibility
|
package org.linphone.compatibility
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
import android.annotation.TargetApi
|
import android.annotation.TargetApi
|
||||||
import android.app.*
|
import android.app.*
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
@ -173,5 +174,9 @@ class Api31Compatibility {
|
||||||
Log.i("[Call] PiP auto enter enabled params set to $enable")
|
Log.i("[Call] PiP auto enter enabled params set to $enable")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun hasBluetoothConnectPermission(context: Context): Boolean {
|
||||||
|
return Compatibility.hasPermission(context, Manifest.permission.BLUETOOTH_CONNECT)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,6 +48,8 @@ import org.linphone.telecom.NativeCallWrapper
|
||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION")
|
||||||
class Compatibility {
|
class Compatibility {
|
||||||
companion object {
|
companion object {
|
||||||
|
const val BLUETOOTH_CONNECT = "android.permission.BLUETOOTH_CONNECT"
|
||||||
|
|
||||||
fun hasPermission(context: Context, permission: String): Boolean {
|
fun hasPermission(context: Context, permission: String): Boolean {
|
||||||
return when (Version.sdkAboveOrEqual(Version.API23_MARSHMALLOW_60)) {
|
return when (Version.sdkAboveOrEqual(Version.API23_MARSHMALLOW_60)) {
|
||||||
true -> Api23Compatibility.hasPermission(context, permission)
|
true -> Api23Compatibility.hasPermission(context, permission)
|
||||||
|
@ -73,6 +75,13 @@ class Compatibility {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun hasBluetoothConnectPermission(context: Context): Boolean {
|
||||||
|
if (Version.sdkAboveOrEqual(Version.API31_ANDROID_12)) {
|
||||||
|
return Api31Compatibility.hasBluetoothConnectPermission(context)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// See https://developer.android.com/about/versions/11/privacy/permissions#phone-numbers
|
// See https://developer.android.com/about/versions/11/privacy/permissions#phone-numbers
|
||||||
fun hasTelecomManagerPermissions(context: Context): Boolean {
|
fun hasTelecomManagerPermissions(context: Context): Boolean {
|
||||||
return if (Version.sdkAboveOrEqual(Version.API30_ANDROID_11)) {
|
return if (Version.sdkAboveOrEqual(Version.API30_ANDROID_11)) {
|
||||||
|
|
|
@ -73,4 +73,8 @@ class PermissionHelper private constructor(private val context: Context) {
|
||||||
fun hasRecordAudioPermission(): Boolean {
|
fun hasRecordAudioPermission(): Boolean {
|
||||||
return hasPermission(Manifest.permission.RECORD_AUDIO)
|
return hasPermission(Manifest.permission.RECORD_AUDIO)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun hasBluetoothConnectPermission(): Boolean {
|
||||||
|
return Compatibility.hasBluetoothConnectPermission(context)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -224,37 +224,38 @@
|
||||||
app:layout_constraintDimensionRatio="W,1:1"
|
app:layout_constraintDimensionRatio="W,1:1"
|
||||||
app:layout_constraintEnd_toStartOf="@id/speaker" />
|
app:layout_constraintEnd_toStartOf="@id/speaker" />
|
||||||
|
|
||||||
<ImageView
|
<RelativeLayout
|
||||||
android:id="@+id/speaker"
|
android:id="@+id/speaker"
|
||||||
android:contentDescription="@{viewModel.isSpeakerSelected ? @string/content_description_disable_speaker : @string/content_description_enable_speaker}"
|
|
||||||
android:onClick="@{() -> viewModel.toggleSpeaker()}"
|
|
||||||
android:selected="@{viewModel.isSpeakerSelected}"
|
|
||||||
android:visibility="@{viewModel.audioRoutesEnabled ? View.GONE : View.VISIBLE}"
|
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_marginStart="5dp"
|
|
||||||
android:background="@drawable/button_background_reverse"
|
|
||||||
android:padding="5dp"
|
|
||||||
android:src="@drawable/icon_toggle_speaker"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintDimensionRatio="W,1:1"
|
app:layout_constraintDimensionRatio="W,1:1"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent" />
|
app:layout_constraintStart_toStartOf="parent">
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:onClick="@{() -> viewModel.toggleAudioRoutesMenu()}"
|
android:layout_width="wrap_content"
|
||||||
android:selected="@{viewModel.audioRoutesSelected}"
|
android:layout_height="match_parent"
|
||||||
android:visibility="@{viewModel.audioRoutesEnabled ? View.VISIBLE : View.GONE, default=gone}"
|
android:background="@drawable/button_background_reverse"
|
||||||
android:layout_height="match_parent"
|
android:contentDescription="@{viewModel.isSpeakerSelected ? @string/content_description_disable_speaker : @string/content_description_enable_speaker}"
|
||||||
android:layout_width="0dp"
|
android:onClick="@{() -> viewModel.toggleSpeaker()}"
|
||||||
android:background="@drawable/button_toggle_background_reverse"
|
android:padding="5dp"
|
||||||
android:src="@drawable/icon_audio_routes"
|
android:selected="@{viewModel.isSpeakerSelected}"
|
||||||
android:padding="5dp"
|
android:src="@drawable/icon_toggle_speaker"
|
||||||
android:contentDescription="@string/content_description_toggle_audio_menu"
|
android:visibility="@{viewModel.audioRoutesEnabled ? View.GONE : View.VISIBLE}" />
|
||||||
app:layout_constraintDimensionRatio="W,1:1"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
<ImageView
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
android:onClick="@{() -> viewModel.toggleAudioRoutesMenu()}"
|
||||||
app:layout_constraintEnd_toEndOf="parent"/>
|
android:selected="@{viewModel.audioRoutesSelected}"
|
||||||
|
android:visibility="@{viewModel.audioRoutesEnabled ? View.VISIBLE : View.GONE, default=gone}"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:background="@drawable/button_toggle_background_reverse"
|
||||||
|
android:src="@drawable/icon_audio_routes"
|
||||||
|
android:padding="5dp"
|
||||||
|
android:contentDescription="@string/content_description_toggle_audio_menu"/>
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:contentDescription="@{viewModel.isVideoEnabled ? @string/content_description_disable_video : @string/content_description_enable_video}"
|
android:contentDescription="@{viewModel.isVideoEnabled ? @string/content_description_disable_video : @string/content_description_enable_video}"
|
||||||
|
|
Loading…
Reference in a new issue