Added initial support for sending and receiving SMS messages

This commit is contained in:
Juha Heinanen
2026-05-17 14:56:20 +03:00
parent a6981d121b
commit 3af0f39a8c
14 changed files with 435 additions and 37 deletions
+60
View File
@@ -36,6 +36,12 @@
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.SEND_SMS" <uses-permission android:name="android.permission.SEND_SMS"
tools:ignore="SmsAndCallLogPolicy" /> tools:ignore="SmsAndCallLogPolicy" />
<uses-permission android:name="android.permission.RECEIVE_SMS"
tools:ignore="SmsAndCallLogPolicy" />
<uses-permission android:name="android.permission.READ_SMS"
tools:ignore="SmsAndCallLogPolicy" />
<uses-permission android:name="android.permission.RECEIVE_MMS"
tools:ignore="SmsAndCallLogPolicy" />
<uses-feature <uses-feature
android:name="android.hardware.telephony" android:name="android.hardware.telephony"
@@ -78,10 +84,28 @@
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
<action android:name="android.intent.action.DIAL" /> <action android:name="android.intent.action.DIAL" />
<action android:name="android.intent.action.CALL" /> <action android:name="android.intent.action.CALL" />
<action android:name="android.intent.action.SENDTO" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" /> <category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="tel" /> <data android:scheme="tel" />
<data android:scheme="sip" /> <data android:scheme="sip" />
<data android:scheme="smsto" />
</intent-filter>
</activity>
<activity
android:name=".ComposeActivity"
android:configChanges="orientation|keyboardHidden|screenSize"
android:exported="true" >
<intent-filter>
<action android:name="android.intent.action.SEND" />
<action android:name="android.intent.action.SENDTO" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="sms" />
<data android:scheme="smsto" />
<data android:scheme="mms" />
<data android:scheme="mmsto" />
</intent-filter> </intent-filter>
</activity> </activity>
@@ -121,6 +145,42 @@
</intent-filter> </intent-filter>
</service> </service>
<service
android:name=".HeadlessSmsSendService"
android:enabled="true"
android:exported="true"
android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE" >
<intent-filter>
<action android:name="android.intent.action.RESPOND_VIA_MESSAGE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="sms" />
<data android:scheme="smsto" />
<data android:scheme="mms" />
<data android:scheme="mmsto" />
</intent-filter>
</service>
<receiver
android:name=".SmsReceiver"
android:enabled="true"
android:exported="true"
android:permission="android.permission.BROADCAST_SMS" >
<intent-filter>
<action android:name="android.provider.Telephony.SMS_DELIVER" />
</intent-filter>
</receiver>
<receiver
android:name=".MmsReceiver"
android:enabled="true"
android:exported="true"
android:permission="android.permission.BROADCAST_WAP_PUSH" >
<intent-filter>
<action android:name="android.provider.Telephony.WAP_PUSH_DELIVER" />
<data android:mimeType="application/vnd.wap.mms-message" />
</intent-filter>
</receiver>
<receiver <receiver
android:name=".BootCompletedReceiver" android:name=".BootCompletedReceiver"
android:enabled="true" android:enabled="true"
@@ -1279,10 +1279,29 @@ class BaresipService: Service() {
error error
} }
val timeStamp = System.currentTimeMillis() handleIncomingMessage(uap, peerUri, text, System.currentTimeMillis())
val timeStampString = timeStamp.toString() }
Log.d(TAG, "Message event for $uap from $peerUri at $timeStampString")
Message(ua.account.aor, peerUri, text, timeStamp, MESSAGE_DOWN, 0, "", true).add() @SuppressLint("UnspecifiedImmutableFlag")
fun handleIncomingMessage(uap: Long, peerUri: String, text: String, timeStamp: Long) {
val ua = UserAgent.ofUap(uap)
if (ua == null) {
Log.w(TAG, "handleIncomingMessage did not find ua $uap")
return
}
val aor = ua.account.aor
// Check for duplicates
val lastMsg = messages.lastOrNull { m -> m.aor == aor }
if (lastMsg != null && lastMsg.timeStamp == timeStamp && lastMsg.peerUri == peerUri && lastMsg.message == text) {
Log.d(TAG, "Omit duplicate message from $peerUri")
return
}
Log.d(TAG, "Message event for $uap from $peerUri at $timeStamp")
Message(aor, peerUri, text, timeStamp, MESSAGE_DOWN, 0, "", true).add()
ua.account.unreadMessages = true ua.account.unreadMessages = true
if (!Utils.isVisible()) { if (!Utils.isVisible()) {
@@ -1339,7 +1358,7 @@ class BaresipService: Service() {
val saveIntent = Intent(this, BaresipService::class.java) val saveIntent = Intent(this, BaresipService::class.java)
saveIntent.action = "Message Save" saveIntent.action = "Message Save"
saveIntent.putExtra("uap", uap).putExtra("time", timeStampString) saveIntent.putExtra("uap", uap).putExtra("time", timeStamp.toString())
val savePendingIntent = PendingIntent.getService(this, SAVE_REQ_CODE, saveIntent, piFlags) val savePendingIntent = PendingIntent.getService(this, SAVE_REQ_CODE, saveIntent, piFlags)
val saveAction = NotificationCompat.Action.Builder( val saveAction = NotificationCompat.Action.Builder(
R.drawable.ic_notification_save, R.drawable.ic_notification_save,
@@ -1349,7 +1368,7 @@ class BaresipService: Service() {
val deleteIntent = Intent(this, BaresipService::class.java) val deleteIntent = Intent(this, BaresipService::class.java)
deleteIntent.action = "Message Delete" deleteIntent.action = "Message Delete"
deleteIntent.putExtra("uap", uap).putExtra("time", timeStampString) deleteIntent.putExtra("uap", uap).putExtra("time", timeStamp.toString())
val deletePendingIntent = PendingIntent.getService(this, DELETE_REQ_CODE, deleteIntent, piFlags) val deletePendingIntent = PendingIntent.getService(this, DELETE_REQ_CODE, deleteIntent, piFlags)
val deleteAction = NotificationCompat.Action.Builder( val deleteAction = NotificationCompat.Action.Builder(
R.drawable.ic_notification_delete, R.drawable.ic_notification_delete,
@@ -349,9 +349,16 @@ private fun Calls(
lastButtonText.value = ctx.getString(R.string.send_message) lastButtonText.value = ctx.getString(R.string.send_message)
lastAction.value = { lastAction.value = {
if (account.isMobile) { if (account.isMobile) {
alertTitle.value = ctx.getString(R.string.notice) if (!Utils.isDefaultSmsApp(ctx)) {
alertMessage.value = ctx.getString(R.string.no_sms_messaging) alertTitle.value = ctx.getString(R.string.notice)
showAlert.value = true alertMessage.value = ctx.getString(R.string.enable_default_messaging)
showAlert.value = true
} else {
if (ua != null) {
handleIntent(ctx, viewModel, intent, "message")
navController.navigateUp()
}
}
} }
else else
if (ua != null) { if (ua != null) {
@@ -0,0 +1,37 @@
package com.tutpro.baresip
import android.content.Intent
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.core.content.IntentSanitizer
/**
* Skeletal activity for SEND/SENDTO intents, required for Default SMS App eligibility.
* Redirects to MainActivity for processing.
*/
class ComposeActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Sanitize incoming intent to prevent intent redirection attacks
val sanitizedIntent = IntentSanitizer.Builder()
.allowAction(Intent.ACTION_SEND)
.allowAction(Intent.ACTION_SENDTO)
.allowType { it.startsWith("text/") }
.allowData { uri ->
uri.scheme in listOf("sms", "smsto", "mms", "mmsto")
}
.allowExtra(Intent.EXTRA_TEXT) { it is String || it is CharSequence }
.allowExtra("sms_body") { it is String || it is CharSequence }
.allowExtra("address") { it is String }
.allowExtra(Intent.EXTRA_STREAM) { true }
.allowExtra("exit_on_sent") { it is Boolean }
.build()
.sanitizeByFiltering(intent)
// Redirect to MainActivity which handles dialer/chat UI
sanitizedIntent.setClass(this, MainActivity::class.java)
sanitizedIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT)
startActivity(sanitizedIntent)
finish()
}
}
@@ -530,9 +530,14 @@ private fun ContactsContent(
lastAction.value = { lastAction.value = {
if (ua != null) { if (ua != null) {
if (ua.account.isMobile) { if (ua.account.isMobile) {
alertTitle.value = ctx.getString(R.string.notice) if (!Utils.isDefaultSmsApp(ctx)) {
alertMessage.value = ctx.getString(R.string.no_sms_messaging) alertTitle.value = ctx.getString(R.string.notice)
showAlert.value = true alertMessage.value = ctx.getString(R.string.enable_default_messaging)
showAlert.value = true
} else {
handleIntent(ctx, viewModel, intent, "message")
navController.navigateUp()
}
} }
else { else {
handleIntent(ctx, viewModel, intent, "message") handleIntent(ctx, viewModel, intent, "message")
@@ -0,0 +1,12 @@
package com.tutpro.baresip
import android.app.Service
import android.content.Intent
import android.os.IBinder
/**
* Skeletal headless SMS send service required for Default SMS App eligibility.
*/
class HeadlessSmsSendService : Service() {
override fun onBind(intent: Intent?): IBinder? = null
}
@@ -6,6 +6,7 @@ import android.Manifest.permission.WRITE_EXTERNAL_STORAGE
import android.app.Activity import android.app.Activity
import android.app.Activity.RESULT_OK import android.app.Activity.RESULT_OK
import android.app.KeyguardManager import android.app.KeyguardManager
import android.app.role.RoleManager
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.res.Configuration import android.content.res.Configuration
@@ -808,17 +809,26 @@ private fun BottomBar(ctx: Context, viewModel: ViewModel, navController: NavCont
IconButton( IconButton(
enabled = aor.isNotEmpty(), enabled = aor.isNotEmpty(),
onClick = { onClick = {
if (isMobile) {
if (!Utils.isDefaultSmsApp(ctx)) {
alertTitle.value = ctx.getString(R.string.notice)
alertMessage.value = ctx.getString(R.string.enable_default_messaging)
showAlert.value = true
return@IconButton
}
}
navController.navigate("chats/$aor") navController.navigate("chats/$aor")
}, },
modifier = Modifier modifier = Modifier.weight(1f).size(buttonSize)
.weight(1f)
.size(buttonSize)
) { ) {
Icon( Icon(
imageVector = Icons.AutoMirrored.Filled.Chat, imageVector = Icons.AutoMirrored.Filled.Chat,
contentDescription = null, contentDescription = null,
Modifier.size(buttonSize), Modifier.size(buttonSize),
tint = if (hasUnreadMessages) MaterialTheme.colorScheme.error else MaterialTheme.colorScheme.secondary tint = if (hasUnreadMessages)
MaterialTheme.colorScheme.error
else
MaterialTheme.colorScheme.secondary
) )
} }
@@ -0,0 +1,127 @@
package com.tutpro.baresip
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.provider.Telephony
import androidx.core.net.toUri
/**
* MMS receiver that extracts text components from incoming MMS messages.
*/
class MmsReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (intent.action == Telephony.Sms.Intents.WAP_PUSH_DELIVER_ACTION) {
val contentType = intent.type
if (contentType == "application/vnd.wap.mms-message") {
Log.d(TAG, "Received MMS WAP Push Deliver")
// For a robust implementation, we'd need to parse the PDU.
// However, since we are the default SMS app, the OS will also
// save the MMS to the system provider. We can query it.
// Note: WAP_PUSH_DELIVER usually triggers before or during the save.
// We might need a small delay or use a ContentObserver if the query fails.
extractTextFromProvider(context)
}
}
}
private fun extractTextFromProvider(context: Context) {
// Query the most recent MMS message
val uri = "content://mms".toUri()
val cursor = context.contentResolver.query(uri, null, null, null, "date DESC LIMIT 1")
cursor?.use { c ->
if (c.moveToFirst()) {
val id = c.getString(c.getColumnIndexOrThrow("_id"))
val date = c.getLong(c.getColumnIndexOrThrow("date")) * 1000 // mms date is in seconds
// Get sender address
val address = getMmsAddr(context, id) ?: "unknown"
// Get text parts
val body = getMmsText(context, id)
if (body.isNotEmpty()) {
Log.d(TAG, "Extracted MMS text from $address: $body")
val mobileUa = BaresipService.uas.value.find { it.account.isMobile }
if (mobileUa != null) {
// Notify Service for history update, notification, and alert sound
if (BaresipService.isServiceRunning) {
BaresipService.instance?.handleIncomingMessage(mobileUa.uap, "tel:$address", body, date)
} else {
// Service not running, at least save to history
val aor = mobileUa.account.aor
// Check if this message was already added (simple deduplication by timestamp)
val lastMsg = Message.messages().lastOrNull { m -> m.aor == aor }
if (lastMsg == null || lastMsg.timeStamp != date || lastMsg.peerUri != "tel:$address") {
Message(aor, "tel:$address", body, date, MESSAGE_DOWN, 0, "", true).add()
mobileUa.account.unreadMessages = true
}
}
}
}
}
}
}
private fun getMmsAddr(context: Context, id: String): String? {
val uri = "content://mms/$id/addr".toUri()
val cursor = context.contentResolver.query(uri, null, "msg_id=$id", null, null)
var addr: String? = null
cursor?.use {
if (it.moveToFirst()) {
do {
val type = it.getInt(it.getColumnIndexOrThrow("type"))
if (type == 137) { // PDU_FROM
addr = it.getString(it.getColumnIndexOrThrow("address"))
break
}
} while (it.moveToNext())
}
}
return addr
}
private fun getMmsText(context: Context, id: String): String {
val selectionPart = "mid=$id"
val uri = "content://mms/part".toUri()
val cursor = context.contentResolver.query(uri, null, selectionPart, null, null)
val sb = StringBuilder()
cursor?.use {
while (it.moveToNext()) {
val type = it.getString(it.getColumnIndexOrThrow("ct"))
if (type == "text/plain") {
val data = it.getString(it.getColumnIndexOrThrow("_data"))
val body = if (data != null) {
getPartText(context, it.getString(it.getColumnIndexOrThrow("_id")))
} else {
it.getString(it.getColumnIndexOrThrow("text"))
}
if (body != null) sb.append(body)
}
}
}
return sb.toString()
}
private fun getPartText(context: Context, partId: String): String? {
val partUri = "content://mms/part/$partId".toUri()
return try {
context.contentResolver.openInputStream(partUri)?.use { isStream ->
isStream.bufferedReader().use { it.readText() }
}
} catch (e: Exception) {
Log.e(TAG, "Failed to read MMS part $partId", e)
null
}
}
companion object {
private const val TAG = "MmsReceiver"
}
}
@@ -1064,6 +1064,7 @@ private fun SettingsContent(
if (!roleManager.isRoleHeld(RoleManager.ROLE_DIALER)) if (!roleManager.isRoleHeld(RoleManager.ROLE_DIALER))
dialerRoleRequest.launch(roleManager.createRequestRoleIntent(RoleManager.ROLE_DIALER)) dialerRoleRequest.launch(roleManager.createRequestRoleIntent(RoleManager.ROLE_DIALER))
} else { } else {
viewModel.defaultMessaging.value = false
try { try {
dialerRoleRequest.launch(Intent("android.settings.MANAGE_DEFAULT_APPS_SETTINGS")) dialerRoleRequest.launch(Intent("android.settings.MANAGE_DEFAULT_APPS_SETTINGS"))
} catch (e: ActivityNotFoundException) { } catch (e: ActivityNotFoundException) {
@@ -1075,6 +1076,64 @@ private fun SettingsContent(
} }
} }
@RequiresApi(29)
@Composable
fun DefaultMessaging() {
val defaultMessagingAppTitle = stringResource(R.string.default_messaging_app)
val defaultMessagingAppHelp = stringResource(R.string.default_messaging_app_help)
val messagingRoleNotAvailableMessage = stringResource(R.string.messaging_role_not_available)
Row(
Modifier
.fillMaxWidth()
.padding(end = 10.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Start
) {
val ctx = LocalContext.current
Text(text = defaultMessagingAppTitle,
modifier = Modifier
.weight(1f)
.clickable {
alertTitle.value = defaultMessagingAppTitle
alertMessage.value = defaultMessagingAppHelp
showAlert.value = true
},
fontSize = 18.sp
)
val defaultMessaging by viewModel.defaultMessaging.collectAsState()
val roleManager = ctx.getSystemService(ROLE_SERVICE) as RoleManager
val messagingRoleRequest = rememberLauncherForActivityResult(
contract = ActivityResultContracts.StartActivityForResult()
) { _ ->
val isHeld = roleManager.isRoleHeld(RoleManager.ROLE_SMS)
viewModel.defaultMessaging.value = isHeld
}
Switch(
checked = defaultMessaging,
onCheckedChange = {
viewModel.defaultMessaging.value = it
if (it) {
if (!roleManager.isRoleAvailable(RoleManager.ROLE_SMS)) {
alertTitle.value = alertTitleText
alertMessage.value = messagingRoleNotAvailableMessage
showAlert.value = true
}
else
if (!roleManager.isRoleHeld(RoleManager.ROLE_SMS))
messagingRoleRequest.launch(roleManager.createRequestRoleIntent(RoleManager.ROLE_SMS))
} else {
try {
messagingRoleRequest.launch(Intent("android.settings.MANAGE_DEFAULT_APPS_SETTINGS"))
} catch (e: ActivityNotFoundException) {
Log.e(TAG, "ActivityNotFound exception: ${e.message}")
}
}
}
)
}
}
@Composable @Composable
fun Debug() { fun Debug() {
val debugTitle = stringResource(R.string.debug) val debugTitle = stringResource(R.string.debug)
@@ -1199,8 +1258,12 @@ private fun SettingsContent(
UserAgent() UserAgent()
UniqueContactUri() UniqueContactUri()
AudioSettings(navController) AudioSettings(navController)
if (VERSION.SDK_INT >= 29) if (VERSION.SDK_INT >= 29) {
DefaultDialer() DefaultDialer()
val defaultDialer by viewModel.defaultDialer.collectAsState()
if (defaultDialer)
DefaultMessaging()
}
BatteryOptimizations() BatteryOptimizations()
DarkTheme() DarkTheme()
if (VERSION.SDK_INT >= 31) if (VERSION.SDK_INT >= 31)
@@ -30,6 +30,7 @@ class SettingsViewModel: ViewModel() {
val colorblind = MutableStateFlow(false) val colorblind = MutableStateFlow(false)
val proximitySensing = MutableStateFlow(false) val proximitySensing = MutableStateFlow(false)
val defaultDialer = MutableStateFlow(false) val defaultDialer = MutableStateFlow(false)
val defaultMessaging = MutableStateFlow(false)
val debug = MutableStateFlow(false) val debug = MutableStateFlow(false)
val sipTrace = MutableStateFlow(false) val sipTrace = MutableStateFlow(false)
@@ -76,6 +77,7 @@ class SettingsViewModel: ViewModel() {
if (Build.VERSION.SDK_INT >= 29) { if (Build.VERSION.SDK_INT >= 29) {
val roleManager = ctx.getSystemService(ROLE_SERVICE) as RoleManager val roleManager = ctx.getSystemService(ROLE_SERVICE) as RoleManager
defaultDialer.value = roleManager.isRoleHeld(RoleManager.ROLE_DIALER) defaultDialer.value = roleManager.isRoleHeld(RoleManager.ROLE_DIALER)
defaultMessaging.value = roleManager.isRoleHeld(RoleManager.ROLE_SMS)
} }
debug.value = Config.variable("log_level") == "0" debug.value = Config.variable("log_level") == "0"
@@ -0,0 +1,38 @@
package com.tutpro.baresip
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.provider.Telephony
class SmsReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (intent.action == Telephony.Sms.Intents.SMS_DELIVER_ACTION) {
val messages = Telephony.Sms.Intents.getMessagesFromIntent(intent)
if (messages.isEmpty()) return
val sender = messages[0].displayOriginatingAddress ?: return
val body = messages.joinToString("") { it.displayMessageBody ?: "" }
val timestamp = messages[0].timestampMillis
Log.d(TAG, "Received SMS from $sender: $body")
val mobileUa = BaresipService.uas.value.find { it.account.isMobile }
if (mobileUa != null) {
// Notify Service for history update, notification, and alert sound
if (BaresipService.isServiceRunning) {
BaresipService.instance?.handleIncomingMessage(mobileUa.uap, "tel:$sender", body, timestamp)
} else {
// Service not running, at least save to history
val aor = mobileUa.account.aor
Message(aor, "tel:$sender", body, timestamp, MESSAGE_DOWN, 0, "", true).add()
mobileUa.account.unreadMessages = true
}
}
}
}
companion object {
private const val TAG = "SmsReceiver"
}
}
+26 -17
View File
@@ -1404,6 +1404,32 @@ object Utils {
return null return null
} }
fun sendSms(ctx: Context, destination: String, message: String): Boolean {
return try {
val smsManager = if (Build.VERSION.SDK_INT >= 31) {
ctx.getSystemService(android.telephony.SmsManager::class.java)
} else {
@Suppress("DEPRECATION")
android.telephony.SmsManager.getDefault()
}
smsManager.sendTextMessage(destination, null, message, null, null)
true
} catch (e: Exception) {
Log.e(TAG, "Failed to send SMS: ${e.message}")
false
}
}
fun isDefaultSmsApp(ctx: Context): Boolean {
return if (Build.VERSION.SDK_INT >= 29) {
val roleManager = ctx.getSystemService(ROLE_SERVICE) as RoleManager
roleManager.isRoleHeld(RoleManager.ROLE_SMS)
} else {
@Suppress("DEPRECATION")
android.provider.Telephony.Sms.getDefaultSmsPackage(ctx) == ctx.packageName
}
}
@Suppress("unused") @Suppress("unused")
fun listFilesInDirectory(directoryPath: String): List<File> { fun listFilesInDirectory(directoryPath: String): List<File> {
val directory = File(directoryPath) val directory = File(directoryPath)
@@ -1439,21 +1465,4 @@ object Utils {
} }
Log.e(TAG, "--------------------------------------") Log.e(TAG, "--------------------------------------")
} }
fun sendSms(ctx: Context, destination: String, message: String): Boolean {
return try {
val smsManager = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
ctx.getSystemService(android.telephony.SmsManager::class.java)
} else {
@Suppress("DEPRECATION")
android.telephony.SmsManager.getDefault()
}
smsManager.sendTextMessage(destination, null, message, null, null)
true
} catch (e: Exception) {
Log.e(TAG, "Failed to send SMS: ${e.message}")
false
}
}
} }
+8 -2
View File
@@ -294,7 +294,6 @@
%2$s puheluhistoriasta\? %2$s puheluhistoriasta\?
</string> </string>
<string name="calls_delete_question">Haluatko poistaa \'%1$s\' %2$s puheluhistoriasta\?</string> <string name="calls_delete_question">Haluatko poistaa \'%1$s\' %2$s puheluhistoriasta\?</string>
<string name="no_sms_messaging">SMS-tekstiviestitystä ei ole vielä toteutettu.</string>
<string name="disable_history">Poista käytöstä</string> <string name="disable_history">Poista käytöstä</string>
<string name="enable_history">Ota käyttöön</string> <string name="enable_history">Ota käyttöön</string>
<string name="delete_history_alert">Haluatko tyhjentää tilin \'%1$s\' puheluhistorian\?</string> <string name="delete_history_alert">Haluatko tyhjentää tilin \'%1$s\' puheluhistorian\?</string>
@@ -343,7 +342,14 @@
ja pääsyä verkkoon.</string> ja pääsyä verkkoon.</string>
<string name="default_phone_app">Oletus puhelusovellus</string> <string name="default_phone_app">Oletus puhelusovellus</string>
<string name="dialer_role_not_available">Oletus puhelusovellusrooli ei ole saatavana</string> <string name="dialer_role_not_available">Oletus puhelusovellusrooli ei ole saatavana</string>
<string name="default_phone_app_help">Jos merkitty, baresip on oletus puhelusovellus.</string> <string name="default_phone_app_help">Jos merkitty, baresip on oletus puhelusovellus</string>
<string name="default_messaging_app">Oletus tekstiviestisovellus</string>
<string name="default_messaging_app_help">Jos merkitty, baresip on tekstiviestien
oletussovellus</string>
<string name="messaging_role_not_available">Oletus tekstiviestisovellusrooli ei ole
saatavana</string>
<string name="enable_default_messaging">Ole hyvä ja merkitse \'Oletus tekstiviestisovellus\'
baresip asetuksissa</string>
<string name="listen_address">Kuunteluosoite</string> <string name="listen_address">Kuunteluosoite</string>
<string name="listen_address_help">IP-osoite ja portti muotoa <string name="listen_address_help">IP-osoite ja portti muotoa
\'osoite:portti\', missä baresip kuuntelee sisään tulevia \'osoite:portti\', missä baresip kuuntelee sisään tulevia
+4 -1
View File
@@ -280,7 +280,6 @@
<string name="calls_add_delete_question">Do you want to add \'%1$s\' to contacts or delete <string name="calls_add_delete_question">Do you want to add \'%1$s\' to contacts or delete
%2$s from call history\?</string> %2$s from call history\?</string>
<string name="calls_delete_question">Do you want to delete \'%1$s\' %2$s from call history\?</string> <string name="calls_delete_question">Do you want to delete \'%1$s\' %2$s from call history\?</string>
<string name="no_sms_messaging">Sorry, no SMS messaging yet.</string>
<string name="disable_history">Disable</string> <string name="disable_history">Disable</string>
<string name="enable_history">Enable</string> <string name="enable_history">Enable</string>
<string name="delete_history_alert">Do you want to delete call history of account \'%1$s\'\?</string> <string name="delete_history_alert">Do you want to delete call history of account \'%1$s\'\?</string>
@@ -328,6 +327,10 @@
<string name="default_phone_app">Default Phone App</string> <string name="default_phone_app">Default Phone App</string>
<string name="dialer_role_not_available">Default phone app role is not available</string> <string name="dialer_role_not_available">Default phone app role is not available</string>
<string name="default_phone_app_help">If checked, baresip is the default phone app.</string> <string name="default_phone_app_help">If checked, baresip is the default phone app.</string>
<string name="default_messaging_app">Default Messaging App</string>
<string name="default_messaging_app_help">If checked, baresip is the default messaging app for SMS/MMS.</string>
<string name="messaging_role_not_available">Default messaging app role is not available</string>
<string name="enable_default_messaging">Please enable Default Messaging App in Settings</string>
<string name="listen_address">Listen Address</string> <string name="listen_address">Listen Address</string>
<string name="listen_address_help">IP address and port of form \'address:port\' at which baresip listens <string name="listen_address_help">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 for incoming SIP requests. If IP address is an IPv6 address, it must be written inside