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

View File

@ -90,7 +90,7 @@ class Account(val accp: Long, virtualAor: String? = null) {
fun print() : String {
var res = if (isMobile) {
"<${aor};transport=udp>"
"<sip:mobile@pstn;transport=udp>"
} else {
if (displayName != "")
"\"${displayName}\" "

View File

@ -45,6 +45,7 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableLongStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
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.unit.dp
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.navigation.NavController
import androidx.navigation.NavGraphBuilder
@ -81,6 +86,7 @@ import java.io.StringReader
import java.net.URL
import java.util.Locale
import javax.net.ssl.HttpsURLConnection
import android.telephony.SubscriptionManager
fun NavGraphBuilder.accountScreenRoute(navController: NavController) {
composable(
@ -126,6 +132,20 @@ private fun AccountScreen(
) {
val ua = UserAgent.ofAor(aor)!!
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 isAccountLoaded by remember { mutableStateOf(false) }
@ -190,7 +210,7 @@ private fun AccountScreen(
}
) { contentPadding ->
if (isAccountLoaded)
AccountContent(viewModel, navController, contentPadding, ua)
AccountContent(viewModel, navController, contentPadding, ua, resumeToggle)
else
Box(
modifier = Modifier.fillMaxSize(),
@ -215,7 +235,8 @@ private fun AccountContent(
viewModel: AccountViewModel,
navController: NavController,
contentPadding: PaddingValues,
ua: UserAgent
ua: UserAgent,
resumeToggle: Long
) {
val ctx = LocalContext.current
val aor = ua.account.aor
@ -224,42 +245,79 @@ private fun AccountContent(
val showStun by remember { derivedStateOf { mediaNat.isNotEmpty() } }
@Composable
fun AoR() {
Row(
fun AoRField(value: String, label: String) {
OutlinedTextField(
value = value,
enabled = false,
onValueChange = {},
modifier = Modifier.fillMaxWidth(),
textStyle = TextStyle(fontSize = 18.sp),
label = {
Text(
text = label,
fontWeight = FontWeight.Bold
)
},
colors = OutlinedTextFieldDefaults.colors(
disabledTextColor = MaterialTheme.colorScheme.onSurface,
disabledBorderColor = MaterialTheme.colorScheme.outline,
disabledLeadingIconColor = MaterialTheme.colorScheme.onSurfaceVariant,
disabledTrailingIconColor = MaterialTheme.colorScheme.onSurfaceVariant,
disabledLabelColor = MaterialTheme.colorScheme.onSurfaceVariant,
)
)
}
@Composable
fun AoR(toggle: Long) {
Column(
Modifier
.fillMaxWidth()
.padding(top = 8.dp, end = 10.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Start
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
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
if (ua.account.isMobile && android.os.Build.VERSION.SDK_INT >= 29) {
val voiceSubId = remember(toggle) { SubscriptionManager.getDefaultVoiceSubscriptionId() }
val smsSubId = remember(toggle) { SubscriptionManager.getDefaultSmsSubscriptionId() }
OutlinedTextField(
value = aorText,
enabled = false,
onValueChange = {},
modifier = Modifier.fillMaxWidth(),
textStyle = TextStyle(fontSize = 18.sp),
label = {
Text(
text = stringResource(if (ua.account.isMobile) R.string.tel_uri else R.string.sip_uri),
fontWeight = FontWeight.Bold
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)
)
},
colors = OutlinedTextFieldDefaults.colors(
disabledTextColor = MaterialTheme.colorScheme.onSurface,
disabledBorderColor = MaterialTheme.colorScheme.outline,
disabledLeadingIconColor = MaterialTheme.colorScheme.onSurfaceVariant,
disabledTrailingIconColor = MaterialTheme.colorScheme.onSurfaceVariant,
disabledLabelColor = MaterialTheme.colorScheme.onSurfaceVariant,
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)
)
)
}
}
}
@ -1259,7 +1317,7 @@ private fun AccountContent(
.verticalScroll(state = scrollState),
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
AoR()
AoR(resumeToggle)
Nickname()
if (!ua.account.isMobile) {
DisplayName()

View File

@ -2088,21 +2088,9 @@ class BaresipService: Service() {
return
}
val userPart = Utils.getLine1Number(this) ?: "mobile"
val mobileAor = "sip:$userPart@pstn"
val mobileAor = "sip:mobile@pstn"
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
}

View File

@ -1374,35 +1374,45 @@ object Utils {
}
}
@RequiresApi(29)
@SuppressLint("HardwareIds")
fun getLine1Number(ctx: Context): String? {
fun getLine1Number(ctx: Context, subscriptionId: Int = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID): String? {
try {
if (Build.VERSION.SDK_INT >= 33) {
if (ctx.checkSelfPermission(Manifest.permission.READ_PHONE_NUMBERS) ==
PackageManager.PERMISSION_GRANTED) {
val sm = ctx.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE) as SubscriptionManager
val number = sm.getPhoneNumber(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID)
PackageManager.PERMISSION_GRANTED
) {
val sm =
ctx.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE) as SubscriptionManager
val number = sm.getPhoneNumber(subscriptionId)
if (number != "") {
Log.d(TAG, "Retrieved SIM number $number via SubscriptionManager")
Log.d(TAG, "Retrieved SIM number $number via SubscriptionManager for $subscriptionId")
return number
}
else
Log.d(TAG, "Did not get SIM number via SubscriptionManager")
}
else
} else
Log.d(TAG, "Did not get SIM number via SubscriptionManager for $subscriptionId")
} else
Log.d(TAG, "No READ_PHONE_NUMBERS permission")
} else {
if (checkPermissions(ctx, arrayOf(Manifest.permission.READ_PHONE_NUMBERS,
Manifest.permission.READ_PHONE_STATE))) {
if (checkPermissions(
ctx, arrayOf(
Manifest.permission.READ_PHONE_NUMBERS,
Manifest.permission.READ_PHONE_STATE
)
)
) {
val tm = ctx.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
val targetTm = if (subscriptionId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID)
tm
else
tm.createForSubscriptionId(subscriptionId)
@Suppress("DEPRECATION")
val number = tm.line1Number
if (number != null) {
Log.d(TAG, "Retrieved SIM number $number via TelephonyManager")
val number = targetTm.line1Number
if (number != null && number != "") {
Log.d(TAG, "Retrieved SIM number $number via TelephonyManager for $subscriptionId")
return number
}
}
else
} else
Log.d(TAG, "No READ_PHONE_NUMBERS and/or READ_PHONE_STATE permissions")
}
} catch (e: Exception) {

View File

@ -113,6 +113,8 @@
</string>
<!-- Account Activity -->
<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="account_nickname_help">Lempinimi (jos annettu) millä tämä tili identifioidaan
baresip sovelluksessa.</string>

View File

@ -116,6 +116,10 @@
</string>
<!-- Account Activity -->
<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="account_nickname_help">Nickname (if any) used to identify this account within
baresip app.</string>
@ -496,8 +500,6 @@
<string name="no">No</string>
<string name="accept">Accept</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="delete">Delete</string>
<string name="edit">Edit</string>