From 801c2327933347f4822f4ab5dcfadcaec252af43 Mon Sep 17 00:00:00 2001 From: Juha Heinanen Date: Tue, 9 Nov 2021 15:15:46 +0200 Subject: [PATCH] Started to work on better permissions handling --- app/build.gradle | 2 + app/src/main/AndroidManifest.xml | 1 + .../com/tutpro/baresip/BaresipService.kt | 33 +- .../com/tutpro/baresip/ConfigActivity.kt | 184 +++++--- .../kotlin/com/tutpro/baresip/Constants.kt | 5 +- .../com/tutpro/baresip/ContactActivity.kt | 96 +++-- .../com/tutpro/baresip/ContactListAdapter.kt | 2 +- .../kotlin/com/tutpro/baresip/MainActivity.kt | 408 ++++++++++-------- .../main/kotlin/com/tutpro/baresip/Utils.kt | 41 +- app/src/main/res/values/strings.xml | 9 +- 10 files changed, 467 insertions(+), 314 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index d54446df..9321f1c6 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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" } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index dfb7aa91..39742349 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -41,6 +41,7 @@ android:roundIcon="@mipmap/ic_launcher" android:supportsRtl="true" android:theme="@style/AppTheme" + tools:remove="android:appComponentFactory" tools:targetApi="q"> { 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", diff --git a/app/src/main/kotlin/com/tutpro/baresip/ConfigActivity.kt b/app/src/main/kotlin/com/tutpro/baresip/ConfigActivity.kt index 0c1f0c0e..fd22e672 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/ConfigActivity.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/ConfigActivity.kt @@ -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 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, - 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") diff --git a/app/src/main/kotlin/com/tutpro/baresip/Constants.kt b/app/src/main/kotlin/com/tutpro/baresip/Constants.kt index 3577ebe3..679d12b5 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/Constants.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/Constants.kt @@ -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 diff --git a/app/src/main/kotlin/com/tutpro/baresip/ContactActivity.kt b/app/src/main/kotlin/com/tutpro/baresip/ContactActivity.kt index f7268204..05b8a711 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/ContactActivity.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/ContactActivity.kt @@ -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> 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, + 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, - 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") diff --git a/app/src/main/kotlin/com/tutpro/baresip/ContactListAdapter.kt b/app/src/main/kotlin/com/tutpro/baresip/ContactListAdapter.kt index 2891af82..d93497b1 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/ContactListAdapter.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/ContactListAdapter.kt @@ -128,7 +128,7 @@ class ContactListAdapter(private val ctx: Context, private val rows: ArrayList + private lateinit var requestPermissionsLauncher: ActivityResultLauncher> private lateinit var accountsRequest: ActivityResultLauncher private lateinit var chatRequests: ActivityResultLauncher @@ -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, + 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, - 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) diff --git a/app/src/main/kotlin/com/tutpro/baresip/Utils.kt b/app/src/main/kotlin/com/tutpro/baresip/Utils.kt index 89bd6acc..bf03077e 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/Utils.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/Utils.kt @@ -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 = 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) : 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) { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e0c9f114..964e3143 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -447,8 +447,13 @@ 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. - You are not able to place or answer calls without Microphone permission. - Grant \"Camera\" permission to place or answer video calls. + You are not able to make or answer calls without Microphone and + Telephone permissions. + You are not able create backup without Storage permission. + You are not able add or remove Android contacts without + Contacts permission. + You are not able restore backup without Storage permission. + Grant \"Camera\" permission to make or answer video calls. You don\'t have any supported video cameras. Show Password No network connection!