diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 176bd86c..f62d3ba9 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,6 +1,6 @@ + xmlns:tools="http://schemas.android.com/tools" > @@ -15,8 +15,7 @@ - @@ -44,6 +43,7 @@ android:supportsRtl="true" android:theme="@style/AppTheme" tools:remove="android:appComponentFactory" > + + + + + + @@ -63,26 +68,29 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + tools:ignore="ExportedReceiver" > @@ -173,4 +209,4 @@ - + \ No newline at end of file diff --git a/app/src/main/kotlin/com/tutpro/baresip/ConfigActivity.kt b/app/src/main/kotlin/com/tutpro/baresip/ConfigActivity.kt index 8176cfb7..af2e345f 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/ConfigActivity.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/ConfigActivity.kt @@ -2,6 +2,7 @@ package com.tutpro.baresip import android.Manifest import android.app.Activity +import android.app.role.RoleManager import android.content.ActivityNotFoundException import android.content.Context import android.content.Intent @@ -21,6 +22,7 @@ import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatDelegate import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat +import androidx.transition.Visibility import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.snackbar.Snackbar import com.tutpro.baresip.Utils.copyInputStreamToFile @@ -36,7 +38,6 @@ class ConfigActivity : AppCompatActivity() { private lateinit var layout: ScrollView private lateinit var baresipService: Intent private lateinit var autoStart: CheckBox - private lateinit var batteryOptimizations: CheckBox private lateinit var listenAddr: EditText private lateinit var netAfSpinner: Spinner private lateinit var netAf: String @@ -49,6 +50,8 @@ class ConfigActivity : AppCompatActivity() { private lateinit var contactsSpinner: Spinner private lateinit var contactsMode: String private lateinit var contactsModeKeys: ArrayList + private lateinit var batteryOptimizations: CheckBox + private lateinit var defaultDialer: CheckBox private lateinit var debug: CheckBox private lateinit var sipTrace: CheckBox private lateinit var reset: CheckBox @@ -74,6 +77,7 @@ class ConfigActivity : AppCompatActivity() { goBack() } } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -109,6 +113,37 @@ class ConfigActivity : AppCompatActivity() { } } + if (Build.VERSION.SDK_INT >= 29) { + val roleManager = getSystemService(ROLE_SERVICE) as RoleManager + defaultDialer = binding.DefaultPhoneApp + defaultDialer.isChecked = roleManager.isRoleHeld(RoleManager.ROLE_DIALER) + val dialerRoleRequest = registerForActivityResult( + ActivityResultContracts.StartActivityForResult() + ) { + Log.d(TAG, "dialerRoleRequest succeeded: ${it.resultCode == Activity.RESULT_OK}") + defaultDialer.isChecked = roleManager.isRoleHeld(RoleManager.ROLE_DIALER) + } + defaultDialer.setOnCheckedChangeListener { _, isChecked -> + if (isChecked) { + if (!roleManager.isRoleAvailable(RoleManager.ROLE_DIALER)) + Utils.alertView(this, getString(R.string.notice), + getString(R.string.dialer_role_not_available)) + else + if (!roleManager.isRoleHeld(RoleManager.ROLE_DIALER)) + dialerRoleRequest.launch(roleManager.createRequestRoleIntent(RoleManager.ROLE_DIALER)) + } else { + try { + dialerRoleRequest.launch(Intent("android.settings.MANAGE_DEFAULT_APPS_SETTINGS")) + } catch (e: ActivityNotFoundException) { + Log.e(TAG, "ActivityNotFound exception: $e") + + } + } + } + } else { + binding.PhoneApp.visibility = View.GONE + } + listenAddr = binding.ListenAddress val laCv = Config.variable("sip_listen") oldListenAddr = if (laCv.size == 0) "" else laCv[0] @@ -642,10 +677,6 @@ class ConfigActivity : AppCompatActivity() { Utils.alertView(this, getString(R.string.start_automatically), getString(R.string.start_automatically_help)) } - binding.BatteryOptimizationsTitle.setOnClickListener { - Utils.alertView(this, getString(R.string.battery_optimizations), - getString(R.string.battery_optimizations_help)) - } binding.ListenAddressTitle.setOnClickListener { Utils.alertView(this, getString(R.string.listen_address), getString(R.string.listen_address_help)) @@ -681,6 +712,14 @@ class ConfigActivity : AppCompatActivity() { Utils.alertView(this, getString(R.string.contacts), getString(R.string.contacts_help)) } + binding.BatteryOptimizationsTitle.setOnClickListener { + Utils.alertView(this, getString(R.string.battery_optimizations), + getString(R.string.battery_optimizations_help)) + } + binding.DefaultPhoneAppTitle.setOnClickListener { + Utils.alertView(this, getString(R.string.default_phone_app), + getString(R.string.default_phone_app_help)) + } binding.DebugTitle.setOnClickListener { Utils.alertView(this, getString(R.string.debug), getString(R.string.debug_help)) } diff --git a/app/src/main/kotlin/com/tutpro/baresip/InCallService.kt b/app/src/main/kotlin/com/tutpro/baresip/InCallService.kt new file mode 100644 index 00000000..fd31969e --- /dev/null +++ b/app/src/main/kotlin/com/tutpro/baresip/InCallService.kt @@ -0,0 +1,14 @@ +package com.tutpro.baresip + +import android.app.Service +import android.content.Intent +import android.os.IBinder + +// This is needed in order to allow choosing baresip as default Phone app + +class InCallService : Service() { + + override fun onBind(intent: Intent): IBinder { + TODO("Return the communication channel to the service.") + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/com/tutpro/baresip/MainActivity.kt b/app/src/main/kotlin/com/tutpro/baresip/MainActivity.kt index 3658fdf9..cba2e54b 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/MainActivity.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/MainActivity.kt @@ -9,6 +9,7 @@ import android.app.NotificationManager import android.content.* import android.content.Intent.ACTION_CALL import android.content.Intent.ACTION_DIAL +import android.content.Intent.ACTION_VIEW import android.content.pm.PackageManager import android.content.res.Configuration.ORIENTATION_PORTRAIT import android.media.AudioManager @@ -37,7 +38,6 @@ 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.net.URLDecoder import kotlin.system.exitProcess class MainActivity : AppCompatActivity() { @@ -653,11 +653,13 @@ class MainActivity : AppCompatActivity() { atStartup = intent.hasExtra("onStartup") - if (intent?.action == ACTION_CALL || intent?.action == ACTION_DIAL) { - if (BaresipService.isServiceRunning) - callAction(intent) - else - BaresipService.callActionUri = URLDecoder.decode(intent.data.toString(), "UTF-8") + when (intent?.action) { + ACTION_DIAL, ACTION_CALL, ACTION_VIEW -> + if (BaresipService.isServiceRunning) + callAction(intent.data, if (intent?.action == ACTION_CALL) "call" else "dial") + else + BaresipService.callActionUri = intent.data.toString() + .replace("tel:%2B", "tel:+") } permissions = if (Build.VERSION.SDK_INT >= 33) @@ -756,6 +758,9 @@ class MainActivity : AppCompatActivity() { callUri.setText(BaresipService.uas[aorSpinner.selectedItemPosition].account.resumeUri) callButton.performClick() } + "dial" -> { + callUri.setText(BaresipService.uas[aorSpinner.selectedItemPosition].account.resumeUri) + } "call transfer", "transfer show", "transfer accept" -> handleServiceEvent("$resumeAction,$resumeUri", arrayListOf(resumeCall!!.ua.uap, resumeCall!!.callp)) @@ -824,11 +829,11 @@ class MainActivity : AppCompatActivity() { resumeAction = "" resumeUri = "" - Log.d(TAG, "onNewIntent with intent.action '${intent.action}'") + Log.d(TAG, "onNewIntent with action/data '${intent.action}/${intent.data}'") when (intent.action) { - ACTION_DIAL, ACTION_CALL -> { - callAction(intent) + ACTION_DIAL, ACTION_CALL, ACTION_VIEW -> { + callAction(intent.data, if (intent.action == ACTION_CALL) "call" else "dial") } else -> { val action = intent.getStringExtra("action") @@ -840,15 +845,14 @@ class MainActivity : AppCompatActivity() { } } - private fun callAction(intent: Intent) { + private fun callAction(uri: Uri?, action: String) { if (Call.inCall() || BaresipService.uas.size == 0) return - val uri: Uri? = intent.data - Log.d(TAG, "callAction to $uri") + Log.d(TAG, "Action $action to $uri") if (uri != null) { when (uri.scheme) { "sip" -> { - val uriStr = URLDecoder.decode(uri.toString(), "UTF-8") + val uriStr = uri.toString() var ua = UserAgent.ofDomain(Utils.uriHostPart(uriStr)) if (ua == null && BaresipService.uas.size > 0) ua = BaresipService.uas[0] @@ -857,11 +861,11 @@ class MainActivity : AppCompatActivity() { return } spinToAor(ua.account.aor) - resumeAction = "call" + resumeAction = action ua.account.resumeUri = uriStr } "tel" -> { - val uriStr = URLDecoder.decode(uri.toString(), "UTF-8") + val uriStr = uri.toString().replace("%2B", "+") .filterNot{setOf('-', ' ', '(', ')').contains(it)} var account: Account? = null for (a in Account.accounts()) @@ -874,7 +878,7 @@ class MainActivity : AppCompatActivity() { return } spinToAor(account.aor) - resumeAction = "call" + resumeAction = action account.resumeUri = uriStr } else -> { @@ -897,7 +901,7 @@ class MainActivity : AppCompatActivity() { getString(R.string.no_network)) return } - "call" -> { + "call", "dial" -> { if (Call.inCall()) { Toast.makeText(applicationContext, getString(R.string.call_already_active), Toast.LENGTH_SHORT).show() @@ -957,7 +961,7 @@ class MainActivity : AppCompatActivity() { val uap = intent.getLongExtra("uap", 0L) val ua = UserAgent.ofUap(uap) if (ua == null) { - Log.w(TAG, "onNewIntent did not find ua $uap") + Log.w(TAG, "handleIntent did not find ua $uap") return } if (ua.account.aor != aorSpinner.tag) @@ -1005,27 +1009,16 @@ class MainActivity : AppCompatActivity() { } if (event == "started") { - val callActionUri = params[0] as String - Log.d(TAG, "Handling service event 'started' with '$callActionUri'") + val uriString = params[0] as String + Log.d(TAG, "Handling service event 'started' with URI '$uriString'") if (!this::uaAdapter.isInitialized) { // Android has restarted baresip when permission has been denied in app settings recreate() return } uaAdapter.notifyDataSetChanged() - if (callActionUri != "") { - var ua = UserAgent.ofDomain(Utils.uriHostPart(callActionUri)) - if (ua == null) - if (BaresipService.uas.size > 0) { - ua = BaresipService.uas[0] - } else { - handleNextEvent("No UAs to make the call to '$callActionUri'") - return - } - spinToAor(ua.account.aor) - ua.account.resumeUri = callActionUri - callUri.setText(callActionUri) - callButton.performClick() + if (uriString != "") { + callAction(uriString.toUri(), "dial") } else { if ((aorSpinner.selectedItemPosition == -1) && (BaresipService.uas.size > 0)) { aorSpinner.setSelection(0) diff --git a/app/src/main/res/layout/activity_config.xml b/app/src/main/res/layout/activity_config.xml index 5e40dc1c..85c2ffd1 100644 --- a/app/src/main/res/layout/activity_config.xml +++ b/app/src/main/res/layout/activity_config.xml @@ -41,33 +41,6 @@ - - - - - - - + + + + + + + + + + + + + + Ota akun käytön optimointi pois päältä (suositeltu), jos haluat vähentää todennäköisyyttä, että Android rajoittaa baresip-sovelluksen toimintaa ja pääsyä verkkoon. + Oletuspuhelinsovellus + Puhelinrooli ei ole saatavana + Jos merkity, baresip on oletuspuhelinsovellus. Älä + merkitse, jos laitteesi täytyy hallita myös muita kuin SIP-puheluita tai -viestejä. Kuunteluosoite IP-osoite ja portti muotoa \'osoite:portti\', missä baresip kuuntelee sisään tulevia diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b5ae1e9a..7cf0501c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -297,6 +297,10 @@ Disable battery optimizations (recommended) if you want to reduce likelihood that Android restricts baresip\'s access to network or enters baresip to standby state. + Default Phone App + Dialer role is not available + If checked, baresip is the default phone app. Do not check + if your device may need to handle also other that SIP calls or messages. Listen Address IP address and port of form \'address:port\' at which baresip listens for incoming SIP requests. If IP address is an IPv6 address, it must be written inside