Show two mobile account tel: URIs if different SIM is in use for calls and messages

This commit is contained in:
Juha Heinanen
2026-05-29 12:07:23 +03:00
parent 27f0bdf1a0
commit 4a388345b2
6 changed files with 125 additions and 65 deletions
@@ -90,7 +90,7 @@ class Account(val accp: Long, virtualAor: String? = null) {
fun print() : String { fun print() : String {
var res = if (isMobile) { var res = if (isMobile) {
"<${aor};transport=udp>" "<sip:mobile@pstn;transport=udp>"
} else { } else {
if (displayName != "") if (displayName != "")
"\"${displayName}\" " "\"${displayName}\" "
@@ -45,6 +45,7 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableLongStateOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
@@ -62,6 +63,10 @@ import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.compose.runtime.DisposableEffect
import androidx.lifecycle.compose.LocalLifecycleOwner
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder import androidx.navigation.NavGraphBuilder
@@ -81,6 +86,7 @@ import java.io.StringReader
import java.net.URL import java.net.URL
import java.util.Locale import java.util.Locale
import javax.net.ssl.HttpsURLConnection import javax.net.ssl.HttpsURLConnection
import android.telephony.SubscriptionManager
fun NavGraphBuilder.accountScreenRoute(navController: NavController) { fun NavGraphBuilder.accountScreenRoute(navController: NavController) {
composable( composable(
@@ -126,6 +132,20 @@ private fun AccountScreen(
) { ) {
val ua = UserAgent.ofAor(aor)!! val ua = UserAgent.ofAor(aor)!!
val acc = ua.account val acc = ua.account
val lifecycleOwner = LocalLifecycleOwner.current
var resumeToggle by remember { mutableLongStateOf(0L) }
DisposableEffect(lifecycleOwner) {
val observer = LifecycleEventObserver { _, event ->
if (event == Lifecycle.Event.ON_RESUME) {
resumeToggle = System.currentTimeMillis()
}
}
lifecycleOwner.lifecycle.addObserver(observer)
onDispose {
lifecycleOwner.lifecycle.removeObserver(observer)
}
}
var isAccountAvailable by remember { mutableStateOf(false) } var isAccountAvailable by remember { mutableStateOf(false) }
var isAccountLoaded by remember { mutableStateOf(false) } var isAccountLoaded by remember { mutableStateOf(false) }
@@ -190,7 +210,7 @@ private fun AccountScreen(
} }
) { contentPadding -> ) { contentPadding ->
if (isAccountLoaded) if (isAccountLoaded)
AccountContent(viewModel, navController, contentPadding, ua) AccountContent(viewModel, navController, contentPadding, ua, resumeToggle)
else else
Box( Box(
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
@@ -215,7 +235,8 @@ private fun AccountContent(
viewModel: AccountViewModel, viewModel: AccountViewModel,
navController: NavController, navController: NavController,
contentPadding: PaddingValues, contentPadding: PaddingValues,
ua: UserAgent ua: UserAgent,
resumeToggle: Long
) { ) {
val ctx = LocalContext.current val ctx = LocalContext.current
val aor = ua.account.aor val aor = ua.account.aor
@@ -224,31 +245,16 @@ private fun AccountContent(
val showStun by remember { derivedStateOf { mediaNat.isNotEmpty() } } val showStun by remember { derivedStateOf { mediaNat.isNotEmpty() } }
@Composable @Composable
fun AoR() { fun AoRField(value: String, label: String) {
Row(
Modifier
.fillMaxWidth()
.padding(top = 8.dp, end = 10.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Start
) {
val aorText = if (ua.account.isMobile) {
if (ua.account.aor == "sip:mobile@pstn")
stringResource(R.string.not_available)
else
"tel:${Utils.uriUserPart(ua.account.aor)}"
} else
ua.account.luri
OutlinedTextField( OutlinedTextField(
value = aorText, value = value,
enabled = false, enabled = false,
onValueChange = {}, onValueChange = {},
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
textStyle = TextStyle(fontSize = 18.sp), textStyle = TextStyle(fontSize = 18.sp),
label = { label = {
Text( Text(
text = stringResource(if (ua.account.isMobile) R.string.tel_uri else R.string.sip_uri), text = label,
fontWeight = FontWeight.Bold fontWeight = FontWeight.Bold
) )
}, },
@@ -261,6 +267,58 @@ private fun AccountContent(
) )
) )
} }
@Composable
fun AoR(toggle: Long) {
Column(
Modifier
.fillMaxWidth()
.padding(top = 8.dp, end = 10.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
if (ua.account.isMobile && android.os.Build.VERSION.SDK_INT >= 29) {
val voiceSubId = remember(toggle) { SubscriptionManager.getDefaultVoiceSubscriptionId() }
val smsSubId = remember(toggle) { SubscriptionManager.getDefaultSmsSubscriptionId() }
if (voiceSubId != smsSubId &&
voiceSubId != SubscriptionManager.INVALID_SUBSCRIPTION_ID &&
smsSubId != SubscriptionManager.INVALID_SUBSCRIPTION_ID
) {
val voiceNumber = remember(voiceSubId, toggle) { Utils.getLine1Number(ctx, voiceSubId) }
AoRField(
value = if (voiceNumber != null) "tel:$voiceNumber" else stringResource(R.string.not_available),
label = stringResource(R.string.tel_uri_calls)
)
val smsNumber = remember(smsSubId, toggle) { Utils.getLine1Number(ctx, smsSubId) }
AoRField(
value = if (smsNumber != null) "tel:$smsNumber" else stringResource(R.string.not_available),
label = stringResource(R.string.tel_uri_messages)
)
} else {
val currentSubId = remember(toggle) {
if (voiceSubId != SubscriptionManager.INVALID_SUBSCRIPTION_ID)
voiceSubId
else
smsSubId
}
val currentNumber = remember(currentSubId, toggle) {
if (currentSubId != SubscriptionManager.INVALID_SUBSCRIPTION_ID)
Utils.getLine1Number(ctx, currentSubId)
else
Utils.getLine1Number(ctx)
}
AoRField(
value = if (currentNumber != null) "tel:$currentNumber" else stringResource(R.string.not_available),
label = stringResource(R.string.tel_uri)
)
}
} else {
AoRField(
value = if (ua.account.isMobile) stringResource(R.string.not_available) else ua.account.luri,
label = stringResource(if (ua.account.isMobile) R.string.tel_uri else R.string.sip_uri)
)
}
}
} }
@Composable @Composable
@@ -1259,7 +1317,7 @@ private fun AccountContent(
.verticalScroll(state = scrollState), .verticalScroll(state = scrollState),
verticalArrangement = Arrangement.spacedBy(8.dp), verticalArrangement = Arrangement.spacedBy(8.dp),
) { ) {
AoR() AoR(resumeToggle)
Nickname() Nickname()
if (!ua.account.isMobile) { if (!ua.account.isMobile) {
DisplayName() DisplayName()
@@ -2088,21 +2088,9 @@ class BaresipService: Service() {
return return
} }
val userPart = Utils.getLine1Number(this) ?: "mobile" val mobileAor = "sip:mobile@pstn"
val mobileAor = "sip:$userPart@pstn"
if (existingMobileUa != null) { if (existingMobileUa != null) {
// Update AOR if it previously was sip:mobile@pstn but now a real number
if (existingMobileUa.account.aor == "sip:mobile@pstn" && mobileAor != "sip:mobile@pstn") {
Log.d(TAG, "Updating existing Mobile account AOR to $mobileAor")
val aorField = Account::class.java.getDeclaredField("aor")
aorField.isAccessible = true
aorField.set(existingMobileUa.account, mobileAor)
val luriField = Account::class.java.getDeclaredField("luri")
luriField.isAccessible = true
luriField.set(existingMobileUa.account, mobileAor)
Account.saveAccounts()
}
return return
} }
+27 -17
View File
@@ -1374,35 +1374,45 @@ object Utils {
} }
} }
@RequiresApi(29)
@SuppressLint("HardwareIds") @SuppressLint("HardwareIds")
fun getLine1Number(ctx: Context): String? { fun getLine1Number(ctx: Context, subscriptionId: Int = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID): String? {
try { try {
if (Build.VERSION.SDK_INT >= 33) { if (Build.VERSION.SDK_INT >= 33) {
if (ctx.checkSelfPermission(Manifest.permission.READ_PHONE_NUMBERS) == if (ctx.checkSelfPermission(Manifest.permission.READ_PHONE_NUMBERS) ==
PackageManager.PERMISSION_GRANTED) { PackageManager.PERMISSION_GRANTED
val sm = ctx.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE) as SubscriptionManager ) {
val number = sm.getPhoneNumber(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) val sm =
ctx.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE) as SubscriptionManager
val number = sm.getPhoneNumber(subscriptionId)
if (number != "") { if (number != "") {
Log.d(TAG, "Retrieved SIM number $number via SubscriptionManager") Log.d(TAG, "Retrieved SIM number $number via SubscriptionManager for $subscriptionId")
return number return number
} } else
else Log.d(TAG, "Did not get SIM number via SubscriptionManager for $subscriptionId")
Log.d(TAG, "Did not get SIM number via SubscriptionManager") } else
}
else
Log.d(TAG, "No READ_PHONE_NUMBERS permission") Log.d(TAG, "No READ_PHONE_NUMBERS permission")
} else { } else {
if (checkPermissions(ctx, arrayOf(Manifest.permission.READ_PHONE_NUMBERS, if (checkPermissions(
Manifest.permission.READ_PHONE_STATE))) { ctx, arrayOf(
Manifest.permission.READ_PHONE_NUMBERS,
Manifest.permission.READ_PHONE_STATE
)
)
) {
val tm = ctx.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager val tm = ctx.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
val targetTm = if (subscriptionId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID)
tm
else
tm.createForSubscriptionId(subscriptionId)
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
val number = tm.line1Number val number = targetTm.line1Number
if (number != null) { if (number != null && number != "") {
Log.d(TAG, "Retrieved SIM number $number via TelephonyManager") Log.d(TAG, "Retrieved SIM number $number via TelephonyManager for $subscriptionId")
return number return number
} }
} } else
else
Log.d(TAG, "No READ_PHONE_NUMBERS and/or READ_PHONE_STATE permissions") Log.d(TAG, "No READ_PHONE_NUMBERS and/or READ_PHONE_STATE permissions")
} }
} catch (e: Exception) { } catch (e: Exception) {
+2
View File
@@ -113,6 +113,8 @@
</string> </string>
<!-- Account Activity --> <!-- Account Activity -->
<string name="account">Tili</string> <string name="account">Tili</string>
<string name="tel_uri_calls">TEL URI Puhelut</string>
<string name="tel_uri_messages">TEL URI Viestit</string>
<string name="not_available">Ei saatavana</string> <string name="not_available">Ei saatavana</string>
<string name="account_nickname_help">Lempinimi (jos annettu) millä tämä tili identifioidaan <string name="account_nickname_help">Lempinimi (jos annettu) millä tämä tili identifioidaan
baresip sovelluksessa.</string> baresip sovelluksessa.</string>
+4 -2
View File
@@ -116,6 +116,10 @@
</string> </string>
<!-- Account Activity --> <!-- Account Activity -->
<string name="account">Account</string> <string name="account">Account</string>
<string name="sip_uri" translatable="false">SIP URI</string>
<string name="tel_uri" translatable="false">TEL URI</string>
<string name="tel_uri_calls">TEL URI for Calls</string>
<string name="tel_uri_messages">TEL URI for Messages</string>
<string name="not_available">Not available</string> <string name="not_available">Not available</string>
<string name="account_nickname_help">Nickname (if any) used to identify this account within <string name="account_nickname_help">Nickname (if any) used to identify this account within
baresip app.</string> baresip app.</string>
@@ -496,8 +500,6 @@
<string name="no">No</string> <string name="no">No</string>
<string name="accept">Accept</string> <string name="accept">Accept</string>
<string name="deny">Deny</string> <string name="deny">Deny</string>
<string name="sip_uri" translatable="false">SIP URI</string>
<string name="tel_uri" translatable="false">TEL URI</string>
<string name="add">Add</string> <string name="add">Add</string>
<string name="delete">Delete</string> <string name="delete">Delete</string>
<string name="edit">Edit</string> <string name="edit">Edit</string>