diff --git a/app/src/main/java/org/linphone/compatibility/Compatibility.kt b/app/src/main/java/org/linphone/compatibility/Compatibility.kt
index 18f66aab9..26bbcb44a 100644
--- a/app/src/main/java/org/linphone/compatibility/Compatibility.kt
+++ b/app/src/main/java/org/linphone/compatibility/Compatibility.kt
@@ -26,6 +26,7 @@ import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.net.Uri
import android.os.Vibrator
+import android.telephony.TelephonyManager
import android.view.View
import android.view.WindowManager
import androidx.core.app.NotificationManagerCompat
@@ -50,6 +51,14 @@ class Compatibility {
}
}
+ fun createPhoneListener(telephonyManager: TelephonyManager): PhoneStateInterface {
+ return if (Version.sdkStrictlyBelow(Version.API31_ANDROID_12)) {
+ PhoneStateListener(telephonyManager)
+ } else {
+ TelephonyListener(telephonyManager)
+ }
+ }
+
/* UI */
fun setShowWhenLocked(activity: Activity, enable: Boolean) {
diff --git a/app/src/main/java/org/linphone/compatibility/PhoneStateInterface.kt b/app/src/main/java/org/linphone/compatibility/PhoneStateInterface.kt
new file mode 100644
index 000000000..4d7723081
--- /dev/null
+++ b/app/src/main/java/org/linphone/compatibility/PhoneStateInterface.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2010-2021 Belledonne Communications SARL.
+ *
+ * This file is part of linphone-android
+ * (see https://www.linphone.org).
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package org.linphone.compatibility
+
+interface PhoneStateInterface {
+ fun destroy()
+
+ fun isInCall(): Boolean
+}
diff --git a/app/src/main/java/org/linphone/compatibility/PhoneStateListener.kt b/app/src/main/java/org/linphone/compatibility/PhoneStateListener.kt
new file mode 100644
index 000000000..59c15ffb6
--- /dev/null
+++ b/app/src/main/java/org/linphone/compatibility/PhoneStateListener.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2010-2021 Belledonne Communications SARL.
+ *
+ * This file is part of linphone-android
+ * (see https://www.linphone.org).
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package org.linphone.compatibility
+
+import android.telephony.PhoneStateListener
+import android.telephony.TelephonyManager
+import org.linphone.core.tools.Log
+
+class PhoneStateListener(private val telephonyManager: TelephonyManager) : PhoneStateInterface {
+ private var gsmCallActive = false
+ private val phoneStateListener = object : PhoneStateListener() {
+ override fun onCallStateChanged(state: Int, phoneNumber: String?) {
+ gsmCallActive = when (state) {
+ TelephonyManager.CALL_STATE_OFFHOOK -> {
+ Log.i("[Context] Phone state is off hook")
+ true
+ }
+ TelephonyManager.CALL_STATE_RINGING -> {
+ Log.i("[Context] Phone state is ringing")
+ true
+ }
+ TelephonyManager.CALL_STATE_IDLE -> {
+ Log.i("[Context] Phone state is idle")
+ false
+ }
+ else -> {
+ Log.w("[Context] Phone state is unexpected: $state")
+ false
+ }
+ }
+ }
+ }
+
+ init {
+ Log.i("[Phone State Listener] Registering phone state listener")
+ telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_CALL_STATE)
+ }
+
+ override fun destroy() {
+ Log.i("[Phone State Listener] Unregistering phone state listener")
+ telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_NONE)
+ }
+
+ override fun isInCall(): Boolean {
+ return gsmCallActive
+ }
+}
diff --git a/app/src/main/java/org/linphone/compatibility/TelephonyListener.kt b/app/src/main/java/org/linphone/compatibility/TelephonyListener.kt
new file mode 100644
index 000000000..b946cc94e
--- /dev/null
+++ b/app/src/main/java/org/linphone/compatibility/TelephonyListener.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2010-2021 Belledonne Communications SARL.
+ *
+ * This file is part of linphone-android
+ * (see https://www.linphone.org).
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package org.linphone.compatibility
+
+import android.annotation.TargetApi
+import android.os.Handler
+import android.os.Looper
+import android.telephony.TelephonyCallback
+import android.telephony.TelephonyManager
+import java.util.concurrent.Executor
+import org.linphone.core.tools.Log
+
+@TargetApi(31)
+class TelephonyListener(private val telephonyManager: TelephonyManager) : PhoneStateInterface {
+ private var gsmCallActive = false
+
+ private fun runOnUiThreadExecutor(): Executor {
+ val handler = Handler(Looper.getMainLooper())
+ return Executor() {
+ handler.post(it)
+ }
+ }
+
+ inner class TelephonyListener : TelephonyCallback(), TelephonyCallback.CallStateListener {
+ override fun onCallStateChanged(state: Int) {
+ gsmCallActive = when (state) {
+ TelephonyManager.CALL_STATE_OFFHOOK -> {
+ Log.i("[Context] Phone state is off hook")
+ true
+ }
+ TelephonyManager.CALL_STATE_RINGING -> {
+ Log.i("[Context] Phone state is ringing")
+ true
+ }
+ TelephonyManager.CALL_STATE_IDLE -> {
+ Log.i("[Context] Phone state is idle")
+ false
+ }
+ else -> {
+ Log.w("[Context] Phone state is unexpected: $state")
+ false
+ }
+ }
+ }
+ }
+ private val telephonyListener = TelephonyListener()
+
+ init {
+ Log.i("[Telephony Listener] Registering telephony callback")
+ telephonyManager.registerTelephonyCallback(runOnUiThreadExecutor(), telephonyListener)
+ }
+
+ override fun destroy() {
+ Log.i("[Telephony Listener] Unregistering telephony callback")
+ telephonyManager.unregisterTelephonyCallback(telephonyListener)
+ }
+
+ override fun isInCall(): Boolean {
+ return gsmCallActive
+ }
+}
diff --git a/app/src/main/java/org/linphone/core/CoreContext.kt b/app/src/main/java/org/linphone/core/CoreContext.kt
index 4aa06320b..79aabda87 100644
--- a/app/src/main/java/org/linphone/core/CoreContext.kt
+++ b/app/src/main/java/org/linphone/core/CoreContext.kt
@@ -27,7 +27,6 @@ import android.os.Handler
import android.os.Looper
import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyProperties
-import android.telephony.PhoneStateListener
import android.telephony.TelephonyManager
import android.util.Base64
import android.util.Pair
@@ -56,6 +55,7 @@ import org.linphone.activities.call.CallActivity
import org.linphone.activities.call.IncomingCallActivity
import org.linphone.activities.call.OutgoingCallActivity
import org.linphone.compatibility.Compatibility
+import org.linphone.compatibility.PhoneStateInterface
import org.linphone.contact.Contact
import org.linphone.contact.ContactsManager
import org.linphone.core.tools.Log
@@ -99,37 +99,13 @@ class CoreContext(val context: Context, coreConfig: Config) {
}
private val loggingService = Factory.instance().loggingService
-
private val coroutineScope = CoroutineScope(Dispatchers.Main + SupervisorJob())
- private var gsmCallActive = false
- private val phoneStateListener = object : PhoneStateListener() {
- override fun onCallStateChanged(state: Int, phoneNumber: String?) {
- gsmCallActive = when (state) {
- TelephonyManager.CALL_STATE_OFFHOOK -> {
- Log.i("[Context] Phone state is off hook")
- true
- }
- TelephonyManager.CALL_STATE_RINGING -> {
- Log.i("[Context] Phone state is ringing")
- true
- }
- TelephonyManager.CALL_STATE_IDLE -> {
- Log.i("[Context] Phone state is idle")
- false
- }
- else -> {
- Log.w("[Context] Phone state is unexpected: $state")
- false
- }
- }
- }
- }
-
private var overlayX = 0f
private var overlayY = 0f
private var callOverlay: View? = null
private var previousCallState = Call.State.Idle
+ private lateinit var phoneStateListener: PhoneStateInterface
private val listener: CoreListenerStub = object : CoreListenerStub() {
override fun onGlobalStateChanged(core: Core, state: GlobalState, message: String) {
@@ -159,6 +135,11 @@ class CoreContext(val context: Context, coreConfig: Config) {
) {
Log.i("[Context] Call state changed [$state]")
if (state == Call.State.IncomingReceived || state == Call.State.IncomingEarlyMedia) {
+ var gsmCallActive = false
+ if (::phoneStateListener.isInitialized) {
+ gsmCallActive = phoneStateListener.isInCall()
+ }
+
if (gsmCallActive) {
Log.w("[Context] Refusing the call with reason busy because a GSM call is active")
call.decline(Reason.Busy)
@@ -324,9 +305,13 @@ class CoreContext(val context: Context, coreConfig: Config) {
configureCore()
- val telephonyManager = context.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
- Log.i("[Context] Registering phone state listener")
- telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_CALL_STATE)
+ try {
+ phoneStateListener =
+ Compatibility.createPhoneListener(context.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager)
+ } catch (exception: SecurityException) {
+ val hasReadPhoneStatePermission = PermissionHelper.get().hasReadPhoneState()
+ Log.e("[Context] Failed to create phone state listener: $exception, READ_PHONE_STATE permission status is $hasReadPhoneStatePermission")
+ }
EmojiCompat.init(BundledEmojiCompatConfig(context))
collator.strength = Collator.NO_DECOMPOSITION
@@ -336,10 +321,9 @@ class CoreContext(val context: Context, coreConfig: Config) {
Log.i("[Context] Stopping")
coroutineScope.cancel()
- val telephonyManager = context.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
- Log.i("[Context] Unregistering phone state listener")
- telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_NONE)
-
+ if (::phoneStateListener.isInitialized) {
+ phoneStateListener.destroy()
+ }
notificationsManager.destroy()
contactsManager.destroy()