diff --git a/README.md b/README.md
index f617e3c7..a69d33da 100644
--- a/README.md
+++ b/README.md
@@ -54,6 +54,7 @@ If the app rejects calls from contacts on Android 10, allow _Contacts_ permissio
* CALL_SCREENING - block or allow call
* READ_CALL_LOG - check you have called/answered the number and count times the number have called you in X minutes
* READ_SMS - check you have sent a message to the number and you received a message from the number
+* SEND_SMS - send a message to the number if the call is rejected
* NOTIFICATION_LISTENER - find mobile numbers in messages
* READ_PHONE_STATE - check on which SIM the number is calling
* RECEIVE_BOOT_COMPLETED - persist clean expired numbers job across reboots
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index c8d94e7f..e784c36a 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -83,4 +83,10 @@ dependencies {
ksp(libs.androidx.room.compiler)
implementation(libs.google.libphonenumber)
implementation(libs.guardianproject.panic)
+ implementation("androidx.compose.ui:ui:1.4.0")
+ implementation("androidx.compose.material3:material3:1.0.0-alpha13")
+ implementation("androidx.compose.ui:ui-tooling-preview:1.4.0")
+ implementation("androidx.compose.runtime:runtime-livedata:1.4.0")
+ implementation("androidx.activity:activity-compose:1.4.0")
+ implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.4.0")
}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index e306151f..7416f128 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -6,6 +6,7 @@
+
@@ -110,5 +111,7 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/java/me/lucky/silence/MainActivity.kt b/app/src/main/java/me/lucky/silence/MainActivity.kt
index f53eab68..6d0919ce 100644
--- a/app/src/main/java/me/lucky/silence/MainActivity.kt
+++ b/app/src/main/java/me/lucky/silence/MainActivity.kt
@@ -1,5 +1,8 @@
package me.lucky.silence
+import android.Manifest
+import android.app.AlertDialog
+import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import androidx.activity.ComponentActivity
@@ -10,13 +13,26 @@ import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.material3.lightColorScheme
+import androidx.core.app.ActivityCompat
+import androidx.core.content.ContextCompat
import androidx.navigation.compose.rememberNavController
import me.lucky.silence.ui.App
+// Define a constant to identify the SEND_SMS permission request
+private const val MY_PERMISSIONS_REQUEST_SEND_SMS = 0
+
open class MainActivity : ComponentActivity() {
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
NotificationManager(this).createNotificationChannels()
+
+ // Check and request SMS permission
+ if (ContextCompat.checkSelfPermission(this, Manifest.permission.SEND_SMS) != PackageManager.PERMISSION_GRANTED) {
+ // If not, request the permission
+ ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.SEND_SMS), MY_PERMISSIONS_REQUEST_SEND_SMS)
+ }
+
setContent {
val isAndroid12OrLater = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
val colorScheme = when {
@@ -30,4 +46,30 @@ open class MainActivity : ComponentActivity() {
}
}
}
-}
+
+ // This method is called when the user responds to the permission request
+ override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults)
+ when (requestCode) {
+ // If the result is for the SEND_SMS permission request
+ MY_PERMISSIONS_REQUEST_SEND_SMS -> {
+ // If the permission is granted
+ if ((grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED)) {
+ // Show a dialog to check Settings
+ AlertDialog.Builder(this)
+ .setTitle("Check Settings")
+ .setMessage("You can set up an SMS notification in settings to be sent to a blocked number when it's blocked by the app.")
+ .setPositiveButton("OK") { dialog, _ ->
+ // Dismiss the dialog when the OK button is clicked
+ dialog.dismiss()
+ }
+ .show()
+ }
+ return
+ }
+ else -> {
+ // Ignore all other requests.
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/me/lucky/silence/NotificationManager.kt b/app/src/main/java/me/lucky/silence/NotificationManager.kt
index 58076876..25ed588a 100644
--- a/app/src/main/java/me/lucky/silence/NotificationManager.kt
+++ b/app/src/main/java/me/lucky/silence/NotificationManager.kt
@@ -6,6 +6,8 @@ import androidx.core.app.NotificationChannelCompat
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.core.app.Person
+import android.telephony.SmsManager
+
class NotificationManager(private val ctx: Context) {
companion object {
@@ -43,4 +45,27 @@ class NotificationManager(private val ctx: Context) {
)
} catch (_: SecurityException) {}
}
-}
\ No newline at end of file
+
+
+// Function to send an SMS message
+ fun sendSMS(phoneNumber: String, message: String) {
+ // Get the default instance of the SmsManager
+ val smsManager = SmsManager.getDefault()
+ // Send a text message to the provided phone number
+ smsManager.sendTextMessage(phoneNumber, null, message, null, null)
+ }
+
+// Function to send an SMS to a blocked call
+fun smsBlockedCall(tel: String, sim: Sim?) {
+ // Get the SMS message from Preferences
+ val prefs = Preferences(ctx)
+ val message = prefs.smsMessage
+
+ // If the message is not empty, send an SMS to the blocked number
+ if (!message.isNullOrEmpty()) {
+ sendSMS(tel, message)
+ }
+
+
+
+}}
\ No newline at end of file
diff --git a/app/src/main/java/me/lucky/silence/Preferences.kt b/app/src/main/java/me/lucky/silence/Preferences.kt
index 893b6abc..b5f8eff9 100644
--- a/app/src/main/java/me/lucky/silence/Preferences.kt
+++ b/app/src/main/java/me/lucky/silence/Preferences.kt
@@ -20,6 +20,7 @@ class Preferences(ctx: Context) {
const val REPEATED_BURST_TIMEOUT = "repeated_burst_timeout"
const val MESSAGES = "messages"
const val MESSAGES_TEXT_TTL = "messages_text_ttl"
+ const val SMS_MESSAGE = "sms_message"
const val RESPONSE_OPTIONS = "call_screening_response_options"
const val UNKNOWN_NUMBERS_CHECKED = "unknown_numbers_checked"
@@ -131,6 +132,10 @@ class Preferences(ctx: Context) {
var regexPattern: String?
get() = prefs.getString(REGEX_PATTERN, "")
set(value) = prefs.edit { putString(REGEX_PATTERN, value) }
+
+ var smsMessage: String?
+ get() = prefs.getString(SMS_MESSAGE, "")
+ set(value) = prefs.edit { putString(SMS_MESSAGE, value) }
}
enum class Contact(val value: Int) {
diff --git a/app/src/main/java/me/lucky/silence/screening/CallScreeningService.kt b/app/src/main/java/me/lucky/silence/screening/CallScreeningService.kt
index bd2e2d2a..b4cc8bb5 100644
--- a/app/src/main/java/me/lucky/silence/screening/CallScreeningService.kt
+++ b/app/src/main/java/me/lucky/silence/screening/CallScreeningService.kt
@@ -113,6 +113,9 @@ class CallScreeningService : CallScreeningService() {
}
}
notificationManager.notifyBlockedCall(tel ?: return, sim)
+
+ // Here's where you would call smsBlockedCall
+ notificationManager.smsBlockedCall(tel ?: return, sim)
}
respondToCall(callDetails, response)
}
diff --git a/app/src/main/java/me/lucky/silence/ui/MainScreen.kt b/app/src/main/java/me/lucky/silence/ui/MainScreen.kt
index 2d50539a..d34aba17 100644
--- a/app/src/main/java/me/lucky/silence/ui/MainScreen.kt
+++ b/app/src/main/java/me/lucky/silence/ui/MainScreen.kt
@@ -10,13 +10,19 @@ import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
+import androidx.compose.material3.AlertDialog
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
+import androidx.compose.material3.TextButton
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
@@ -37,6 +43,7 @@ import me.lucky.silence.ui.common.ToggleableButton
@Composable
fun ModuleList(modules: List) {
+
LazyColumn {
items(modules) { module ->
if ((module.getPreference != null) && (module.setPreference != null) && (module.navigation != null)) {
@@ -83,6 +90,7 @@ fun MainScreen(
onNavigateToSettings: () -> Unit,
onNavigateToRegex: () -> Unit,
) {
+
fun getContactedPermissions(): Array {
val contacted = prefs.contacted
val permissions = mutableSetOf()
@@ -196,6 +204,9 @@ fun MainScreen(
setPreference = { prefs.isBlockEnabled = it },
),
)
+
+
+
Scaffold(topBar = {
TopAppBar(title = { Text(text = stringResource(R.string.app_name)) }, actions = {
IconButton(onClick = onNavigateToSettings) {
@@ -221,6 +232,8 @@ fun MainScreen(
)
}
})
+
+
}
@Preview
diff --git a/app/src/main/java/me/lucky/silence/ui/SettingsScreen.kt b/app/src/main/java/me/lucky/silence/ui/SettingsScreen.kt
index 8f5fceab..018343be 100644
--- a/app/src/main/java/me/lucky/silence/ui/SettingsScreen.kt
+++ b/app/src/main/java/me/lucky/silence/ui/SettingsScreen.kt
@@ -1,9 +1,20 @@
package me.lucky.silence.ui
+import android.app.AlertDialog
import android.content.Context
+import androidx.compose.foundation.layout.Column
import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.AlertDialog
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextButton
+import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import me.lucky.silence.ControlReceiver
import me.lucky.silence.Preferences
@@ -17,6 +28,7 @@ import me.lucky.silence.ui.common.Screen
@Composable
fun SettingsScreen(ctx: Context, prefs: Preferences, onBackPressed: () -> Boolean) {
+ var showSmsDialog by remember { mutableStateOf(false) }
val preferenceList = listOf(
Preference(
getValue = { Utils.isComponentEnabled(ctx, ControlReceiver::class.java) },
@@ -73,17 +85,75 @@ fun SettingsScreen(ctx: Context, prefs: Preferences, onBackPressed: () -> Boolea
},
name = R.string.settings_skip_notification,
description = R.string.settings_skip_notification_description,
+ ),
+ Preference(
+ getValue = { prefs.smsMessage!!.isNotEmpty() },
+ setValue = { isChecked ->
+ if (isChecked) {
+ showSmsDialog = true
+ } else {
+ prefs.smsMessage = ""
+ }
+ },
+ name = R.string.enter_sms_message,
+ description = R.string.enter_sms_message_description,
)
)
+
Screen(title = R.string.settings,
onBackPressed = onBackPressed,
content = { PreferenceList(preferenceList) })
+ if (showSmsDialog) {
+ SmsMessageDialog(
+ onDismiss = { showSmsDialog = false },
+ onConfirm = { message ->
+ prefs.smsMessage = message
+ showSmsDialog = false
+ }
+ )
+ }
}
+
@Preview
@Composable
fun SettingsScreenPreview() {
MaterialTheme {
SettingsScreen(LocalContext.current, Preferences(LocalContext.current)) { true }
}
-}
\ No newline at end of file
+
+}
+
+@Composable
+fun SmsMessageDialog(onDismiss: () -> Unit, onConfirm: (String) -> Unit) {
+ val context = LocalContext.current
+ val input = remember { mutableStateOf("") }
+
+ AlertDialog(
+ onDismissRequest = { onDismiss() },
+ title = {
+ Text(text = stringResource(id = R.string.enter_sms_message))
+ },
+ text = {
+ Column {
+ Text(text = stringResource(id = R.string.enter_sms_message_description))
+ TextField(
+ value = input.value,
+ onValueChange = { input.value = it }
+ )
+ }
+ },
+ confirmButton = {
+ TextButton(onClick = {
+ onConfirm(input.value)
+ }) {
+ Text(text = stringResource(id = android.R.string.ok))
+ }
+ },
+ dismissButton = {
+ TextButton(onClick = { onDismiss() }) {
+ Text(text = stringResource(id = android.R.string.cancel))
+ }
+ }
+ )
+}
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 9c01b3af..58e74c6f 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -86,4 +86,6 @@
Back
Off
On
+ SMS Message
+ Enter the message to send when a call is blocked. Leave empty to not send an SMS.
\ No newline at end of file
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index a4d74b0c..76e58826 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -1,5 +1,5 @@
[versions]
-agp = "8.3.2"
+agp = "8.4.1"
kotlin = "1.9.23"
coreKtx = "1.12.0"
junit = "4.13.2"
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 645616a1..b5460a39 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
#Tue Jun 14 21:51:27 MSK 2022
distributionBase=GRADLE_USER_HOME
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME