From c6e8fadf9c8aab0934045e922ca5eed0c96d9183 Mon Sep 17 00:00:00 2001 From: Gert-Jan <138776253+Pruts-Hacker@users.noreply.github.com> Date: Fri, 24 May 2024 11:11:42 +0200 Subject: [PATCH 1/2] Added SMS Notification Feature and Permission Request for Silence/app Summary This update introduces a new feature to the Silence app that enhances how blocked calls are managed. It includes sending an SMS to blocked numbers and various user interface improvements to inform the user of blocked calls and necessary permissions. Features and Improvements SMS Notification to Blocked Numbers: When a number is blocked, the app automatically sends an SMS to the blocked number. The SMS requests the caller to send their name and reason for calling. Permission Request: Implemented a permission request prompt for the user to allow the app to send SMS messages. Ensures compliance with user privacy and permission protocols. User Notification: Added a popup notification that prompts the user to check their messages whenever a call is blocked. Enhances user awareness and ensures they are informed about the blocked calls and incoming messages. Bug Fixes None in this update. UI/UX Changes Popup notification added for informing the user about blocked calls and the need to check their messages. Code Refactoring and Optimization No major refactoring or optimization in this update. Documentation Updated the user guide to include information about the new SMS notification feature and permission request. Added in-app tips to help users understand the new functionality. Please review the changes and provide feedback. Looking forward to your comments and approval. --- README.md | 1 + app/src/main/AndroidManifest.xml | 1 + .../java/me/lucky/silence/MainActivity.kt | 44 ++++++++++++++++++- .../me/lucky/silence/NotificationManager.kt | 22 ++++++++++ .../silence/screening/CallScreeningService.kt | 3 ++ gradle/libs.versions.toml | 2 +- gradle/wrapper/gradle-wrapper.properties | 2 +- 7 files changed, 72 insertions(+), 3 deletions(-) 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/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e306151f..cc5fd9e0 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -6,6 +6,7 @@ + diff --git a/app/src/main/java/me/lucky/silence/MainActivity.kt b/app/src/main/java/me/lucky/silence/MainActivity.kt index f53eab68..ee436e1a 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 SMS messages + AlertDialog.Builder(this) + .setTitle("Check SMS Messages") + .setMessage("Please check your SMS messages when you receive a notification.") + .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..fcb46158 100644 --- a/app/src/main/java/me/lucky/silence/NotificationManager.kt +++ b/app/src/main/java/me/lucky/silence/NotificationManager.kt @@ -6,6 +6,7 @@ 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 +44,25 @@ class NotificationManager(private val ctx: Context) { ) } catch (_: SecurityException) {} } + + +// 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 + // The second parameter is the service center address, null means use the current default SMSC + // The third parameter is the message to send + // The fourth and fifth parameters are PendingIntent objects to be broadcast when the message is sent and delivered, respectively. We don't need these, so we set them to null + smsManager.sendTextMessage(phoneNumber, null, message, null, null) + } + +// Function to send an SMS to a blocked call + fun smsBlockedCall(tel: String, sim: Sim?) { + // Send an SMS to the blocked number so the caller knows they have been blocked. + sendSMS(tel, "Your number has been blocked because it is not known to the person you tried to call. If you want to contact this person, please send a message with your name and reason first.") + } + + + } \ No newline at end of file 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/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 From 1fa38c7e5e063b257f209211d465a04a91d4daee Mon Sep 17 00:00:00 2001 From: Gert-Jan <138776253+Pruts-Hacker@users.noreply.github.com> Date: Mon, 3 Jun 2024 14:34:39 +0200 Subject: [PATCH 2/2] Updated SMS Notification Feature for Silence/app Summary I updated the SMS Notification feature so the user can turn it on or off in settings and write their own message so it can be in their own language. I also changed the message pop up to inform the user of the SMS update. --- app/build.gradle.kts | 6 ++ app/src/main/AndroidManifest.xml | 2 + .../java/me/lucky/silence/MainActivity.kt | 6 +- .../me/lucky/silence/NotificationManager.kt | 17 +++-- .../main/java/me/lucky/silence/Preferences.kt | 5 ++ .../java/me/lucky/silence/ui/MainScreen.kt | 13 ++++ .../me/lucky/silence/ui/SettingsScreen.kt | 72 ++++++++++++++++++- app/src/main/res/values/strings.xml | 2 + 8 files changed, 112 insertions(+), 11 deletions(-) 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 cc5fd9e0..7416f128 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -111,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 ee436e1a..6d0919ce 100644 --- a/app/src/main/java/me/lucky/silence/MainActivity.kt +++ b/app/src/main/java/me/lucky/silence/MainActivity.kt @@ -55,10 +55,10 @@ open class MainActivity : ComponentActivity() { MY_PERMISSIONS_REQUEST_SEND_SMS -> { // If the permission is granted if ((grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED)) { - // Show a dialog to check SMS messages + // Show a dialog to check Settings AlertDialog.Builder(this) - .setTitle("Check SMS Messages") - .setMessage("Please check your SMS messages when you receive a notification.") + .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() diff --git a/app/src/main/java/me/lucky/silence/NotificationManager.kt b/app/src/main/java/me/lucky/silence/NotificationManager.kt index fcb46158..25ed588a 100644 --- a/app/src/main/java/me/lucky/silence/NotificationManager.kt +++ b/app/src/main/java/me/lucky/silence/NotificationManager.kt @@ -8,6 +8,7 @@ import androidx.core.app.NotificationManagerCompat import androidx.core.app.Person import android.telephony.SmsManager + class NotificationManager(private val ctx: Context) { companion object { private const val CHANNEL_BLOCKED_CALLS_ID = "blocked_calls" @@ -51,18 +52,20 @@ class NotificationManager(private val ctx: Context) { // Get the default instance of the SmsManager val smsManager = SmsManager.getDefault() // Send a text message to the provided phone number - // The second parameter is the service center address, null means use the current default SMSC - // The third parameter is the message to send - // The fourth and fifth parameters are PendingIntent objects to be broadcast when the message is sent and delivered, respectively. We don't need these, so we set them to null smsManager.sendTextMessage(phoneNumber, null, message, null, null) } // Function to send an SMS to a blocked call - fun smsBlockedCall(tel: String, sim: Sim?) { - // Send an SMS to the blocked number so the caller knows they have been blocked. - sendSMS(tel, "Your number has been blocked because it is not known to the person you tried to call. If you want to contact this person, please send a message with your name and reason first.") +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 +}} \ 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/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