diff --git a/atox/src/main/kotlin/ActionReceiver.kt b/atox/src/main/kotlin/ActionReceiver.kt index 67fe0562d..1957d99c1 100644 --- a/atox/src/main/kotlin/ActionReceiver.kt +++ b/atox/src/main/kotlin/ActionReceiver.kt @@ -6,15 +6,22 @@ package ltd.evilcorp.atox import android.Manifest +import android.app.Activity import android.content.BroadcastReceiver import android.content.Context import android.content.Intent +import android.net.Uri +import android.provider.Settings import android.util.Log import android.widget.Toast +import androidx.activity.result.contract.ActivityResultContracts +import androidx.appcompat.app.AppCompatActivity import androidx.core.app.RemoteInput import androidx.core.content.IntentCompat +import androidx.fragment.app.Fragment import im.tox.tox4j.av.exceptions.ToxavAnswerException import javax.inject.Inject +import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.firstOrNull @@ -129,6 +136,17 @@ class ActionReceiver : BroadcastReceiver() { return } + if (!context.hasPermission(Manifest.permission.RECORD_AUDIO)) { + // Unable to speak, so reject the call + callManager.endCall(pk) + callManager.removePendingCall(pk) + notificationHelper.dismissCallNotification(pk) + // take the user to Settings to set mic permission + val intent = buildPermissionIntent(context) + context.startActivity(intent) + return + } + try { callManager.startCall(pk) notificationHelper.showOngoingCallNotification(contact) @@ -139,9 +157,22 @@ class ActionReceiver : BroadcastReceiver() { val isSendingAudio = context.hasPermission(Manifest.permission.RECORD_AUDIO) && callManager.startSendingAudio() if (!isSendingAudio) { + Log.e(TAG, "Failed to start sending audio") withContext(Dispatchers.Main) { Toast.makeText(context, R.string.call_mic_permission_needed, Toast.LENGTH_LONG).show() } } } + + private fun buildPermissionIntent(context: Context): Intent { + val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS) + with(intent) { + data = Uri.fromParts("package", context.packageName, null) + addCategory(Intent.CATEGORY_DEFAULT) + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY) + addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) + } + return intent + } } diff --git a/atox/src/main/kotlin/ui/call/CallFragment.kt b/atox/src/main/kotlin/ui/call/CallFragment.kt index 1b2fa540e..24a2ab811 100644 --- a/atox/src/main/kotlin/ui/call/CallFragment.kt +++ b/atox/src/main/kotlin/ui/call/CallFragment.kt @@ -7,6 +7,7 @@ package ltd.evilcorp.atox.ui.call import android.Manifest import android.os.Bundle +import android.util.Log import android.view.View import android.widget.Toast import androidx.activity.result.contract.ActivityResultContracts @@ -26,21 +27,11 @@ import ltd.evilcorp.atox.vmFactory import ltd.evilcorp.core.vo.PublicKey import ltd.evilcorp.domain.feature.CallState -private const val PERMISSION = Manifest.permission.RECORD_AUDIO +private const val TAG = "CallFragment" class CallFragment : BaseFragment(FragmentCallBinding::inflate) { private val vm: CallViewModel by viewModels { vmFactory } - private val requestPermissionLauncher = registerForActivityResult( - ActivityResultContracts.RequestPermission(), - ) { granted -> - if (granted) { - vm.startSendingAudio() - } else { - Toast.makeText(requireContext(), getString(R.string.call_mic_permission_needed), Toast.LENGTH_LONG).show() - } - } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) = binding.run { ViewCompat.setOnApplyWindowInsetsListener(view) { _, compat -> val insets = compat.getInsets(WindowInsetsCompat.Type.systemBars()) @@ -70,10 +61,11 @@ class CallFragment : BaseFragment(FragmentCallBinding::infl if (vm.sendingAudio.value) { vm.stopSendingAudio() } else { - if (requireContext().hasPermission(PERMISSION)) { + if (requireContext().hasPermission(Manifest.permission.RECORD_AUDIO)) { vm.startSendingAudio() } else { - requestPermissionLauncher.launch(PERMISSION) + // not to get here; permission had been checked in ChatFragment.onViewCreated + Log.e(TAG, "Attempt to activate mic without user permission") } } } @@ -99,7 +91,7 @@ class CallFragment : BaseFragment(FragmentCallBinding::infl startCall() - if (requireContext().hasPermission(PERMISSION)) { + if (requireContext().hasPermission(Manifest.permission.RECORD_AUDIO)) { vm.startSendingAudio() } } diff --git a/atox/src/main/kotlin/ui/chat/ChatFragment.kt b/atox/src/main/kotlin/ui/chat/ChatFragment.kt index f9a504063..e330bf648 100644 --- a/atox/src/main/kotlin/ui/chat/ChatFragment.kt +++ b/atox/src/main/kotlin/ui/chat/ChatFragment.kt @@ -5,6 +5,7 @@ package ltd.evilcorp.atox.ui.chat +import android.Manifest import android.app.AlertDialog import android.content.ActivityNotFoundException import android.content.ClipData @@ -45,6 +46,7 @@ import java.util.Locale import ltd.evilcorp.atox.BuildConfig import ltd.evilcorp.atox.R import ltd.evilcorp.atox.databinding.FragmentChatBinding +import ltd.evilcorp.atox.hasPermission import ltd.evilcorp.atox.requireStringArg import ltd.evilcorp.atox.truncated import ltd.evilcorp.atox.ui.BaseFragment @@ -68,13 +70,21 @@ class OpenMultiplePersistableDocuments : ActivityResultContracts.OpenMultipleDoc } class ChatFragment : BaseFragment(FragmentChatBinding::inflate) { - private val viewModel: ChatViewModel by viewModels { vmFactory } + private val viewModel: ChatViewModel by viewModels { vmFactory } private lateinit var contactPubKey: String private var contactName = "" private var selectedFt: Int = Int.MIN_VALUE private var fts: List = listOf() + private val requestPermissionLauncher = registerForActivityResult( + ActivityResultContracts.RequestPermission(), + ) { granted -> + if (!granted) { + Toast.makeText(requireContext(), getString(R.string.call_mic_permission_needed), Toast.LENGTH_LONG).show() + } + } + private val exportBackupLauncher = registerForActivityResult(ActivityResultContracts.CreateDocument("application/json")) { dest -> if (dest == null) return@registerForActivityResult @@ -443,6 +453,12 @@ class ChatFragment : BaseFragment(FragmentChatBinding::infl } private fun navigateToCallScreen() { + // check the permission before opening CallFragment + if (!requireContext().hasPermission(Manifest.permission.RECORD_AUDIO)) { + requestPermissionLauncher.launch(Manifest.permission.RECORD_AUDIO) + return + } + view?.let { WindowInsetsControllerCompat(requireActivity().window, it).hide(WindowInsetsCompat.Type.ime()) } findNavController().navigate( R.id.action_chatFragment_to_callFragment,