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()