diff --git a/CHANGELOG.md b/CHANGELOG.md index fb07e11e3..acd2725b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,7 +17,7 @@ Group changes to describe their impact on the project, as follows: - Voice recordings in chat feature - Allow video recording in chat file sharing - Unread messages indicator in chat conversation that separates read & unread messages -- Notify incoming/outgoing calls on bluetooth devices using self-managed connections from telecom manager API +- Notify incoming/outgoing calls on bluetooth devices using self-managed connections from telecom manager API (disables SDK audio focus) - New video call UI on foldable device like Galaxy Z Fold - Setting to automatically record all calls diff --git a/app/src/main/java/org/linphone/activities/assistant/AssistantActivity.kt b/app/src/main/java/org/linphone/activities/assistant/AssistantActivity.kt index f3524463d..16ec6d044 100644 --- a/app/src/main/java/org/linphone/activities/assistant/AssistantActivity.kt +++ b/app/src/main/java/org/linphone/activities/assistant/AssistantActivity.kt @@ -23,6 +23,7 @@ import android.os.Bundle import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.lifecycle.ViewModelProvider import com.google.android.material.snackbar.Snackbar +import org.linphone.LinphoneApplication.Companion.corePreferences import org.linphone.R import org.linphone.activities.GenericActivity import org.linphone.activities.SnackBarActivity @@ -40,6 +41,8 @@ class AssistantActivity : GenericActivity(), SnackBarActivity { sharedViewModel = ViewModelProvider(this)[SharedAssistantViewModel::class.java] coordinator = findViewById(R.id.coordinator) + + corePreferences.firstStart = false } override fun showSnackBar(resourceId: Int) { diff --git a/app/src/main/java/org/linphone/activities/main/MainActivity.kt b/app/src/main/java/org/linphone/activities/main/MainActivity.kt index 6a9771e31..a24cd6b53 100644 --- a/app/src/main/java/org/linphone/activities/main/MainActivity.kt +++ b/app/src/main/java/org/linphone/activities/main/MainActivity.kt @@ -148,7 +148,6 @@ class MainActivity : GenericActivity(), SnackBarActivity, NavController.OnDestin if (coreContext.core.accountList.isEmpty()) { if (corePreferences.firstStart) { - corePreferences.firstStart = false startActivity(Intent(this, AssistantActivity::class.java)) } } diff --git a/app/src/main/java/org/linphone/activities/main/dialer/fragments/DialerFragment.kt b/app/src/main/java/org/linphone/activities/main/dialer/fragments/DialerFragment.kt index 2e28b6677..f2606d932 100644 --- a/app/src/main/java/org/linphone/activities/main/dialer/fragments/DialerFragment.kt +++ b/app/src/main/java/org/linphone/activities/main/dialer/fragments/DialerFragment.kt @@ -45,9 +45,11 @@ import org.linphone.activities.main.viewmodels.DialogViewModel import org.linphone.activities.main.viewmodels.SharedMainViewModel import org.linphone.activities.navigateToConfigFileViewer import org.linphone.activities.navigateToContacts +import org.linphone.compatibility.Compatibility import org.linphone.core.tools.Log import org.linphone.databinding.DialerFragmentBinding import org.linphone.mediastream.Version +import org.linphone.telecom.TelecomHelper import org.linphone.utils.AppUtils import org.linphone.utils.DialogUtils import org.linphone.utils.Event @@ -108,25 +110,6 @@ class DialerFragment : SecureFragment() { } } - if (arguments?.containsKey("Transfer") == true) { - sharedViewModel.pendingCallTransfer = arguments?.getBoolean("Transfer") ?: false - Log.i("[Dialer] Is pending call transfer: ${sharedViewModel.pendingCallTransfer}") - } - - if (arguments?.containsKey("URI") == true) { - val address = arguments?.getString("URI") ?: "" - Log.i("[Dialer] Found URI to call: $address") - val skipAutoCall = arguments?.getBoolean("SkipAutoCallStart") ?: false - - if (corePreferences.callRightAway && !skipAutoCall) { - Log.i("[Dialer] Call right away setting is enabled, start the call to $address") - viewModel.directCall(address) - } else { - sharedViewModel.dialerUri = address - } - } - arguments?.clear() - viewModel.enteredUri.observe( viewLifecycleOwner, { @@ -166,6 +149,30 @@ class DialerFragment : SecureFragment() { } ) + if (corePreferences.firstStart) { + Log.w("[Dialer] First start detected, wait for assistant to be finished to check for update & request permissions") + return + } + + if (arguments?.containsKey("Transfer") == true) { + sharedViewModel.pendingCallTransfer = arguments?.getBoolean("Transfer") ?: false + Log.i("[Dialer] Is pending call transfer: ${sharedViewModel.pendingCallTransfer}") + } + + if (arguments?.containsKey("URI") == true) { + val address = arguments?.getString("URI") ?: "" + Log.i("[Dialer] Found URI to call: $address") + val skipAutoCall = arguments?.getBoolean("SkipAutoCallStart") ?: false + + if (corePreferences.callRightAway && !skipAutoCall) { + Log.i("[Dialer] Call right away setting is enabled, start the call to $address") + viewModel.directCall(address) + } else { + sharedViewModel.dialerUri = address + } + } + arguments?.clear() + Log.i("[Dialer] Pending call transfer mode = ${sharedViewModel.pendingCallTransfer}") viewModel.transferVisibility.value = sharedViewModel.pendingCallTransfer @@ -205,18 +212,72 @@ class DialerFragment : SecureFragment() { Log.i("[Dialer] READ_PHONE_STATE permission has been granted") coreContext.initPhoneStateListener() } + checkTelecomManagerPermissions() + } else if (requestCode == 1) { + var allGranted = true + for (result in grantResults) { + if (result != PackageManager.PERMISSION_GRANTED) { + allGranted = false + } + } + if (allGranted) { + Log.i("[Dialer] Telecom Manager permission have been granted") + enableTelecomManager() + } else { + Log.w("[Dialer] Telecom Manager permission have been denied (at least one of them)") + } } super.onRequestPermissionsResult(requestCode, permissions, grantResults) } @TargetApi(Version.API23_MARSHMALLOW_60) private fun checkPermissions() { + checkReadPhoneStatePermission() + if (Version.sdkAboveOrEqual(Version.API26_O_80) && PermissionHelper.get().hasReadPhoneStatePermission()) { + // Don't check the following the previous permission is being asked + checkTelecomManagerPermissions() + } + } + + @TargetApi(Version.API23_MARSHMALLOW_60) + private fun checkReadPhoneStatePermission() { if (!PermissionHelper.get().hasReadPhoneStatePermission()) { Log.i("[Dialer] Asking for READ_PHONE_STATE permission") requestPermissions(arrayOf(Manifest.permission.READ_PHONE_STATE), 0) } } + @TargetApi(Version.API26_O_80) + private fun checkTelecomManagerPermissions() { + if (!corePreferences.useTelecomManager) { + Log.i("[Dialer] Telecom Manager feature is disabled") + if (corePreferences.manuallyDisabledTelecomManager) { + Log.w("[Dialer] User has manually disabled Telecom Manager feature") + } else { + if (PermissionHelper.get().hasTelecomManagerPermissions()) { + enableTelecomManager() + } else { + Log.i("[Dialer] Asking for Telecom Manager permissions") + Compatibility.requestTelecomManagerPermission(requireActivity(), 1) + } + } + } else { + Log.i("[Dialer] Telecom Manager feature is already enabled") + } + } + + @TargetApi(Version.API26_O_80) + private fun enableTelecomManager() { + Log.i("[Dialer] Telecom Manager permissions granted") + if (!TelecomHelper.exists()) { + Log.i("[Dialer] Creating Telecom Helper") + TelecomHelper.create(requireContext()) + } else { + Log.e("[Dialer] Telecom Manager was already created ?!") + } + corePreferences.useTelecomManager = true + } + private fun displayDebugPopup() { val alertDialog = MaterialAlertDialogBuilder(requireContext()) alertDialog.setTitle(getString(R.string.debug_popup_title)) diff --git a/app/src/main/java/org/linphone/activities/main/settings/fragments/CallSettingsFragment.kt b/app/src/main/java/org/linphone/activities/main/settings/fragments/CallSettingsFragment.kt index 9fb05caf5..5ba103442 100644 --- a/app/src/main/java/org/linphone/activities/main/settings/fragments/CallSettingsFragment.kt +++ b/app/src/main/java/org/linphone/activities/main/settings/fragments/CallSettingsFragment.kt @@ -19,7 +19,6 @@ */ package org.linphone.activities.main.settings.fragments -import android.Manifest import android.content.Intent import android.content.pm.PackageManager import android.net.Uri @@ -95,11 +94,7 @@ class CallSettingsFragment : GenericSettingFragment { it.consume { if (!PermissionHelper.get().hasTelecomManagerPermissions()) { - val permissions = arrayOf( - Manifest.permission.READ_PHONE_NUMBERS, - Manifest.permission.MANAGE_OWN_CALLS - ) - requestPermissions(permissions, 1) + Compatibility.requestTelecomManagerPermission(requireActivity(), 1) } else if (!TelecomHelper.exists()) { corePreferences.useTelecomManager = true Log.w("[Telecom Helper] Doesn't exists yet, creating it") diff --git a/app/src/main/java/org/linphone/activities/main/settings/viewmodels/CallSettingsViewModel.kt b/app/src/main/java/org/linphone/activities/main/settings/viewmodels/CallSettingsViewModel.kt index 47f3ef501..0488a51f3 100644 --- a/app/src/main/java/org/linphone/activities/main/settings/viewmodels/CallSettingsViewModel.kt +++ b/app/src/main/java/org/linphone/activities/main/settings/viewmodels/CallSettingsViewModel.kt @@ -73,6 +73,9 @@ class CallSettingsViewModel : GenericSettingsViewModel() { TelecomHelper.get().removeAccount() TelecomHelper.get().destroy() TelecomHelper.destroy() + + Log.w("[Call Settings] Disabling Telecom Manager auto-enable") + prefs.manuallyDisabledTelecomManager = true } prefs.useTelecomManager = newValue } diff --git a/app/src/main/java/org/linphone/compatibility/Api26Compatibility.kt b/app/src/main/java/org/linphone/compatibility/Api26Compatibility.kt index 8f66015e0..906fafbf4 100644 --- a/app/src/main/java/org/linphone/compatibility/Api26Compatibility.kt +++ b/app/src/main/java/org/linphone/compatibility/Api26Compatibility.kt @@ -19,6 +19,7 @@ */ package org.linphone.compatibility +import android.Manifest import android.annotation.SuppressLint import android.annotation.TargetApi import android.app.Activity @@ -138,5 +139,15 @@ class Api26Compatibility { fun changeAudioRouteForTelecomManager(connection: NativeCallWrapper, route: Int) { connection.setAudioRoute(route) } + + fun requestTelecomManagerPermission(activity: Activity, code: Int) { + activity.requestPermissions( + arrayOf( + Manifest.permission.READ_PHONE_STATE, + Manifest.permission.MANAGE_OWN_CALLS + ), + code + ) + } } } diff --git a/app/src/main/java/org/linphone/compatibility/Api30Compatibility.kt b/app/src/main/java/org/linphone/compatibility/Api30Compatibility.kt index dcf7d050b..433d4c349 100644 --- a/app/src/main/java/org/linphone/compatibility/Api30Compatibility.kt +++ b/app/src/main/java/org/linphone/compatibility/Api30Compatibility.kt @@ -45,6 +45,16 @@ class Api30Compatibility { activity.requestPermissions(arrayOf(Manifest.permission.READ_PHONE_NUMBERS), code) } + fun requestTelecomManagerPermission(activity: Activity, code: Int) { + activity.requestPermissions( + arrayOf( + Manifest.permission.READ_PHONE_NUMBERS, + Manifest.permission.MANAGE_OWN_CALLS + ), + code + ) + } + fun removeChatRoomShortcut(context: Context, chatRoom: ChatRoom) { val shortcutManager = context.getSystemService(ShortcutManager::class.java) val id = LinphoneUtils.getChatRoomId(chatRoom.localAddress, chatRoom.peerAddress) diff --git a/app/src/main/java/org/linphone/compatibility/Compatibility.kt b/app/src/main/java/org/linphone/compatibility/Compatibility.kt index c1c23718a..5df9208e3 100644 --- a/app/src/main/java/org/linphone/compatibility/Compatibility.kt +++ b/app/src/main/java/org/linphone/compatibility/Compatibility.kt @@ -62,6 +62,14 @@ class Compatibility { Api29Compatibility.requestReadPhoneStatePermission(activity, code) } } + // See https://developer.android.com/about/versions/11/privacy/permissions#phone-numbers + fun requestTelecomManagerPermission(activity: Activity, code: Int) { + if (Version.sdkAboveOrEqual(Version.API30_ANDROID_11)) { + Api30Compatibility.requestTelecomManagerPermission(activity, code) + } else { + Api26Compatibility.requestTelecomManagerPermission(activity, code) + } + } fun getDeviceName(context: Context): String { return when (Version.sdkAboveOrEqual(Version.API25_NOUGAT_71)) { diff --git a/app/src/main/java/org/linphone/core/CorePreferences.kt b/app/src/main/java/org/linphone/core/CorePreferences.kt index d423b10eb..0da1cd467 100644 --- a/app/src/main/java/org/linphone/core/CorePreferences.kt +++ b/app/src/main/java/org/linphone/core/CorePreferences.kt @@ -317,6 +317,13 @@ class CorePreferences constructor(private val context: Context) { config.setBool("audio", "android_disable_audio_focus_requests", value) } + // We will try to auto enable Telecom Manager feature, but in case user disables it don't try again + var manuallyDisabledTelecomManager: Boolean + get() = config.getBool("app", "user_disabled_self_managed_telecom_manager", false) + set(value) { + config.setBool("app", "user_disabled_self_managed_telecom_manager", value) + } + var fullScreenCallUI: Boolean get() = config.getBool("app", "full_screen_call", true) set(value) { diff --git a/app/src/main/res/layout/settings_call_fragment.xml b/app/src/main/res/layout/settings_call_fragment.xml index 12caa303b..b8b8645c1 100644 --- a/app/src/main/res/layout/settings_call_fragment.xml +++ b/app/src/main/res/layout/settings_call_fragment.xml @@ -204,7 +204,8 @@ layout="@layout/settings_widget_switch" linphone:title="@{@string/call_settings_pause_calls_lost_audio_focus_title}" linphone:listener="@{viewModel.pauseCallsWhenAudioFocusIsLostListener}" - linphone:checked="@={viewModel.pauseCallsWhenAudioFocusIsLost}"/> + linphone:checked="@={viewModel.pauseCallsWhenAudioFocusIsLost}" + linphone:enabled="@{!viewModel.useTelecomManager}"/>