Skip to content

Commit 3396398

Browse files
committed
Improve missing permissions handling
1 parent 865363a commit 3396398

File tree

4 files changed

+114
-46
lines changed

4 files changed

+114
-46
lines changed

app/src/main/java/com/addev/listaspam/MainActivity.kt

Lines changed: 60 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
package com.addev.listaspam
22

3+
import android.Manifest
34
import android.app.Activity
5+
import android.app.AlertDialog
46
import android.app.role.RoleManager
57
import android.content.Context
68
import android.content.Intent
79
import android.content.pm.PackageManager
10+
import android.net.Uri
811
import android.os.Build
912
import android.os.Bundle
13+
import android.provider.Settings
1014
import android.telecom.TelecomManager
1115
import android.widget.Toast
1216
import androidx.activity.result.ActivityResultLauncher
@@ -31,13 +35,7 @@ import com.addev.listaspam.utils.SpamUtils
3135
*/
3236
class MainActivity : AppCompatActivity() {
3337

34-
companion object {
35-
private const val REQUEST_PERMISSION_PHONE_STATE = 1
36-
}
37-
3838
private lateinit var intentLauncher: ActivityResultLauncher<Intent>
39-
private lateinit var recyclerView: RecyclerView
40-
private lateinit var adapter: CallLogAdapter
4139

4240
/**
4341
* Called when the activity is starting.
@@ -47,18 +45,37 @@ class MainActivity : AppCompatActivity() {
4745
super.onCreate(savedInstanceState)
4846
setContentView(R.layout.activity_main)
4947
setupWindowInsets()
50-
5148
setupIntentLauncher()
49+
}
50+
51+
/**
52+
* Initializes the activity by checking permissions, requesting the call screening role, and refreshing the call logs if the necessary permissions are granted.
53+
*/
54+
private fun init() {
5255
checkPermissionsAndRequest()
5356

5457
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
5558
requestCallScreeningRole()
5659
}
5760

58-
refreshCallLogs()
61+
if (ContextCompat.checkSelfPermission(
62+
this,
63+
Manifest.permission.READ_CALL_LOG
64+
) == PackageManager.PERMISSION_GRANTED
65+
) {
66+
refreshCallLogs()
67+
}
68+
}
69+
70+
/**
71+
* Called when the activity comes to the foreground.
72+
*/
73+
override fun onResume() {
74+
super.onResume()
75+
init()
5976
}
6077

61-
fun refreshCallLogs() {
78+
private fun refreshCallLogs() {
6279
val blockedNumbers = getBlockedNumbers()
6380
val callLogs = getCallLogs(this)
6481

@@ -112,28 +129,47 @@ class MainActivity : AppCompatActivity() {
112129
*/
113130
private fun checkPermissionsAndRequest() {
114131
val permissions = mutableListOf(
115-
android.Manifest.permission.READ_CALL_LOG,
116-
android.Manifest.permission.READ_PHONE_STATE
132+
Manifest.permission.READ_CALL_LOG,
133+
Manifest.permission.READ_PHONE_STATE,
134+
Manifest.permission.READ_CONTACTS,
135+
Manifest.permission.ANSWER_PHONE_CALLS,
117136
)
118-
119-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
120-
permissions.add(android.Manifest.permission.ANSWER_PHONE_CALLS)
121-
}
122-
123137
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
124-
permissions.add(android.Manifest.permission.POST_NOTIFICATIONS)
138+
permissions.add(Manifest.permission.POST_NOTIFICATIONS)
125139
}
126-
127140
val missingPermissions = permissions.filter {
128141
ContextCompat.checkSelfPermission(this, it) != PackageManager.PERMISSION_GRANTED
129142
}
130-
131143
if (missingPermissions.isNotEmpty()) {
132-
ActivityCompat.requestPermissions(
133-
this,
134-
missingPermissions.toTypedArray(),
135-
REQUEST_PERMISSION_PHONE_STATE
136-
)
144+
showPermissionToastAndRequest(missingPermissions)
145+
}
146+
}
147+
148+
private fun showPermissionToastAndRequest(missingPermissions: List<String>) {
149+
val permissionNames = missingPermissions.map { "- " + getPermissionName(it) }
150+
val message =
151+
"Los siguientes permisos son necesarios para que la aplicación funcione correctamente:\n\n${
152+
permissionNames.joinToString("\n")
153+
}\n\nPor favor, ve a los ajustes del sistema y otorga los permisos manualmente."
154+
AlertDialog.Builder(this)
155+
.setTitle("Permisos requeridos")
156+
.setMessage(message)
157+
.setPositiveButton("Ir a ajustes") { _, _ ->
158+
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
159+
intent.data = Uri.fromParts("package", packageName, null)
160+
startActivity(intent)
161+
}
162+
.show()
163+
}
164+
165+
private fun getPermissionName(permission: String): String {
166+
return when (permission) {
167+
Manifest.permission.READ_CALL_LOG -> "Acceso al registro de llamadas"
168+
Manifest.permission.READ_PHONE_STATE -> "Acceso al estado del teléfono"
169+
Manifest.permission.READ_CONTACTS -> "Acceso a los contactos"
170+
Manifest.permission.ANSWER_PHONE_CALLS -> "Responder llamadas telefónicas"
171+
Manifest.permission.POST_NOTIFICATIONS -> "Notificaciones"
172+
else -> permission
137173
}
138174
}
139175

app/src/main/java/com/addev/listaspam/calllog/CallLogAdapter.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ class CallLogAdapter(
2121
private val blockedNumbers: Set<String>
2222
) : RecyclerView.Adapter<CallLogAdapter.CallLogViewHolder>() {
2323

24-
val formatter = SimpleDateFormat("dd/MM/yyyy")
24+
val formatter = SimpleDateFormat("dd/MM/yyyy HH:ss")
2525

2626
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CallLogViewHolder {
2727
val view = LayoutInflater.from(context).inflate(R.layout.item_call_log, parent, false)

app/src/main/java/com/addev/listaspam/services/MyCallScreeningService.kt

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,6 @@ class MyCallScreeningService : CallScreeningService() {
1818

1919
private val spamUtils = SpamUtils()
2020

21-
companion object {
22-
private const val SPAM_PREFS = "SPAM_PREFS"
23-
private const val BLOCK_NUMBERS_KEY = "BLOCK_NUMBERS"
24-
}
25-
2621
/**
2722
* Called when an incoming call is being screened.
2823
* @param details Details of the incoming call.

app/src/main/java/com/addev/listaspam/utils/SpamUtils.kt

Lines changed: 53 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,16 @@ import android.app.NotificationChannel
55
import android.app.NotificationManager
66
import android.content.Context
77
import android.content.pm.PackageManager
8+
import android.database.Cursor
89
import android.os.Build
910
import android.os.Handler
1011
import android.os.Looper
12+
import android.provider.ContactsContract
1113
import android.widget.Toast
1214
import androidx.core.app.ActivityCompat
1315
import androidx.core.app.NotificationCompat
1416
import androidx.core.app.NotificationManagerCompat
17+
import androidx.core.content.ContextCompat
1518
import com.addev.listaspam.R
1619
import com.addev.listaspam.model.SpamData
1720
import kotlinx.coroutines.CoroutineScope
@@ -58,10 +61,16 @@ class SpamUtils {
5861
return@launch
5962
}
6063

64+
if (ContextCompat.checkSelfPermission(context, Manifest.permission.READ_CONTACTS) == PackageManager.PERMISSION_GRANTED) {
65+
if (isNumberInAgenda(context, number)) {
66+
handleNonSpamNumber(context, number, callback)
67+
return@launch
68+
}
69+
}
70+
6171
val spamCheckers = listOf(
6272
::checkListaSpam,
6373
::checkResponderono,
64-
// ::checkCleverDialer
6574
)
6675

6776
val isSpam = spamCheckers.any { checker ->
@@ -76,6 +85,48 @@ class SpamUtils {
7685
}
7786
}
7887

88+
/**
89+
* Normalizes a phone number by removing all non-digit characters.
90+
*
91+
* @param number The phone number to normalize.
92+
* @return The normalized phone number.
93+
*/
94+
private fun normalizePhoneNumber(number: String): String {
95+
return number.replace("\\D".toRegex(), "")
96+
}
97+
98+
/**
99+
* Checks if the given number exists in the user's contact list.
100+
* Ignores spaces and considers different number prefixes for comparison.
101+
*
102+
* @param context The context of the caller.
103+
* @param number The phone number to check.
104+
* @return True if the number is in the user's contact list, false otherwise.
105+
*/
106+
private fun isNumberInAgenda(context: Context, number: String): Boolean {
107+
val contentResolver = context.contentResolver
108+
val uri = ContactsContract.CommonDataKinds.Phone.CONTENT_URI
109+
val projection = arrayOf(ContactsContract.CommonDataKinds.Phone.NUMBER)
110+
val normalizedNumber = normalizePhoneNumber(number)
111+
112+
var cursor: Cursor? = null
113+
return try {
114+
cursor = contentResolver.query(uri, projection, null, null, null)
115+
cursor?.use {
116+
val numberIndex = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER)
117+
while (cursor.moveToNext()) {
118+
val contactNumber = normalizePhoneNumber(cursor.getString(numberIndex))
119+
if (normalizedNumber == contactNumber || normalizedNumber.endsWith(contactNumber) || contactNumber.endsWith(normalizedNumber)) {
120+
return true
121+
}
122+
}
123+
}
124+
false
125+
} finally {
126+
cursor?.close()
127+
}
128+
}
129+
79130
/**
80131
* Checks if a number is blocked locally in shared preferences.
81132
*
@@ -92,20 +143,6 @@ class SpamUtils {
92143
return false
93144
}
94145

95-
/**
96-
* Checks if a number is marked as spam on CleverDialer.
97-
*
98-
* @param number The phone number to check.
99-
* @return True if the number is marked as spam, false otherwise.
100-
*/
101-
private suspend fun checkCleverDialer(number: String): Boolean {
102-
val url = CLEVER_DIALER_URL_TEMPLATE.format(number)
103-
return checkUrlForSpam(
104-
url,
105-
".front-stars.stars-1 #star-full-black, .front-stars.stars-2 #star-full-black, .front-stars.stars-3 #star-full-black"
106-
)
107-
}
108-
109146
/**
110147
* Checks if a number is marked as spam on ListaSpam.
111148
*
@@ -116,7 +153,7 @@ class SpamUtils {
116153
val url = LISTA_SPAM_URL_TEMPLATE.format(number)
117154
return checkUrlForSpam(
118155
url,
119-
".data_top .phone_rating.result-3, .data_top .phone_rating.result-2, .data_top .phone_rating.result-1, alert-icon-big"
156+
".data_top .phone_rating.result-3, .data_top .phone_rating.result-2, .data_top .phone_rating.result-1, .alert-icon-big"
120157
)
121158
}
122159

0 commit comments

Comments
 (0)