Added initial support for sending and receiving SMS messages
This commit is contained in:
@ -36,6 +36,12 @@
|
||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
|
||||
<uses-permission android:name="android.permission.SEND_SMS"
|
||||
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
|
||||
android:name="android.hardware.telephony"
|
||||
@ -78,10 +84,28 @@
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<action android:name="android.intent.action.DIAL" />
|
||||
<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.BROWSABLE" />
|
||||
<data android:scheme="tel" />
|
||||
<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>
|
||||
</activity>
|
||||
|
||||
@ -121,6 +145,42 @@
|
||||
</intent-filter>
|
||||
</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
|
||||
android:name=".BootCompletedReceiver"
|
||||
android:enabled="true"
|
||||
|
||||
@ -1279,10 +1279,29 @@ class BaresipService: Service() {
|
||||
error
|
||||
}
|
||||
|
||||
val timeStamp = 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()
|
||||
handleIncomingMessage(uap, peerUri, text, System.currentTimeMillis())
|
||||
}
|
||||
|
||||
@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
|
||||
|
||||
if (!Utils.isVisible()) {
|
||||
@ -1339,7 +1358,7 @@ class BaresipService: Service() {
|
||||
|
||||
val saveIntent = Intent(this, BaresipService::class.java)
|
||||
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 saveAction = NotificationCompat.Action.Builder(
|
||||
R.drawable.ic_notification_save,
|
||||
@ -1349,7 +1368,7 @@ class BaresipService: Service() {
|
||||
|
||||
val deleteIntent = Intent(this, BaresipService::class.java)
|
||||
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 deleteAction = NotificationCompat.Action.Builder(
|
||||
R.drawable.ic_notification_delete,
|
||||
|
||||
@ -349,9 +349,16 @@ private fun Calls(
|
||||
lastButtonText.value = ctx.getString(R.string.send_message)
|
||||
lastAction.value = {
|
||||
if (account.isMobile) {
|
||||
alertTitle.value = ctx.getString(R.string.notice)
|
||||
alertMessage.value = ctx.getString(R.string.no_sms_messaging)
|
||||
showAlert.value = true
|
||||
if (!Utils.isDefaultSmsApp(ctx)) {
|
||||
alertTitle.value = ctx.getString(R.string.notice)
|
||||
alertMessage.value = ctx.getString(R.string.enable_default_messaging)
|
||||
showAlert.value = true
|
||||
} else {
|
||||
if (ua != null) {
|
||||
handleIntent(ctx, viewModel, intent, "message")
|
||||
navController.navigateUp()
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
if (ua != null) {
|
||||
|
||||
37
app/src/main/kotlin/com/tutpro/baresip/ComposeActivity.kt
Normal file
37
app/src/main/kotlin/com/tutpro/baresip/ComposeActivity.kt
Normal file
@ -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 = {
|
||||
if (ua != null) {
|
||||
if (ua.account.isMobile) {
|
||||
alertTitle.value = ctx.getString(R.string.notice)
|
||||
alertMessage.value = ctx.getString(R.string.no_sms_messaging)
|
||||
showAlert.value = true
|
||||
if (!Utils.isDefaultSmsApp(ctx)) {
|
||||
alertTitle.value = ctx.getString(R.string.notice)
|
||||
alertMessage.value = ctx.getString(R.string.enable_default_messaging)
|
||||
showAlert.value = true
|
||||
} else {
|
||||
handleIntent(ctx, viewModel, intent, "message")
|
||||
navController.navigateUp()
|
||||
}
|
||||
}
|
||||
else {
|
||||
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.RESULT_OK
|
||||
import android.app.KeyguardManager
|
||||
import android.app.role.RoleManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.res.Configuration
|
||||
@ -808,17 +809,26 @@ private fun BottomBar(ctx: Context, viewModel: ViewModel, navController: NavCont
|
||||
IconButton(
|
||||
enabled = aor.isNotEmpty(),
|
||||
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")
|
||||
},
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.size(buttonSize)
|
||||
modifier = Modifier.weight(1f).size(buttonSize)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.AutoMirrored.Filled.Chat,
|
||||
contentDescription = null,
|
||||
Modifier.size(buttonSize),
|
||||
tint = if (hasUnreadMessages) MaterialTheme.colorScheme.error else MaterialTheme.colorScheme.secondary
|
||||
tint = if (hasUnreadMessages)
|
||||
MaterialTheme.colorScheme.error
|
||||
else
|
||||
MaterialTheme.colorScheme.secondary
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
127
app/src/main/kotlin/com/tutpro/baresip/MmsReceiver.kt
Normal file
127
app/src/main/kotlin/com/tutpro/baresip/MmsReceiver.kt
Normal file
@ -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))
|
||||
dialerRoleRequest.launch(roleManager.createRequestRoleIntent(RoleManager.ROLE_DIALER))
|
||||
} else {
|
||||
viewModel.defaultMessaging.value = false
|
||||
try {
|
||||
dialerRoleRequest.launch(Intent("android.settings.MANAGE_DEFAULT_APPS_SETTINGS"))
|
||||
} 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
|
||||
fun Debug() {
|
||||
val debugTitle = stringResource(R.string.debug)
|
||||
@ -1199,8 +1258,12 @@ private fun SettingsContent(
|
||||
UserAgent()
|
||||
UniqueContactUri()
|
||||
AudioSettings(navController)
|
||||
if (VERSION.SDK_INT >= 29)
|
||||
if (VERSION.SDK_INT >= 29) {
|
||||
DefaultDialer()
|
||||
val defaultDialer by viewModel.defaultDialer.collectAsState()
|
||||
if (defaultDialer)
|
||||
DefaultMessaging()
|
||||
}
|
||||
BatteryOptimizations()
|
||||
DarkTheme()
|
||||
if (VERSION.SDK_INT >= 31)
|
||||
|
||||
@ -30,6 +30,7 @@ class SettingsViewModel: ViewModel() {
|
||||
val colorblind = MutableStateFlow(false)
|
||||
val proximitySensing = MutableStateFlow(false)
|
||||
val defaultDialer = MutableStateFlow(false)
|
||||
val defaultMessaging = MutableStateFlow(false)
|
||||
val debug = MutableStateFlow(false)
|
||||
val sipTrace = MutableStateFlow(false)
|
||||
|
||||
@ -76,6 +77,7 @@ class SettingsViewModel: ViewModel() {
|
||||
if (Build.VERSION.SDK_INT >= 29) {
|
||||
val roleManager = ctx.getSystemService(ROLE_SERVICE) as RoleManager
|
||||
defaultDialer.value = roleManager.isRoleHeld(RoleManager.ROLE_DIALER)
|
||||
defaultMessaging.value = roleManager.isRoleHeld(RoleManager.ROLE_SMS)
|
||||
}
|
||||
|
||||
debug.value = Config.variable("log_level") == "0"
|
||||
|
||||
38
app/src/main/kotlin/com/tutpro/baresip/SmsReceiver.kt
Normal file
38
app/src/main/kotlin/com/tutpro/baresip/SmsReceiver.kt
Normal file
@ -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"
|
||||
}
|
||||
}
|
||||
@ -1404,6 +1404,32 @@ object Utils {
|
||||
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")
|
||||
fun listFilesInDirectory(directoryPath: String): List<File> {
|
||||
val directory = File(directoryPath)
|
||||
@ -1439,21 +1465,4 @@ object Utils {
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -294,7 +294,6 @@
|
||||
%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="enable_history">Ota käyttöön</string>
|
||||
<string name="delete_history_alert">Haluatko tyhjentää tilin \'%1$s\' puheluhistorian\?</string>
|
||||
@ -343,7 +342,14 @@
|
||||
ja pääsyä verkkoon.</string>
|
||||
<string name="default_phone_app">Oletus puhelusovellus</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_help">IP-osoite ja portti muotoa
|
||||
\'osoite:portti\', missä baresip kuuntelee sisään tulevia
|
||||
|
||||
@ -280,7 +280,6 @@
|
||||
<string name="calls_add_delete_question">Do you want to add \'%1$s\' to contacts or delete
|
||||
%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="enable_history">Enable</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="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_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_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
|
||||
|
||||
Reference in New Issue
Block a user