Started to work on better permissions handling
This commit is contained in:
@ -57,4 +57,6 @@ dependencies {
|
|||||||
implementation "androidx.core:core-ktx:1.7.0"
|
implementation "androidx.core:core-ktx:1.7.0"
|
||||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1'
|
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1'
|
||||||
implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
|
implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
|
||||||
|
implementation "androidx.activity:activity-ktx:1.4.0"
|
||||||
|
implementation "androidx.fragment:fragment-ktx:1.3.6"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -41,6 +41,7 @@
|
|||||||
android:roundIcon="@mipmap/ic_launcher"
|
android:roundIcon="@mipmap/ic_launcher"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/AppTheme"
|
android:theme="@style/AppTheme"
|
||||||
|
tools:remove="android:appComponentFactory"
|
||||||
tools:targetApi="q">
|
tools:targetApi="q">
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
|
|||||||
@ -21,6 +21,7 @@ import android.view.View
|
|||||||
import android.widget.RemoteViews
|
import android.widget.RemoteViews
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.BroadcastReceiver
|
import android.content.BroadcastReceiver
|
||||||
|
import android.content.pm.PackageManager
|
||||||
import androidx.annotation.ColorRes
|
import androidx.annotation.ColorRes
|
||||||
import androidx.annotation.Keep
|
import androidx.annotation.Keep
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
@ -36,6 +37,7 @@ import kotlin.concurrent.schedule
|
|||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
import android.media.MediaPlayer
|
import android.media.MediaPlayer
|
||||||
import android.telecom.TelecomManager
|
import android.telecom.TelecomManager
|
||||||
|
import androidx.core.app.ActivityCompat
|
||||||
|
|
||||||
class BaresipService: Service() {
|
class BaresipService: Service() {
|
||||||
|
|
||||||
@ -576,20 +578,23 @@ class BaresipService: Service() {
|
|||||||
"call incoming" -> {
|
"call incoming" -> {
|
||||||
val peerUri = Api.call_peeruri(callp)
|
val peerUri = Api.call_peeruri(callp)
|
||||||
if (Call.calls().size > 0 ||
|
if (Call.calls().size > 0 ||
|
||||||
(Utils.checkPermission(this, Manifest.permission.READ_PHONE_STATE) &&
|
!Utils.checkPermissions(this, arrayOf(Manifest.permission.RECORD_AUDIO)) ||
|
||||||
tm.isInCall) ||
|
ActivityCompat.checkSelfPermission(
|
||||||
!Utils.checkPermission(applicationContext, Manifest.permission.RECORD_AUDIO)) {
|
this,
|
||||||
Log.d(TAG, "Auto-rejecting incoming call $uap/$callp/$peerUri")
|
Manifest.permission.READ_PHONE_STATE
|
||||||
Api.ua_hangup(uap, callp, 486, "Busy Here")
|
) != PackageManager.PERMISSION_GRANTED ||
|
||||||
if (ua.account.callHistory) {
|
tm.isInCall) {
|
||||||
CallHistory.add(CallHistory(aor, peerUri, "in", false))
|
Log.d(TAG, "Auto-rejecting incoming call $uap/$callp/$peerUri")
|
||||||
CallHistory.save()
|
Api.ua_hangup(uap, callp, 486, "Busy Here")
|
||||||
ua.account.missedCalls = true
|
if (ua.account.callHistory) {
|
||||||
}
|
CallHistory.add(CallHistory(aor, peerUri, "in", false))
|
||||||
playUnInterrupted(R.raw.callwaiting, 1)
|
CallHistory.save()
|
||||||
if (!Utils.isVisible())
|
ua.account.missedCalls = true
|
||||||
return
|
}
|
||||||
newEvent = "call rejected"
|
playUnInterrupted(R.raw.callwaiting, 1)
|
||||||
|
if (!Utils.isVisible())
|
||||||
|
return
|
||||||
|
newEvent = "call rejected"
|
||||||
} else {
|
} else {
|
||||||
Log.d(TAG, "Incoming call $uap/$callp/$peerUri")
|
Log.d(TAG, "Incoming call $uap/$callp/$peerUri")
|
||||||
Call(callp, ua, peerUri, "in", "incoming",
|
Call(callp, ua, peerUri, "in", "incoming",
|
||||||
|
|||||||
@ -12,10 +12,15 @@ import android.view.MenuItem
|
|||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.*
|
import android.widget.*
|
||||||
import android.widget.AdapterView
|
import android.widget.AdapterView
|
||||||
|
import androidx.activity.result.ActivityResultLauncher
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.appcompat.app.AppCompatDelegate
|
import androidx.appcompat.app.AppCompatDelegate
|
||||||
|
import androidx.core.app.ActivityCompat
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import com.tutpro.baresip.Utils.copyInputStreamToFile
|
import com.tutpro.baresip.Utils.copyInputStreamToFile
|
||||||
|
import com.tutpro.baresip.Utils.showSnackBar
|
||||||
import com.tutpro.baresip.databinding.ActivityConfigBinding
|
import com.tutpro.baresip.databinding.ActivityConfigBinding
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileInputStream
|
import java.io.FileInputStream
|
||||||
@ -24,6 +29,7 @@ import java.util.*
|
|||||||
class ConfigActivity : AppCompatActivity() {
|
class ConfigActivity : AppCompatActivity() {
|
||||||
|
|
||||||
private lateinit var binding: ActivityConfigBinding
|
private lateinit var binding: ActivityConfigBinding
|
||||||
|
private lateinit var layout: ScrollView
|
||||||
private lateinit var autoStart: CheckBox
|
private lateinit var autoStart: CheckBox
|
||||||
private lateinit var listenAddr: EditText
|
private lateinit var listenAddr: EditText
|
||||||
private lateinit var dnsServers: EditText
|
private lateinit var dnsServers: EditText
|
||||||
@ -34,6 +40,7 @@ class ConfigActivity : AppCompatActivity() {
|
|||||||
private lateinit var debug: CheckBox
|
private lateinit var debug: CheckBox
|
||||||
private lateinit var sipTrace: CheckBox
|
private lateinit var sipTrace: CheckBox
|
||||||
private lateinit var reset: CheckBox
|
private lateinit var reset: CheckBox
|
||||||
|
private lateinit var requestPermissionLauncher: ActivityResultLauncher<String>
|
||||||
|
|
||||||
private var oldAutoStart = ""
|
private var oldAutoStart = ""
|
||||||
private var oldListenAddr = ""
|
private var oldListenAddr = ""
|
||||||
@ -54,6 +61,7 @@ class ConfigActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
binding = ActivityConfigBinding.inflate(layoutInflater)
|
binding = ActivityConfigBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
|
layout = binding.ConfigView
|
||||||
|
|
||||||
Utils.addActivity("config")
|
Utils.addActivity("config")
|
||||||
|
|
||||||
@ -88,8 +96,9 @@ class ConfigActivity : AppCompatActivity() {
|
|||||||
if (it.resultCode == RESULT_OK) {
|
if (it.resultCode == RESULT_OK) {
|
||||||
it.data?.data?.also { uri ->
|
it.data?.data?.also { uri ->
|
||||||
try {
|
try {
|
||||||
val inputStream = applicationContext.contentResolver.openInputStream(uri)
|
val inputStream =
|
||||||
as FileInputStream
|
applicationContext.contentResolver.openInputStream(uri)
|
||||||
|
as FileInputStream
|
||||||
File(BaresipService.filesPath + "/cert.pem")
|
File(BaresipService.filesPath + "/cert.pem")
|
||||||
.copyInputStreamToFile(inputStream)
|
.copyInputStreamToFile(inputStream)
|
||||||
inputStream.close()
|
inputStream.close()
|
||||||
@ -113,26 +122,45 @@ class ConfigActivity : AppCompatActivity() {
|
|||||||
certificateFile.setOnCheckedChangeListener { _, isChecked ->
|
certificateFile.setOnCheckedChangeListener { _, isChecked ->
|
||||||
if (isChecked) {
|
if (isChecked) {
|
||||||
if (Build.VERSION.SDK_INT < 29) {
|
if (Build.VERSION.SDK_INT < 29) {
|
||||||
if (!Utils.requestPermission(this,
|
certificateFile.isChecked = false
|
||||||
Manifest.permission.READ_EXTERNAL_STORAGE,
|
if (ContextCompat.checkSelfPermission(
|
||||||
READ_CERT_PERMISSION_CODE)) {
|
this,
|
||||||
certificateFile.isChecked = false
|
Manifest.permission.READ_EXTERNAL_STORAGE
|
||||||
return@setOnCheckedChangeListener
|
) == PackageManager.PERMISSION_GRANTED
|
||||||
|
) {
|
||||||
|
Log.d(TAG, "Read External Storage permission granted")
|
||||||
|
val downloadsPath = Utils.downloadsPath("cert.pem")
|
||||||
|
val content = Utils.getFileContents(downloadsPath)
|
||||||
|
if (content == null) {
|
||||||
|
Utils.alertView(
|
||||||
|
this, getString(R.string.error),
|
||||||
|
getString(R.string.read_cert_error)
|
||||||
|
)
|
||||||
|
return@setOnCheckedChangeListener
|
||||||
|
}
|
||||||
|
val filesPath = BaresipService.filesPath + "/cert.pem"
|
||||||
|
Utils.putFileContents(filesPath, content)
|
||||||
|
Config.removeVariable("sip_certificate")
|
||||||
|
Config.addLine("sip_certificate $filesPath")
|
||||||
|
certificateFile.isChecked = true
|
||||||
|
save = true
|
||||||
|
restart = true
|
||||||
|
} else if (ActivityCompat.shouldShowRequestPermissionRationale(
|
||||||
|
this,
|
||||||
|
Manifest.permission.READ_EXTERNAL_STORAGE
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
layout.showSnackBar(
|
||||||
|
binding.root,
|
||||||
|
getString(R.string.no_restore),
|
||||||
|
Snackbar.LENGTH_INDEFINITE,
|
||||||
|
getString(R.string.ok)
|
||||||
|
) {
|
||||||
|
requestPermissionLauncher.launch(Manifest.permission.READ_EXTERNAL_STORAGE)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
requestPermissionLauncher.launch(Manifest.permission.READ_EXTERNAL_STORAGE)
|
||||||
}
|
}
|
||||||
val downloadsPath = Utils.downloadsPath("cert.pem")
|
|
||||||
val content = Utils.getFileContents(downloadsPath)
|
|
||||||
if (content == null) {
|
|
||||||
Utils.alertView(this, getString(R.string.error),
|
|
||||||
getString(R.string.read_cert_error))
|
|
||||||
certificateFile.isChecked = false
|
|
||||||
return@setOnCheckedChangeListener
|
|
||||||
}
|
|
||||||
val filesPath = BaresipService.filesPath + "/cert.pem"
|
|
||||||
Utils.putFileContents(filesPath, content)
|
|
||||||
Config.removeVariable("sip_certificate")
|
|
||||||
Config.addLine("sip_certificate $filesPath")
|
|
||||||
save = true
|
|
||||||
restart = true
|
|
||||||
} else {
|
} else {
|
||||||
Utils.selectInputFile(certificateRequest)
|
Utils.selectInputFile(certificateRequest)
|
||||||
}
|
}
|
||||||
@ -157,8 +185,9 @@ class ConfigActivity : AppCompatActivity() {
|
|||||||
if (it.resultCode == Activity.RESULT_OK)
|
if (it.resultCode == Activity.RESULT_OK)
|
||||||
it.data?.data?.also { uri ->
|
it.data?.data?.also { uri ->
|
||||||
try {
|
try {
|
||||||
val inputStream = applicationContext.contentResolver.openInputStream(uri)
|
val inputStream =
|
||||||
as FileInputStream
|
applicationContext.contentResolver.openInputStream(uri)
|
||||||
|
as FileInputStream
|
||||||
File(BaresipService.filesPath + "/ca_certs.crt")
|
File(BaresipService.filesPath + "/ca_certs.crt")
|
||||||
.copyInputStreamToFile(inputStream)
|
.copyInputStreamToFile(inputStream)
|
||||||
inputStream.close()
|
inputStream.close()
|
||||||
@ -167,8 +196,10 @@ class ConfigActivity : AppCompatActivity() {
|
|||||||
save = true
|
save = true
|
||||||
restart = true
|
restart = true
|
||||||
} catch (e: Error) {
|
} catch (e: Error) {
|
||||||
Utils.alertView(this, getString(R.string.error),
|
Utils.alertView(
|
||||||
getString(R.string.read_ca_certs_error))
|
this, getString(R.string.error),
|
||||||
|
getString(R.string.read_ca_certs_error)
|
||||||
|
)
|
||||||
caFile.isChecked = false
|
caFile.isChecked = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -179,26 +210,45 @@ class ConfigActivity : AppCompatActivity() {
|
|||||||
caFile.setOnCheckedChangeListener { _, isChecked ->
|
caFile.setOnCheckedChangeListener { _, isChecked ->
|
||||||
if (isChecked) {
|
if (isChecked) {
|
||||||
if (Build.VERSION.SDK_INT < 29) {
|
if (Build.VERSION.SDK_INT < 29) {
|
||||||
if (!Utils.requestPermission(this,
|
caFile.isChecked = false
|
||||||
Manifest.permission.READ_EXTERNAL_STORAGE,
|
if (ContextCompat.checkSelfPermission(
|
||||||
READ_CERT_PERMISSION_CODE)) {
|
this,
|
||||||
caFile.isChecked = false
|
Manifest.permission.READ_EXTERNAL_STORAGE
|
||||||
return@setOnCheckedChangeListener
|
) == PackageManager.PERMISSION_GRANTED
|
||||||
|
) {
|
||||||
|
Log.d(TAG, "Read External Storage permission granted")
|
||||||
|
val downloadsPath = Utils.downloadsPath("ca_certs.crt")
|
||||||
|
val content = Utils.getFileContents(downloadsPath)
|
||||||
|
if (content == null) {
|
||||||
|
Utils.alertView(
|
||||||
|
this, getString(R.string.error),
|
||||||
|
getString(R.string.read_ca_certs_error)
|
||||||
|
)
|
||||||
|
return@setOnCheckedChangeListener
|
||||||
|
}
|
||||||
|
val filesPath = BaresipService.filesPath + "/ca_certs.crt"
|
||||||
|
Utils.putFileContents(filesPath, content)
|
||||||
|
Config.removeVariable("sip_cafile")
|
||||||
|
Config.addLine("sip_cafile $filesPath")
|
||||||
|
caFile.isChecked = true
|
||||||
|
save = true
|
||||||
|
restart = true
|
||||||
|
} else if (ActivityCompat.shouldShowRequestPermissionRationale(
|
||||||
|
this,
|
||||||
|
Manifest.permission.READ_EXTERNAL_STORAGE
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
layout.showSnackBar(
|
||||||
|
binding.root,
|
||||||
|
getString(R.string.no_restore),
|
||||||
|
Snackbar.LENGTH_INDEFINITE,
|
||||||
|
getString(R.string.ok)
|
||||||
|
) {
|
||||||
|
requestPermissionLauncher.launch(Manifest.permission.READ_EXTERNAL_STORAGE)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
requestPermissionLauncher.launch(Manifest.permission.READ_EXTERNAL_STORAGE)
|
||||||
}
|
}
|
||||||
val downloadsPath = Utils.downloadsPath("ca_certs.crt")
|
|
||||||
val content = Utils.getFileContents(downloadsPath)
|
|
||||||
if (content == null) {
|
|
||||||
Utils.alertView(this, getString(R.string.error),
|
|
||||||
getString(R.string.read_ca_certs_error))
|
|
||||||
caFile.isChecked = false
|
|
||||||
return@setOnCheckedChangeListener
|
|
||||||
}
|
|
||||||
val filesPath = BaresipService.filesPath + "/ca_certs.crt"
|
|
||||||
Utils.putFileContents(filesPath, content)
|
|
||||||
Config.removeVariable("sip_cafile")
|
|
||||||
Config.addLine("sip_cafile $filesPath")
|
|
||||||
save = true
|
|
||||||
restart = true
|
|
||||||
} else {
|
} else {
|
||||||
Utils.selectInputFile(certificatesRequest)
|
Utils.selectInputFile(certificatesRequest)
|
||||||
}
|
}
|
||||||
@ -218,14 +268,22 @@ class ConfigActivity : AppCompatActivity() {
|
|||||||
volVals.removeAt(curVal)
|
volVals.removeAt(curVal)
|
||||||
volKeys.add(0, curKey)
|
volKeys.add(0, curKey)
|
||||||
volVals.add(0, curVal)
|
volVals.add(0, curVal)
|
||||||
val callVolAdapter = ArrayAdapter(this, android.R.layout.simple_spinner_item,
|
val callVolAdapter = ArrayAdapter(
|
||||||
volKeys)
|
this, android.R.layout.simple_spinner_item,
|
||||||
|
volKeys
|
||||||
|
)
|
||||||
callVolAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
callVolAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
||||||
callVolSpinner.adapter = callVolAdapter
|
callVolSpinner.adapter = callVolAdapter
|
||||||
callVolSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
|
callVolSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
|
||||||
override fun onItemSelected(parent: AdapterView<*>, view: View?, position: Int, id: Long) {
|
override fun onItemSelected(
|
||||||
|
parent: AdapterView<*>,
|
||||||
|
view: View?,
|
||||||
|
position: Int,
|
||||||
|
id: Long
|
||||||
|
) {
|
||||||
callVolume = volVals[volKeys.indexOf(parent.selectedItem.toString())]
|
callVolume = volVals[volKeys.indexOf(parent.selectedItem.toString())]
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onNothingSelected(parent: AdapterView<*>) {
|
override fun onNothingSelected(parent: AdapterView<*>) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -240,7 +298,7 @@ class ConfigActivity : AppCompatActivity() {
|
|||||||
"2"
|
"2"
|
||||||
else
|
else
|
||||||
dbCv[0]
|
dbCv[0]
|
||||||
debug.isChecked = oldLogLevel == "0"
|
debug.isChecked = oldLogLevel == "0"
|
||||||
|
|
||||||
sipTrace = binding.SipTrace
|
sipTrace = binding.SipTrace
|
||||||
sipTrace.isChecked = BaresipService.sipTrace
|
sipTrace.isChecked = BaresipService.sipTrace
|
||||||
@ -252,7 +310,7 @@ class ConfigActivity : AppCompatActivity() {
|
|||||||
if (isChecked) {
|
if (isChecked) {
|
||||||
val titleView = View.inflate(this, R.layout.alert_title, null) as TextView
|
val titleView = View.inflate(this, R.layout.alert_title, null) as TextView
|
||||||
titleView.text = getString(R.string.confirmation)
|
titleView.text = getString(R.string.confirmation)
|
||||||
with (AlertDialog.Builder(this@ConfigActivity)) {
|
with(AlertDialog.Builder(this@ConfigActivity)) {
|
||||||
setCustomTitle(titleView)
|
setCustomTitle(titleView)
|
||||||
setMessage(getString(R.string.reset_config_alert))
|
setMessage(getString(R.string.reset_config_alert))
|
||||||
setPositiveButton(getText(R.string.reset)) { dialog, _ ->
|
setPositiveButton(getText(R.string.reset)) { dialog, _ ->
|
||||||
@ -275,6 +333,12 @@ class ConfigActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onStart() {
|
||||||
|
super.onStart()
|
||||||
|
requestPermissionLauncher =
|
||||||
|
registerForActivityResult(ActivityResultContracts.RequestPermission()) {}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||||
|
|
||||||
super.onCreateOptionsMenu(menu)
|
super.onCreateOptionsMenu(menu)
|
||||||
@ -413,28 +477,6 @@ class ConfigActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>,
|
|
||||||
grantResults: IntArray) {
|
|
||||||
|
|
||||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
|
||||||
|
|
||||||
when (requestCode) {
|
|
||||||
READ_CERT_PERMISSION_CODE ->
|
|
||||||
if (grantResults.isNotEmpty() &&
|
|
||||||
(grantResults[0] == PackageManager.PERMISSION_GRANTED))
|
|
||||||
menu!!.performIdentifierAction(R.id.checkIcon, 0)
|
|
||||||
else
|
|
||||||
certificateFile.isChecked = false
|
|
||||||
READ_CA_PERMISSION_CODE ->
|
|
||||||
if (grantResults.isNotEmpty() &&
|
|
||||||
(grantResults[0] == PackageManager.PERMISSION_GRANTED))
|
|
||||||
menu!!.performIdentifierAction(R.id.checkIcon, 0)
|
|
||||||
else
|
|
||||||
caFile.isChecked = false
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onBackPressed() {
|
override fun onBackPressed() {
|
||||||
|
|
||||||
BaresipService.activities.remove("config")
|
BaresipService.activities.remove("config")
|
||||||
|
|||||||
@ -7,10 +7,11 @@ const val HIGH_CHANNEL_ID = "com.tutpro.baresip.high"
|
|||||||
|
|
||||||
const val BACKUP_PERMISSION_REQUEST_CODE = 1
|
const val BACKUP_PERMISSION_REQUEST_CODE = 1
|
||||||
const val RESTORE_PERMISSION_REQUEST_CODE = 2
|
const val RESTORE_PERMISSION_REQUEST_CODE = 2
|
||||||
const val RECORD_PERMISSION_REQUEST_CODE = 3
|
const val CALL_PERMISSION_REQUEST_CODE = 3
|
||||||
const val CONTACTS_PERMISSION_REQUEST_CODE = 4
|
const val CONTACT_PERMISSION_REQUEST_CODE = 4
|
||||||
const val READ_CERT_PERMISSION_CODE = 5
|
const val READ_CERT_PERMISSION_CODE = 5
|
||||||
const val READ_CA_PERMISSION_CODE = 6
|
const val READ_CA_PERMISSION_CODE = 6
|
||||||
|
const val REQUEST_PERMISSIONS_CODE = 7
|
||||||
|
|
||||||
const val STATUS_NOTIFICATION_ID = 101
|
const val STATUS_NOTIFICATION_ID = 101
|
||||||
const val CALL_NOTIFICATION_ID = 102
|
const val CALL_NOTIFICATION_ID = 102
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import android.graphics.Bitmap
|
|||||||
import android.graphics.BitmapFactory
|
import android.graphics.BitmapFactory
|
||||||
import android.graphics.Matrix
|
import android.graphics.Matrix
|
||||||
import android.graphics.drawable.BitmapDrawable
|
import android.graphics.drawable.BitmapDrawable
|
||||||
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.provider.ContactsContract
|
import android.provider.ContactsContract
|
||||||
import android.provider.ContactsContract.CommonDataKinds
|
import android.provider.ContactsContract.CommonDataKinds
|
||||||
@ -20,10 +21,14 @@ import android.view.Menu
|
|||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.*
|
import android.widget.*
|
||||||
|
import androidx.activity.result.ActivityResultLauncher
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.cardview.widget.CardView
|
import androidx.cardview.widget.CardView
|
||||||
|
import androidx.core.app.ActivityCompat
|
||||||
import androidx.exifinterface.media.ExifInterface
|
import androidx.exifinterface.media.ExifInterface
|
||||||
|
import com.google.android.material.snackbar.Snackbar
|
||||||
|
import com.tutpro.baresip.Utils.showSnackBar
|
||||||
import com.tutpro.baresip.databinding.ActivityContactBinding
|
import com.tutpro.baresip.databinding.ActivityContactBinding
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@ -31,6 +36,7 @@ import java.io.File
|
|||||||
class ContactActivity : AppCompatActivity() {
|
class ContactActivity : AppCompatActivity() {
|
||||||
|
|
||||||
private lateinit var binding: ActivityContactBinding
|
private lateinit var binding: ActivityContactBinding
|
||||||
|
private lateinit var layout: LinearLayout
|
||||||
private lateinit var textAvatarView: TextView
|
private lateinit var textAvatarView: TextView
|
||||||
private lateinit var cardAvatarView: CardView
|
private lateinit var cardAvatarView: CardView
|
||||||
private lateinit var cardImageAvatarView: ImageView
|
private lateinit var cardImageAvatarView: ImageView
|
||||||
@ -38,6 +44,7 @@ class ContactActivity : AppCompatActivity() {
|
|||||||
private lateinit var uriView: EditText
|
private lateinit var uriView: EditText
|
||||||
private lateinit var androidCheck: CheckBox
|
private lateinit var androidCheck: CheckBox
|
||||||
private lateinit var menu: Menu
|
private lateinit var menu: Menu
|
||||||
|
private lateinit var requestPermissionsLauncher: ActivityResultLauncher<Array<String>>
|
||||||
|
|
||||||
private var newContact = false
|
private var newContact = false
|
||||||
private var newAvatar = ""
|
private var newAvatar = ""
|
||||||
@ -48,11 +55,15 @@ class ContactActivity : AppCompatActivity() {
|
|||||||
private var id: Long = 0
|
private var id: Long = 0
|
||||||
private var oldAndroid = false
|
private var oldAndroid = false
|
||||||
|
|
||||||
|
private val permissions =
|
||||||
|
arrayOf(Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS)
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
binding = ActivityContactBinding.inflate(layoutInflater)
|
binding = ActivityContactBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
|
layout = binding.ContactView
|
||||||
|
|
||||||
textAvatarView = binding.TextAvatar
|
textAvatarView = binding.TextAvatar
|
||||||
cardAvatarView = binding.CardAvatar
|
cardAvatarView = binding.CardAvatar
|
||||||
@ -166,10 +177,22 @@ class ContactActivity : AppCompatActivity() {
|
|||||||
getString(R.string.android_contact_help))
|
getString(R.string.android_contact_help))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
androidCheck.setOnClickListener{
|
||||||
|
if (Build.VERSION.SDK_INT >= 23)
|
||||||
|
if (!Utils.checkPermissions(this, permissions))
|
||||||
|
requestPermissions(permissions, CONTACT_PERMISSION_REQUEST_CODE)
|
||||||
|
}
|
||||||
|
|
||||||
Utils.addActivity("contact,$newContact,$uOrI")
|
Utils.addActivity("contact,$newContact,$uOrI")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onStart() {
|
||||||
|
super.onStart()
|
||||||
|
requestPermissionsLauncher =
|
||||||
|
registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) {}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreateOptionsMenu(optionsMenu: Menu): Boolean {
|
override fun onCreateOptionsMenu(optionsMenu: Menu): Boolean {
|
||||||
|
|
||||||
super.onCreateOptionsMenu(optionsMenu)
|
super.onCreateOptionsMenu(optionsMenu)
|
||||||
@ -181,6 +204,47 @@ class ContactActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>,
|
||||||
|
grandResults: IntArray) {
|
||||||
|
super.onRequestPermissionsResult(requestCode, permissions, grandResults)
|
||||||
|
var allowed = true
|
||||||
|
when (requestCode) {
|
||||||
|
CONTACT_PERMISSION_REQUEST_CODE ->
|
||||||
|
for (res in grandResults)
|
||||||
|
allowed = allowed && res == PackageManager.PERMISSION_GRANTED
|
||||||
|
}
|
||||||
|
if (!allowed) {
|
||||||
|
androidCheck.isChecked = oldAndroid
|
||||||
|
if (ActivityCompat.shouldShowRequestPermissionRationale(
|
||||||
|
this,
|
||||||
|
Manifest.permission.READ_CONTACTS
|
||||||
|
))
|
||||||
|
{
|
||||||
|
layout.showSnackBar(
|
||||||
|
binding.root,
|
||||||
|
getString(R.string.no_android_contacts),
|
||||||
|
Snackbar.LENGTH_INDEFINITE,
|
||||||
|
getString(R.string.ok)
|
||||||
|
) {
|
||||||
|
requestPermissionsLauncher.launch(permissions)
|
||||||
|
}
|
||||||
|
} else if (ActivityCompat.shouldShowRequestPermissionRationale(
|
||||||
|
this,
|
||||||
|
Manifest.permission.WRITE_CONTACTS
|
||||||
|
))
|
||||||
|
{
|
||||||
|
layout.showSnackBar(
|
||||||
|
binding.root,
|
||||||
|
getString(R.string.no_android_contacts),
|
||||||
|
Snackbar.LENGTH_INDEFINITE,
|
||||||
|
getString(R.string.ok)
|
||||||
|
) {
|
||||||
|
requestPermissionsLauncher.launch(permissions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
|
|
||||||
if (BaresipService.activities.indexOf("contact,$newContact,$uOrI") == -1) return true
|
if (BaresipService.activities.indexOf("contact,$newContact,$uOrI") == -1) return true
|
||||||
@ -260,20 +324,13 @@ class ContactActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
Contact.contacts().sortBy { Contact -> Contact.name }
|
Contact.contacts().sortBy { Contact -> Contact.name }
|
||||||
|
|
||||||
if (Utils.checkPermission(this, Manifest.permission.READ_CONTACTS +
|
if (Utils.checkPermissions(this, permissions)) {
|
||||||
"|" + Manifest.permission.WRITE_CONTACTS)) {
|
|
||||||
if (contact.androidContact)
|
if (contact.androidContact)
|
||||||
addOrUpdateAndroidContact(this, contact)
|
addOrUpdateAndroidContact(this, contact)
|
||||||
else
|
else if (oldAndroid)
|
||||||
if (oldAndroid)
|
deleteAndroidContact(this, contact)
|
||||||
deleteAndroidContact(this, contact)
|
|
||||||
} else {
|
} else {
|
||||||
if (contact.androidContact) {
|
contact.androidContact = oldAndroid
|
||||||
Utils.requestPermission(this, Manifest.permission.READ_CONTACTS +
|
|
||||||
"|" + Manifest.permission.WRITE_CONTACTS,
|
|
||||||
CONTACTS_PERMISSION_REQUEST_CODE)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Contact.save()
|
Contact.save()
|
||||||
@ -298,23 +355,6 @@ class ContactActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>,
|
|
||||||
grantResults: IntArray) {
|
|
||||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
|
||||||
|
|
||||||
when (requestCode) {
|
|
||||||
|
|
||||||
CONTACTS_PERMISSION_REQUEST_CODE ->
|
|
||||||
if (grantResults.isNotEmpty() && permissions.size == grantResults.size) {
|
|
||||||
for (res in grantResults)
|
|
||||||
if (res != PackageManager.PERMISSION_GRANTED)
|
|
||||||
return
|
|
||||||
menu.performIdentifierAction(R.id.checkIcon, 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onBackPressed() {
|
override fun onBackPressed() {
|
||||||
|
|
||||||
BaresipService.activities.remove("contact,$newContact,$uOrI")
|
BaresipService.activities.remove("contact,$newContact,$uOrI")
|
||||||
|
|||||||
@ -128,7 +128,7 @@ class ContactListAdapter(private val ctx: Context, private val rows: ArrayList<C
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (contact.androidContact &&
|
if (contact.androidContact &&
|
||||||
Utils.checkPermission(ctx, Manifest.permission.WRITE_CONTACTS))
|
Utils.checkPermissions(ctx, arrayOf(Manifest.permission.WRITE_CONTACTS)))
|
||||||
ContactActivity.deleteAndroidContact(ctx, contact)
|
ContactActivity.deleteAndroidContact(ctx, contact)
|
||||||
Contact.contacts().removeAt(position)
|
Contact.contacts().removeAt(position)
|
||||||
Contact.save()
|
Contact.save()
|
||||||
|
|||||||
@ -29,8 +29,12 @@ import androidx.annotation.RequiresApi
|
|||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.appcompat.app.AppCompatDelegate
|
import androidx.appcompat.app.AppCompatDelegate
|
||||||
|
import androidx.core.app.ActivityCompat
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager
|
import androidx.localbroadcastmanager.content.LocalBroadcastManager
|
||||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||||
|
import com.google.android.material.snackbar.Snackbar
|
||||||
|
import com.tutpro.baresip.Utils.showSnackBar
|
||||||
import com.tutpro.baresip.databinding.ActivityMainBinding
|
import com.tutpro.baresip.databinding.ActivityMainBinding
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileInputStream
|
import java.io.FileInputStream
|
||||||
@ -72,6 +76,8 @@ class MainActivity : AppCompatActivity() {
|
|||||||
private lateinit var stopState: String
|
private lateinit var stopState: String
|
||||||
private var speakerIcon: MenuItem? = null
|
private var speakerIcon: MenuItem? = null
|
||||||
private lateinit var swipeRefresh: SwipeRefreshLayout
|
private lateinit var swipeRefresh: SwipeRefreshLayout
|
||||||
|
private lateinit var requestPermissionLauncher: ActivityResultLauncher<String>
|
||||||
|
private lateinit var requestPermissionsLauncher: ActivityResultLauncher<Array<String>>
|
||||||
|
|
||||||
private lateinit var accountsRequest: ActivityResultLauncher<Intent>
|
private lateinit var accountsRequest: ActivityResultLauncher<Intent>
|
||||||
private lateinit var chatRequests: ActivityResultLauncher<Intent>
|
private lateinit var chatRequests: ActivityResultLauncher<Intent>
|
||||||
@ -304,44 +310,10 @@ class MainActivity : AppCompatActivity() {
|
|||||||
callButton.setOnClickListener {
|
callButton.setOnClickListener {
|
||||||
if (aorSpinner.selectedItemPosition == -1)
|
if (aorSpinner.selectedItemPosition == -1)
|
||||||
return@setOnClickListener
|
return@setOnClickListener
|
||||||
callUri.setAdapter(null)
|
val permissions = arrayOf(Manifest.permission.RECORD_AUDIO, Manifest.permission.READ_PHONE_STATE)
|
||||||
val ua = UserAgent.uas()[aorSpinner.selectedItemPosition]
|
if (Build.VERSION.SDK_INT >= 23)
|
||||||
val aor = ua.account.aor
|
if (!Utils.checkPermissions(this, permissions))
|
||||||
if (Call.calls().isEmpty()) {
|
requestPermissions(permissions, CALL_PERMISSION_REQUEST_CODE)
|
||||||
val uriText = callUri.text.toString().trim()
|
|
||||||
if (uriText.isNotEmpty()) {
|
|
||||||
val uri = Utils.uriComplete(
|
|
||||||
ContactsActivity.findContactURI(uriText).filterNot { it.isWhitespace() },
|
|
||||||
Utils.aorDomain(aor)
|
|
||||||
)
|
|
||||||
if (!Utils.checkSipUri(uri)) {
|
|
||||||
Utils.alertView(this, getString(R.string.notice),
|
|
||||||
String.format(getString(R.string.invalid_sip_uri), uri))
|
|
||||||
} else {
|
|
||||||
callUri.isFocusable = false
|
|
||||||
if (!call(ua, uri)) {
|
|
||||||
callButton.visibility = View.VISIBLE
|
|
||||||
callButton.isEnabled = true
|
|
||||||
hangupButton.visibility = View.INVISIBLE
|
|
||||||
hangupButton.isEnabled = false
|
|
||||||
} else {
|
|
||||||
callButton.visibility = View.INVISIBLE
|
|
||||||
callButton.isEnabled = false
|
|
||||||
hangupButton.visibility = View.VISIBLE
|
|
||||||
hangupButton.isEnabled = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
val latest = CallHistory.aorLatestHistory(aor)
|
|
||||||
if (latest != null)
|
|
||||||
callUri.setText(
|
|
||||||
Utils.friendlyUri(
|
|
||||||
ContactsActivity.contactName(latest.peerUri),
|
|
||||||
Utils.aorDomain(ua.account.aor)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
hangupButton.setOnClickListener {
|
hangupButton.setOnClickListener {
|
||||||
@ -656,17 +628,182 @@ class MainActivity : AppCompatActivity() {
|
|||||||
delegate.applyDayNight()
|
delegate.applyDayNight()
|
||||||
}
|
}
|
||||||
|
|
||||||
window.decorView.post {
|
} // OnCreate
|
||||||
if (firstRun) {
|
|
||||||
if (!Utils.checkPermission(this, Manifest.permission.RECORD_AUDIO))
|
override fun onStart() {
|
||||||
Utils.requestPermission(
|
Log.i(TAG, "Main onStart")
|
||||||
this, Manifest.permission.RECORD_AUDIO, RECORD_PERMISSION_REQUEST_CODE
|
super.onStart()
|
||||||
)
|
requestPermissionLauncher =
|
||||||
firstRun = false
|
registerForActivityResult(ActivityResultContracts.RequestPermission()) {}
|
||||||
|
requestPermissionsLauncher =
|
||||||
|
registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) {
|
||||||
|
permissions ->
|
||||||
|
permissions.entries.forEach {
|
||||||
|
if (it.value)
|
||||||
|
Log.i(TAG, "Permission ${it.key} granted")
|
||||||
|
else
|
||||||
|
Log.i(TAG, "Permission ${it.key} denied")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
Log.d(TAG, "Main onResume with action '$resumeAction'")
|
||||||
|
nm.cancelAll()
|
||||||
|
BaresipService.isMainVisible = true
|
||||||
|
when (resumeAction) {
|
||||||
|
"call show" ->
|
||||||
|
handleServiceEvent("call incoming",
|
||||||
|
arrayListOf(resumeCall!!.ua.uap, resumeCall!!.callp))
|
||||||
|
"call answer" -> {
|
||||||
|
answerButton.performClick()
|
||||||
|
showCall(resumeCall!!.ua)
|
||||||
|
}
|
||||||
|
"call missed" -> {
|
||||||
|
callsButton.performClick()
|
||||||
|
}
|
||||||
|
"call reject" ->
|
||||||
|
rejectButton.performClick()
|
||||||
|
"call" -> {
|
||||||
|
callUri.setText(UserAgent.uas()[aorSpinner.selectedItemPosition].account.resumeUri)
|
||||||
|
callButton.performClick()
|
||||||
|
}
|
||||||
|
"transfer show", "transfer accept" ->
|
||||||
|
handleServiceEvent("$resumeAction,$resumeUri",
|
||||||
|
arrayListOf(resumeCall!!.ua.uap, resumeCall!!.callp))
|
||||||
|
"message", "message show", "message reply" ->
|
||||||
|
handleServiceEvent(resumeAction, arrayListOf(resumeUap, resumeUri))
|
||||||
|
else -> {
|
||||||
|
val incomingCall = Call.call("incoming")
|
||||||
|
if (incomingCall != null) {
|
||||||
|
spinToAor(incomingCall.ua.account.aor)
|
||||||
|
} else {
|
||||||
|
restoreActivities()
|
||||||
|
if (UserAgent.uas().size > 0) {
|
||||||
|
if (aorSpinner.selectedItemPosition == -1) {
|
||||||
|
if (Call.calls().size > 0)
|
||||||
|
spinToAor(Call.calls()[0].ua.account.aor)
|
||||||
|
else {
|
||||||
|
aorSpinner.setSelection(0)
|
||||||
|
aorSpinner.tag = UserAgent.uas()[0].account.aor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
uaAdapter.notifyDataSetChanged()
|
||||||
|
if (UserAgent.uas().size > 0) {
|
||||||
|
val ua = UserAgent.uas()[aorSpinner.selectedItemPosition]
|
||||||
|
showCall(ua)
|
||||||
|
updateIcons(ua.account)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
resumeAction = ""
|
||||||
|
}
|
||||||
|
|
||||||
} // OnCreate
|
override fun onPause() {
|
||||||
|
Log.d(TAG, "Main onPause")
|
||||||
|
Utils.addActivity("main")
|
||||||
|
BaresipService.isMainVisible = false
|
||||||
|
saveCallUri()
|
||||||
|
super.onPause()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStop() {
|
||||||
|
Log.d(TAG, "Main onStop")
|
||||||
|
super.onStop()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
Log.d(TAG, "Main onDestroy")
|
||||||
|
LocalBroadcastManager.getInstance(this).unregisterReceiver(serviceEventReceiver)
|
||||||
|
BaresipService.activities.clear()
|
||||||
|
super.onDestroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>,
|
||||||
|
grandResults: IntArray) {
|
||||||
|
super.onRequestPermissionsResult(requestCode, permissions, grandResults)
|
||||||
|
var allowed = true
|
||||||
|
when (requestCode) {
|
||||||
|
CALL_PERMISSION_REQUEST_CODE -> {
|
||||||
|
for (res in grandResults)
|
||||||
|
allowed = allowed && res == PackageManager.PERMISSION_GRANTED
|
||||||
|
if (allowed) {
|
||||||
|
callUri.setAdapter(null)
|
||||||
|
val ua = UserAgent.uas()[aorSpinner.selectedItemPosition]
|
||||||
|
val aor = ua.account.aor
|
||||||
|
if (Call.calls().isEmpty()) {
|
||||||
|
val uriText = callUri.text.toString().trim()
|
||||||
|
if (uriText.isNotEmpty()) {
|
||||||
|
val uri = Utils.uriComplete(
|
||||||
|
ContactsActivity.findContactURI(uriText)
|
||||||
|
.filterNot { it.isWhitespace() },
|
||||||
|
Utils.aorDomain(aor)
|
||||||
|
)
|
||||||
|
if (!Utils.checkSipUri(uri)) {
|
||||||
|
Utils.alertView(
|
||||||
|
this, getString(R.string.notice),
|
||||||
|
String.format(getString(R.string.invalid_sip_uri), uri)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
callUri.isFocusable = false
|
||||||
|
if (!call(ua, uri)) {
|
||||||
|
callButton.visibility = View.VISIBLE
|
||||||
|
callButton.isEnabled = true
|
||||||
|
hangupButton.visibility = View.INVISIBLE
|
||||||
|
hangupButton.isEnabled = false
|
||||||
|
} else {
|
||||||
|
callButton.visibility = View.INVISIBLE
|
||||||
|
callButton.isEnabled = false
|
||||||
|
hangupButton.visibility = View.VISIBLE
|
||||||
|
hangupButton.isEnabled = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val latest = CallHistory.aorLatestHistory(aor)
|
||||||
|
if (latest != null)
|
||||||
|
callUri.setText(
|
||||||
|
Utils.friendlyUri(
|
||||||
|
ContactsActivity.contactName(latest.peerUri),
|
||||||
|
Utils.aorDomain(ua.account.aor)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (ActivityCompat.shouldShowRequestPermissionRationale(
|
||||||
|
this,
|
||||||
|
Manifest.permission.RECORD_AUDIO
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
layout.showSnackBar(
|
||||||
|
binding.root,
|
||||||
|
getString(R.string.no_android_contacts),
|
||||||
|
Snackbar.LENGTH_INDEFINITE,
|
||||||
|
getString(R.string.ok)
|
||||||
|
) {
|
||||||
|
requestPermissionsLauncher.launch(permissions)
|
||||||
|
}
|
||||||
|
} else if (ActivityCompat.shouldShowRequestPermissionRationale(
|
||||||
|
this,
|
||||||
|
Manifest.permission.READ_PHONE_STATE
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
layout.showSnackBar(
|
||||||
|
binding.root,
|
||||||
|
getString(R.string.no_android_contacts),
|
||||||
|
Snackbar.LENGTH_INDEFINITE,
|
||||||
|
getString(R.string.ok)
|
||||||
|
) {
|
||||||
|
requestPermissionsLauncher.launch(permissions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onNewIntent(intent: Intent) {
|
override fun onNewIntent(intent: Intent) {
|
||||||
// Called when MainActivity already exists at the top of current task
|
// Called when MainActivity already exists at the top of current task
|
||||||
@ -790,84 +927,6 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStart() {
|
|
||||||
Log.d(TAG, "Main onStart")
|
|
||||||
super.onStart()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onResume() {
|
|
||||||
super.onResume()
|
|
||||||
Log.d(TAG, "Main onResume with action '$resumeAction'")
|
|
||||||
nm.cancelAll()
|
|
||||||
BaresipService.isMainVisible = true
|
|
||||||
when (resumeAction) {
|
|
||||||
"call show" ->
|
|
||||||
handleServiceEvent("call incoming",
|
|
||||||
arrayListOf(resumeCall!!.ua.uap, resumeCall!!.callp))
|
|
||||||
"call answer" -> {
|
|
||||||
answerButton.performClick()
|
|
||||||
showCall(resumeCall!!.ua)
|
|
||||||
}
|
|
||||||
"call missed" -> {
|
|
||||||
callsButton.performClick()
|
|
||||||
}
|
|
||||||
"call reject" ->
|
|
||||||
rejectButton.performClick()
|
|
||||||
"call" -> {
|
|
||||||
callUri.setText(UserAgent.uas()[aorSpinner.selectedItemPosition].account.resumeUri)
|
|
||||||
callButton.performClick()
|
|
||||||
}
|
|
||||||
"transfer show", "transfer accept" ->
|
|
||||||
handleServiceEvent("$resumeAction,$resumeUri",
|
|
||||||
arrayListOf(resumeCall!!.ua.uap, resumeCall!!.callp))
|
|
||||||
"message", "message show", "message reply" ->
|
|
||||||
handleServiceEvent(resumeAction, arrayListOf(resumeUap, resumeUri))
|
|
||||||
else -> {
|
|
||||||
val incomingCall = Call.call("incoming")
|
|
||||||
if (incomingCall != null) {
|
|
||||||
spinToAor(incomingCall.ua.account.aor)
|
|
||||||
} else {
|
|
||||||
restoreActivities()
|
|
||||||
if (UserAgent.uas().size > 0) {
|
|
||||||
if (aorSpinner.selectedItemPosition == -1) {
|
|
||||||
if (Call.calls().size > 0)
|
|
||||||
spinToAor(Call.calls()[0].ua.account.aor)
|
|
||||||
else {
|
|
||||||
aorSpinner.setSelection(0)
|
|
||||||
aorSpinner.tag = UserAgent.uas()[0].account.aor
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
uaAdapter.notifyDataSetChanged()
|
|
||||||
if (UserAgent.uas().size > 0) {
|
|
||||||
val ua = UserAgent.uas()[aorSpinner.selectedItemPosition]
|
|
||||||
showCall(ua)
|
|
||||||
updateIcons(ua.account)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
resumeAction = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onPause() {
|
|
||||||
Log.d(TAG, "Main onPause")
|
|
||||||
Utils.addActivity("main")
|
|
||||||
BaresipService.isMainVisible = false
|
|
||||||
saveCallUri()
|
|
||||||
super.onPause()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onStop() {
|
|
||||||
Log.d(TAG, "Main onStop")
|
|
||||||
super.onStop()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun recreate() {
|
|
||||||
Log.d(TAG, "Main onCreate")
|
|
||||||
super.recreate()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
|
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
|
||||||
val stream = if (am.mode == AudioManager.MODE_RINGTONE)
|
val stream = if (am.mode == AudioManager.MODE_RINGTONE)
|
||||||
AudioManager.STREAM_RING
|
AudioManager.STREAM_RING
|
||||||
@ -971,10 +1030,6 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
"call incoming" -> {
|
"call incoming" -> {
|
||||||
val callp = params[1]
|
val callp = params[1]
|
||||||
if (!Utils.checkPermission(this, Manifest.permission.RECORD_AUDIO)) {
|
|
||||||
Api.ua_hangup(uap, callp, 486, "Busy Here")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (BaresipService.isMainVisible) {
|
if (BaresipService.isMainVisible) {
|
||||||
if (aor != aorSpinner.tag)
|
if (aor != aorSpinner.tag)
|
||||||
spinToAor(aor)
|
spinToAor(aor)
|
||||||
@ -1173,13 +1228,6 @@ class MainActivity : AppCompatActivity() {
|
|||||||
moveTaskToBack(true)
|
moveTaskToBack(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
|
||||||
Log.d(TAG, "Main onDestroy")
|
|
||||||
LocalBroadcastManager.getInstance(this).unregisterReceiver(serviceEventReceiver)
|
|
||||||
BaresipService.activities.clear()
|
|
||||||
super.onDestroy()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||||
menuInflater.inflate(R.menu.main_menu, menu)
|
menuInflater.inflate(R.menu.main_menu, menu)
|
||||||
menuInflater.inflate(R.menu.speaker_icon, menu)
|
menuInflater.inflate(R.menu.speaker_icon, menu)
|
||||||
@ -1219,24 +1267,59 @@ class MainActivity : AppCompatActivity() {
|
|||||||
if (Build.VERSION.SDK_INT >= 29) {
|
if (Build.VERSION.SDK_INT >= 29) {
|
||||||
pickupFileFromDownloads("backup")
|
pickupFileFromDownloads("backup")
|
||||||
} else {
|
} else {
|
||||||
if (Utils.requestPermission(
|
if (ContextCompat.checkSelfPermission(
|
||||||
this,
|
this,
|
||||||
Manifest.permission.WRITE_EXTERNAL_STORAGE,
|
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||||
BACKUP_PERMISSION_REQUEST_CODE
|
) == PackageManager.PERMISSION_GRANTED) {
|
||||||
)
|
Log.d(TAG, "Write External Storage permission granted")
|
||||||
) {
|
|
||||||
val path = Utils.downloadsPath("baresip.bs")
|
val path = Utils.downloadsPath("baresip.bs")
|
||||||
downloadsOutputStream = FileOutputStream(File(path))
|
downloadsOutputStream = FileOutputStream(File(path))
|
||||||
askPassword(getString(R.string.encrypt_password))
|
askPassword(getString(R.string.encrypt_password))
|
||||||
|
} else if (ActivityCompat.shouldShowRequestPermissionRationale(
|
||||||
|
this,
|
||||||
|
Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
|
||||||
|
layout.showSnackBar(
|
||||||
|
binding.root,
|
||||||
|
getString(R.string.no_backup),
|
||||||
|
Snackbar.LENGTH_INDEFINITE,
|
||||||
|
getString(R.string.ok)
|
||||||
|
) {
|
||||||
|
requestPermissionLauncher.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
requestPermissionLauncher.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.restore -> {
|
R.id.restore -> {
|
||||||
if (Build.VERSION.SDK_INT >= 29 ||
|
if (Build.VERSION.SDK_INT >= 29) {
|
||||||
Utils.requestPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE,
|
pickupFileFromDownloads("restore")
|
||||||
RESTORE_PERMISSION_REQUEST_CODE))
|
} else {
|
||||||
startRestore()
|
if (ContextCompat.checkSelfPermission(
|
||||||
|
this,
|
||||||
|
Manifest.permission.READ_EXTERNAL_STORAGE
|
||||||
|
) == PackageManager.PERMISSION_GRANTED) {
|
||||||
|
Log.d(TAG, "Read External Storage permission granted")
|
||||||
|
val path = Utils.downloadsPath("baresip.bs")
|
||||||
|
downloadsInputStream = FileInputStream(File(path))
|
||||||
|
askPassword(getString(R.string.decrypt_password))
|
||||||
|
} else if (ActivityCompat.shouldShowRequestPermissionRationale(
|
||||||
|
this,
|
||||||
|
Manifest.permission.READ_EXTERNAL_STORAGE)) {
|
||||||
|
layout.showSnackBar(
|
||||||
|
binding.root,
|
||||||
|
getString(R.string.no_restore),
|
||||||
|
Snackbar.LENGTH_INDEFINITE,
|
||||||
|
getString(R.string.ok)
|
||||||
|
) {
|
||||||
|
requestPermissionLauncher.launch(Manifest.permission.READ_EXTERNAL_STORAGE)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
requestPermissionLauncher.launch(Manifest.permission.READ_EXTERNAL_STORAGE)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.about -> {
|
R.id.about -> {
|
||||||
@ -1250,38 +1333,6 @@ class MainActivity : AppCompatActivity() {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>,
|
|
||||||
grantResults: IntArray) {
|
|
||||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
|
||||||
|
|
||||||
when (requestCode) {
|
|
||||||
|
|
||||||
RECORD_PERMISSION_REQUEST_CODE ->
|
|
||||||
if ((grantResults.isNotEmpty()) && (grantResults[0] != PackageManager.PERMISSION_GRANTED))
|
|
||||||
Utils.alertView(this, getString(R.string.notice),
|
|
||||||
getString(R.string.no_calls))
|
|
||||||
|
|
||||||
BACKUP_PERMISSION_REQUEST_CODE ->
|
|
||||||
if ((grantResults.isNotEmpty()) && (grantResults[0] == PackageManager.PERMISSION_GRANTED))
|
|
||||||
askPassword(getString(R.string.encrypt_password))
|
|
||||||
|
|
||||||
RESTORE_PERMISSION_REQUEST_CODE ->
|
|
||||||
if ((grantResults.isNotEmpty()) && (grantResults[0] == PackageManager.PERMISSION_GRANTED))
|
|
||||||
startRestore()
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun startRestore() {
|
|
||||||
if (Build.VERSION.SDK_INT >= 29) {
|
|
||||||
pickupFileFromDownloads("restore")
|
|
||||||
} else {
|
|
||||||
val path = Utils.downloadsPath("baresip.bs")
|
|
||||||
downloadsInputStream = FileInputStream(File(path))
|
|
||||||
askPassword(getString(R.string.decrypt_password))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@RequiresApi(29)
|
@RequiresApi(29)
|
||||||
private fun pickupFileFromDownloads(action: String) {
|
private fun pickupFileFromDownloads(action: String) {
|
||||||
when (action) {
|
when (action) {
|
||||||
@ -1559,11 +1610,6 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun call(ua: UserAgent, uri: String): Boolean {
|
private fun call(ua: UserAgent, uri: String): Boolean {
|
||||||
if (!Utils.checkPermission(this, Manifest.permission.RECORD_AUDIO)) {
|
|
||||||
Toast.makeText(applicationContext, getString(R.string.no_calls),
|
|
||||||
Toast.LENGTH_LONG).show()
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if (ua.account.aor != aorSpinner.tag)
|
if (ua.account.aor != aorSpinner.tag)
|
||||||
spinToAor(ua.account.aor)
|
spinToAor(ua.account.aor)
|
||||||
val callp = Api.ua_connect(ua.uap, uri, Api.VIDMODE_OFF)
|
val callp = Api.ua_connect(ua.uap, uri, Api.VIDMODE_OFF)
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
package com.tutpro.baresip
|
package com.tutpro.baresip
|
||||||
|
|
||||||
import android.app.Activity
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
@ -22,10 +21,10 @@ import android.widget.TextView
|
|||||||
import androidx.activity.result.ActivityResultLauncher
|
import androidx.activity.result.ActivityResultLauncher
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.core.app.ActivityCompat
|
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.ProcessLifecycleOwner
|
import androidx.lifecycle.ProcessLifecycleOwner
|
||||||
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import java.io.*
|
import java.io.*
|
||||||
import java.lang.reflect.Method
|
import java.lang.reflect.Method
|
||||||
import java.security.SecureRandom
|
import java.security.SecureRandom
|
||||||
@ -320,8 +319,9 @@ object Utils {
|
|||||||
val addresses: Enumeration<InetAddress> = iface.inetAddresses
|
val addresses: Enumeration<InetAddress> = iface.inetAddresses
|
||||||
while (addresses.hasMoreElements()) {
|
while (addresses.hasMoreElements()) {
|
||||||
val inetAddress: InetAddress = addresses.nextElement()
|
val inetAddress: InetAddress = addresses.nextElement()
|
||||||
if (!inetAddress.isLoopbackAddress && !inetAddress.isLinkLocalAddress)
|
if (inetAddress.hostAddress != null && !inetAddress.isLoopbackAddress &&
|
||||||
result[inetAddress.hostAddress] = ifName
|
!inetAddress.isLinkLocalAddress)
|
||||||
|
result[inetAddress.hostAddress!!] = ifName
|
||||||
}
|
}
|
||||||
if (result.isNotEmpty()) return result
|
if (result.isNotEmpty()) return result
|
||||||
}
|
}
|
||||||
@ -356,22 +356,33 @@ object Utils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun checkPermission(ctx: Context, permissions: String) : Boolean {
|
fun checkPermissions(ctx: Context, permissions: Array<String>) : Boolean {
|
||||||
for (p in permissions.split("|")) {
|
for (p in permissions) {
|
||||||
if (ContextCompat.checkSelfPermission(ctx, p) != PackageManager.PERMISSION_GRANTED)
|
if (ContextCompat.checkSelfPermission(ctx, p) != PackageManager.PERMISSION_GRANTED) {
|
||||||
|
Log.d(TAG, "Permission $p is not granted")
|
||||||
return false
|
return false
|
||||||
|
} else {
|
||||||
|
Log.d(TAG, "Permission $p is granted")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
fun requestPermission(ctx: Context, permissions: String, requestCode: Int) : Boolean {
|
fun View.showSnackBar(
|
||||||
val pArray = permissions.split("|").toTypedArray()
|
view: View,
|
||||||
for (p in pArray)
|
msg: String,
|
||||||
if (ContextCompat.checkSelfPermission(ctx, p) != PackageManager.PERMISSION_GRANTED) {
|
length: Int,
|
||||||
ActivityCompat.requestPermissions(ctx as Activity, pArray, requestCode)
|
actionMessage: CharSequence?,
|
||||||
return false
|
action: (View) -> Unit
|
||||||
}
|
) {
|
||||||
return true
|
val snackBar = Snackbar.make(view, msg, length)
|
||||||
|
if (actionMessage != null) {
|
||||||
|
snackBar.setAction(actionMessage) {
|
||||||
|
action(this)
|
||||||
|
}.show()
|
||||||
|
} else {
|
||||||
|
snackBar.show()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun copyAssetToFile(context: Context, asset: String, path: String) {
|
fun copyAssetToFile(context: Context, asset: String, path: String) {
|
||||||
|
|||||||
@ -447,8 +447,13 @@
|
|||||||
<string name="restore_failed">Failed to restore application data. Check that you gave correct
|
<string name="restore_failed">Failed to restore application data. Check that you gave correct
|
||||||
password. In Android versions 9 and below, also check Apps → baresip → Permissions → Storage
|
password. In Android versions 9 and below, also check Apps → baresip → Permissions → Storage
|
||||||
and that file \'%1$s\' exists in Download folder.</string>
|
and that file \'%1$s\' exists in Download folder.</string>
|
||||||
<string name="no_calls">You are not able to place or answer calls without Microphone permission.</string>
|
<string name="no_calls">You are not able to make or answer calls without Microphone and
|
||||||
<string name="no_video_calls">Grant \"Camera\" permission to place or answer video calls.</string>
|
Telephone permissions.</string>
|
||||||
|
<string name="no_backup">You are not able create backup without Storage permission.</string>
|
||||||
|
<string name="no_android_contacts">You are not able add or remove Android contacts without
|
||||||
|
Contacts permission.</string>
|
||||||
|
<string name="no_restore">You are not able restore backup without Storage permission.</string>
|
||||||
|
<string name="no_video_calls">Grant \"Camera\" permission to make or answer video calls.</string>
|
||||||
<string name="no_cameras">You don\'t have any supported video cameras.</string>
|
<string name="no_cameras">You don\'t have any supported video cameras.</string>
|
||||||
<string name="show_password">Show Password</string>
|
<string name="show_password">Show Password</string>
|
||||||
<string name="no_network">No network connection!</string>
|
<string name="no_network">No network connection!</string>
|
||||||
|
|||||||
Reference in New Issue
Block a user