Started to work on better permissions handling

This commit is contained in:
Juha Heinanen
2021-11-09 15:15:46 +02:00
parent b79d10770c
commit 801c232793
10 changed files with 467 additions and 314 deletions

View File

@ -57,4 +57,6 @@ dependencies {
implementation "androidx.core:core-ktx:1.7.0"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1'
implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
implementation "androidx.activity:activity-ktx:1.4.0"
implementation "androidx.fragment:fragment-ktx:1.3.6"
}

View File

@ -41,6 +41,7 @@
android:roundIcon="@mipmap/ic_launcher"
android:supportsRtl="true"
android:theme="@style/AppTheme"
tools:remove="android:appComponentFactory"
tools:targetApi="q">
<activity
android:name=".MainActivity"

View File

@ -21,6 +21,7 @@ import android.view.View
import android.widget.RemoteViews
import android.content.Intent
import android.content.BroadcastReceiver
import android.content.pm.PackageManager
import androidx.annotation.ColorRes
import androidx.annotation.Keep
import androidx.annotation.StringRes
@ -36,6 +37,7 @@ import kotlin.concurrent.schedule
import kotlin.math.roundToInt
import android.media.MediaPlayer
import android.telecom.TelecomManager
import androidx.core.app.ActivityCompat
class BaresipService: Service() {
@ -576,20 +578,23 @@ class BaresipService: Service() {
"call incoming" -> {
val peerUri = Api.call_peeruri(callp)
if (Call.calls().size > 0 ||
(Utils.checkPermission(this, Manifest.permission.READ_PHONE_STATE) &&
tm.isInCall) ||
!Utils.checkPermission(applicationContext, Manifest.permission.RECORD_AUDIO)) {
Log.d(TAG, "Auto-rejecting incoming call $uap/$callp/$peerUri")
Api.ua_hangup(uap, callp, 486, "Busy Here")
if (ua.account.callHistory) {
CallHistory.add(CallHistory(aor, peerUri, "in", false))
CallHistory.save()
ua.account.missedCalls = true
}
playUnInterrupted(R.raw.callwaiting, 1)
if (!Utils.isVisible())
return
newEvent = "call rejected"
!Utils.checkPermissions(this, arrayOf(Manifest.permission.RECORD_AUDIO)) ||
ActivityCompat.checkSelfPermission(
this,
Manifest.permission.READ_PHONE_STATE
) != PackageManager.PERMISSION_GRANTED ||
tm.isInCall) {
Log.d(TAG, "Auto-rejecting incoming call $uap/$callp/$peerUri")
Api.ua_hangup(uap, callp, 486, "Busy Here")
if (ua.account.callHistory) {
CallHistory.add(CallHistory(aor, peerUri, "in", false))
CallHistory.save()
ua.account.missedCalls = true
}
playUnInterrupted(R.raw.callwaiting, 1)
if (!Utils.isVisible())
return
newEvent = "call rejected"
} else {
Log.d(TAG, "Incoming call $uap/$callp/$peerUri")
Call(callp, ua, peerUri, "in", "incoming",

View File

@ -12,10 +12,15 @@ import android.view.MenuItem
import android.view.View
import android.widget.*
import android.widget.AdapterView
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AlertDialog
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.showSnackBar
import com.tutpro.baresip.databinding.ActivityConfigBinding
import java.io.File
import java.io.FileInputStream
@ -24,6 +29,7 @@ import java.util.*
class ConfigActivity : AppCompatActivity() {
private lateinit var binding: ActivityConfigBinding
private lateinit var layout: ScrollView
private lateinit var autoStart: CheckBox
private lateinit var listenAddr: EditText
private lateinit var dnsServers: EditText
@ -34,6 +40,7 @@ class ConfigActivity : AppCompatActivity() {
private lateinit var debug: CheckBox
private lateinit var sipTrace: CheckBox
private lateinit var reset: CheckBox
private lateinit var requestPermissionLauncher: ActivityResultLauncher<String>
private var oldAutoStart = ""
private var oldListenAddr = ""
@ -54,6 +61,7 @@ class ConfigActivity : AppCompatActivity() {
binding = ActivityConfigBinding.inflate(layoutInflater)
setContentView(binding.root)
layout = binding.ConfigView
Utils.addActivity("config")
@ -88,8 +96,9 @@ class ConfigActivity : AppCompatActivity() {
if (it.resultCode == RESULT_OK) {
it.data?.data?.also { uri ->
try {
val inputStream = applicationContext.contentResolver.openInputStream(uri)
as FileInputStream
val inputStream =
applicationContext.contentResolver.openInputStream(uri)
as FileInputStream
File(BaresipService.filesPath + "/cert.pem")
.copyInputStreamToFile(inputStream)
inputStream.close()
@ -113,26 +122,45 @@ class ConfigActivity : AppCompatActivity() {
certificateFile.setOnCheckedChangeListener { _, isChecked ->
if (isChecked) {
if (Build.VERSION.SDK_INT < 29) {
if (!Utils.requestPermission(this,
Manifest.permission.READ_EXTERNAL_STORAGE,
READ_CERT_PERMISSION_CODE)) {
certificateFile.isChecked = false
return@setOnCheckedChangeListener
certificateFile.isChecked = false
if (ContextCompat.checkSelfPermission(
this,
Manifest.permission.READ_EXTERNAL_STORAGE
) == 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 {
Utils.selectInputFile(certificateRequest)
}
@ -157,8 +185,9 @@ class ConfigActivity : AppCompatActivity() {
if (it.resultCode == Activity.RESULT_OK)
it.data?.data?.also { uri ->
try {
val inputStream = applicationContext.contentResolver.openInputStream(uri)
as FileInputStream
val inputStream =
applicationContext.contentResolver.openInputStream(uri)
as FileInputStream
File(BaresipService.filesPath + "/ca_certs.crt")
.copyInputStreamToFile(inputStream)
inputStream.close()
@ -167,8 +196,10 @@ class ConfigActivity : AppCompatActivity() {
save = true
restart = true
} catch (e: Error) {
Utils.alertView(this, getString(R.string.error),
getString(R.string.read_ca_certs_error))
Utils.alertView(
this, getString(R.string.error),
getString(R.string.read_ca_certs_error)
)
caFile.isChecked = false
}
}
@ -179,26 +210,45 @@ class ConfigActivity : AppCompatActivity() {
caFile.setOnCheckedChangeListener { _, isChecked ->
if (isChecked) {
if (Build.VERSION.SDK_INT < 29) {
if (!Utils.requestPermission(this,
Manifest.permission.READ_EXTERNAL_STORAGE,
READ_CERT_PERMISSION_CODE)) {
caFile.isChecked = false
return@setOnCheckedChangeListener
caFile.isChecked = false
if (ContextCompat.checkSelfPermission(
this,
Manifest.permission.READ_EXTERNAL_STORAGE
) == 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 {
Utils.selectInputFile(certificatesRequest)
}
@ -218,14 +268,22 @@ class ConfigActivity : AppCompatActivity() {
volVals.removeAt(curVal)
volKeys.add(0, curKey)
volVals.add(0, curVal)
val callVolAdapter = ArrayAdapter(this, android.R.layout.simple_spinner_item,
volKeys)
val callVolAdapter = ArrayAdapter(
this, android.R.layout.simple_spinner_item,
volKeys
)
callVolAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
callVolSpinner.adapter = callVolAdapter
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())]
}
override fun onNothingSelected(parent: AdapterView<*>) {
}
}
@ -240,7 +298,7 @@ class ConfigActivity : AppCompatActivity() {
"2"
else
dbCv[0]
debug.isChecked = oldLogLevel == "0"
debug.isChecked = oldLogLevel == "0"
sipTrace = binding.SipTrace
sipTrace.isChecked = BaresipService.sipTrace
@ -252,7 +310,7 @@ class ConfigActivity : AppCompatActivity() {
if (isChecked) {
val titleView = View.inflate(this, R.layout.alert_title, null) as TextView
titleView.text = getString(R.string.confirmation)
with (AlertDialog.Builder(this@ConfigActivity)) {
with(AlertDialog.Builder(this@ConfigActivity)) {
setCustomTitle(titleView)
setMessage(getString(R.string.reset_config_alert))
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 {
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() {
BaresipService.activities.remove("config")

View File

@ -7,10 +7,11 @@ const val HIGH_CHANNEL_ID = "com.tutpro.baresip.high"
const val BACKUP_PERMISSION_REQUEST_CODE = 1
const val RESTORE_PERMISSION_REQUEST_CODE = 2
const val RECORD_PERMISSION_REQUEST_CODE = 3
const val CONTACTS_PERMISSION_REQUEST_CODE = 4
const val CALL_PERMISSION_REQUEST_CODE = 3
const val CONTACT_PERMISSION_REQUEST_CODE = 4
const val READ_CERT_PERMISSION_CODE = 5
const val READ_CA_PERMISSION_CODE = 6
const val REQUEST_PERMISSIONS_CODE = 7
const val STATUS_NOTIFICATION_ID = 101
const val CALL_NOTIFICATION_ID = 102

View File

@ -12,6 +12,7 @@ import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Matrix
import android.graphics.drawable.BitmapDrawable
import android.os.Build
import android.os.Bundle
import android.provider.ContactsContract
import android.provider.ContactsContract.CommonDataKinds
@ -20,10 +21,14 @@ import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.widget.*
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.cardview.widget.CardView
import androidx.core.app.ActivityCompat
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 java.io.ByteArrayOutputStream
import java.io.File
@ -31,6 +36,7 @@ import java.io.File
class ContactActivity : AppCompatActivity() {
private lateinit var binding: ActivityContactBinding
private lateinit var layout: LinearLayout
private lateinit var textAvatarView: TextView
private lateinit var cardAvatarView: CardView
private lateinit var cardImageAvatarView: ImageView
@ -38,6 +44,7 @@ class ContactActivity : AppCompatActivity() {
private lateinit var uriView: EditText
private lateinit var androidCheck: CheckBox
private lateinit var menu: Menu
private lateinit var requestPermissionsLauncher: ActivityResultLauncher<Array<String>>
private var newContact = false
private var newAvatar = ""
@ -48,11 +55,15 @@ class ContactActivity : AppCompatActivity() {
private var id: Long = 0
private var oldAndroid = false
private val permissions =
arrayOf(Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityContactBinding.inflate(layoutInflater)
setContentView(binding.root)
layout = binding.ContactView
textAvatarView = binding.TextAvatar
cardAvatarView = binding.CardAvatar
@ -166,10 +177,22 @@ class ContactActivity : AppCompatActivity() {
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")
}
override fun onStart() {
super.onStart()
requestPermissionsLauncher =
registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) {}
}
override fun onCreateOptionsMenu(optionsMenu: Menu): Boolean {
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 {
if (BaresipService.activities.indexOf("contact,$newContact,$uOrI") == -1) return true
@ -260,20 +324,13 @@ class ContactActivity : AppCompatActivity() {
Contact.contacts().sortBy { Contact -> Contact.name }
if (Utils.checkPermission(this, Manifest.permission.READ_CONTACTS +
"|" + Manifest.permission.WRITE_CONTACTS)) {
if (Utils.checkPermissions(this, permissions)) {
if (contact.androidContact)
addOrUpdateAndroidContact(this, contact)
else
if (oldAndroid)
deleteAndroidContact(this, contact)
else if (oldAndroid)
deleteAndroidContact(this, contact)
} else {
if (contact.androidContact) {
Utils.requestPermission(this, Manifest.permission.READ_CONTACTS +
"|" + Manifest.permission.WRITE_CONTACTS,
CONTACTS_PERMISSION_REQUEST_CODE)
return false
}
contact.androidContact = oldAndroid
}
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() {
BaresipService.activities.remove("contact,$newContact,$uOrI")

View File

@ -128,7 +128,7 @@ class ContactListAdapter(private val ctx: Context, private val rows: ArrayList<C
}
}
if (contact.androidContact &&
Utils.checkPermission(ctx, Manifest.permission.WRITE_CONTACTS))
Utils.checkPermissions(ctx, arrayOf(Manifest.permission.WRITE_CONTACTS)))
ContactActivity.deleteAndroidContact(ctx, contact)
Contact.contacts().removeAt(position)
Contact.save()

View File

@ -29,8 +29,12 @@ import androidx.annotation.RequiresApi
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.localbroadcastmanager.content.LocalBroadcastManager
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 java.io.File
import java.io.FileInputStream
@ -72,6 +76,8 @@ class MainActivity : AppCompatActivity() {
private lateinit var stopState: String
private var speakerIcon: MenuItem? = null
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 chatRequests: ActivityResultLauncher<Intent>
@ -304,44 +310,10 @@ class MainActivity : AppCompatActivity() {
callButton.setOnClickListener {
if (aorSpinner.selectedItemPosition == -1)
return@setOnClickListener
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)
)
)
}
}
val permissions = arrayOf(Manifest.permission.RECORD_AUDIO, Manifest.permission.READ_PHONE_STATE)
if (Build.VERSION.SDK_INT >= 23)
if (!Utils.checkPermissions(this, permissions))
requestPermissions(permissions, CALL_PERMISSION_REQUEST_CODE)
}
hangupButton.setOnClickListener {
@ -656,17 +628,182 @@ class MainActivity : AppCompatActivity() {
delegate.applyDayNight()
}
window.decorView.post {
if (firstRun) {
if (!Utils.checkPermission(this, Manifest.permission.RECORD_AUDIO))
Utils.requestPermission(
this, Manifest.permission.RECORD_AUDIO, RECORD_PERMISSION_REQUEST_CODE
)
firstRun = false
} // OnCreate
override fun onStart() {
Log.i(TAG, "Main onStart")
super.onStart()
requestPermissionLauncher =
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) {
// 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 {
val stream = if (am.mode == AudioManager.MODE_RINGTONE)
AudioManager.STREAM_RING
@ -971,10 +1030,6 @@ class MainActivity : AppCompatActivity() {
}
"call incoming" -> {
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 (aor != aorSpinner.tag)
spinToAor(aor)
@ -1173,13 +1228,6 @@ class MainActivity : AppCompatActivity() {
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 {
menuInflater.inflate(R.menu.main_menu, menu)
menuInflater.inflate(R.menu.speaker_icon, menu)
@ -1219,24 +1267,59 @@ class MainActivity : AppCompatActivity() {
if (Build.VERSION.SDK_INT >= 29) {
pickupFileFromDownloads("backup")
} else {
if (Utils.requestPermission(
if (ContextCompat.checkSelfPermission(
this,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
BACKUP_PERMISSION_REQUEST_CODE
)
) {
Manifest.permission.WRITE_EXTERNAL_STORAGE
) == PackageManager.PERMISSION_GRANTED) {
Log.d(TAG, "Write External Storage permission granted")
val path = Utils.downloadsPath("baresip.bs")
downloadsOutputStream = FileOutputStream(File(path))
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 -> {
if (Build.VERSION.SDK_INT >= 29 ||
Utils.requestPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE,
RESTORE_PERMISSION_REQUEST_CODE))
startRestore()
if (Build.VERSION.SDK_INT >= 29) {
pickupFileFromDownloads("restore")
} else {
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 -> {
@ -1250,38 +1333,6 @@ class MainActivity : AppCompatActivity() {
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)
private fun pickupFileFromDownloads(action: String) {
when (action) {
@ -1559,11 +1610,6 @@ class MainActivity : AppCompatActivity() {
}
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)
spinToAor(ua.account.aor)
val callp = Api.ua_connect(ua.uap, uri, Api.VIDMODE_OFF)

View File

@ -1,6 +1,5 @@
package com.tutpro.baresip
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
@ -22,10 +21,10 @@ import android.widget.TextView
import androidx.activity.result.ActivityResultLauncher
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AlertDialog
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.ProcessLifecycleOwner
import com.google.android.material.snackbar.Snackbar
import java.io.*
import java.lang.reflect.Method
import java.security.SecureRandom
@ -320,8 +319,9 @@ object Utils {
val addresses: Enumeration<InetAddress> = iface.inetAddresses
while (addresses.hasMoreElements()) {
val inetAddress: InetAddress = addresses.nextElement()
if (!inetAddress.isLoopbackAddress && !inetAddress.isLinkLocalAddress)
result[inetAddress.hostAddress] = ifName
if (inetAddress.hostAddress != null && !inetAddress.isLoopbackAddress &&
!inetAddress.isLinkLocalAddress)
result[inetAddress.hostAddress!!] = ifName
}
if (result.isNotEmpty()) return result
}
@ -356,22 +356,33 @@ object Utils {
}
}
fun checkPermission(ctx: Context, permissions: String) : Boolean {
for (p in permissions.split("|")) {
if (ContextCompat.checkSelfPermission(ctx, p) != PackageManager.PERMISSION_GRANTED)
fun checkPermissions(ctx: Context, permissions: Array<String>) : Boolean {
for (p in permissions) {
if (ContextCompat.checkSelfPermission(ctx, p) != PackageManager.PERMISSION_GRANTED) {
Log.d(TAG, "Permission $p is not granted")
return false
} else {
Log.d(TAG, "Permission $p is granted")
}
}
return true
}
fun requestPermission(ctx: Context, permissions: String, requestCode: Int) : Boolean {
val pArray = permissions.split("|").toTypedArray()
for (p in pArray)
if (ContextCompat.checkSelfPermission(ctx, p) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(ctx as Activity, pArray, requestCode)
return false
}
return true
fun View.showSnackBar(
view: View,
msg: String,
length: Int,
actionMessage: CharSequence?,
action: (View) -> Unit
) {
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) {

View File

@ -447,8 +447,13 @@
<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
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_video_calls">Grant \"Camera\" permission to place or answer video calls.</string>
<string name="no_calls">You are not able to make or answer calls without Microphone and
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="show_password">Show Password</string>
<string name="no_network">No network connection!</string>