Skip to content
This repository was archived by the owner on May 2, 2026. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions atox/src/main/kotlin/ActionReceiver.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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
}
}
20 changes: 6 additions & 14 deletions atox/src/main/kotlin/ui/call/CallFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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>(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())
Expand Down Expand Up @@ -70,10 +61,11 @@ class CallFragment : BaseFragment<FragmentCallBinding>(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")
}
}
}
Expand All @@ -99,7 +91,7 @@ class CallFragment : BaseFragment<FragmentCallBinding>(FragmentCallBinding::infl

startCall()

if (requireContext().hasPermission(PERMISSION)) {
if (requireContext().hasPermission(Manifest.permission.RECORD_AUDIO)) {
vm.startSendingAudio()
}
}
Expand Down
18 changes: 17 additions & 1 deletion atox/src/main/kotlin/ui/chat/ChatFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -68,13 +70,21 @@ class OpenMultiplePersistableDocuments : ActivityResultContracts.OpenMultipleDoc
}

class ChatFragment : BaseFragment<FragmentChatBinding>(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<FileTransfer> = 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
Expand Down Expand Up @@ -443,6 +453,12 @@ class ChatFragment : BaseFragment<FragmentChatBinding>(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,
Expand Down
Loading