first commit

This commit is contained in:
Andre Silva
2025-12-05 18:47:28 -03:00
parent 5ece01c17c
commit 9a08f1dec9
20 changed files with 725 additions and 0 deletions
+47
View File
@@ -0,0 +1,47 @@
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
}
android {
namespace = "com.callblocker"
compileSdk = 34
defaultConfig {
applicationId = "com.callblocker"
minSdk = 24
targetSdk = 34
versionCode = 1
versionName = "1.0"
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = "17"
}
buildFeatures {
viewBinding = true
}
}
dependencies {
implementation("androidx.core:core-ktx:1.12.0")
implementation("androidx.appcompat:appcompat:1.6.1")
implementation("com.google.android.material:material:1.11.0")
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
}
Vendored Executable
+6
View File
@@ -0,0 +1,6 @@
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in android SDK/tools/proguard/proguard-android.txt
# Keep the CallBlockerService
-keep class com.callblocker.CallBlockerService { *; }
+43
View File
@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<!-- Permissões necessárias -->
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.READ_CALL_LOG" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.ANSWER_PHONE_CALLS" />
<uses-permission android:name="android.permission.CALL_PHONE" />
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@drawable/ic_launcher"
android:supportsRtl="true"
android:theme="@style/Theme.CallBlocker"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true"
android:theme="@style/Theme.CallBlocker">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<!-- Serviço de triagem de chamadas (Android 10+) -->
<service
android:name=".CallBlockerService"
android:permission="android.permission.BIND_SCREENING_SERVICE"
android:exported="true">
<intent-filter>
<action android:name="android.telecom.CallScreeningService" />
</intent-filter>
</service>
</application>
</manifest>
+94
View File
@@ -0,0 +1,94 @@
package com.callblocker
import android.content.Context
import android.database.Cursor
import android.net.Uri
import android.os.Build
import android.provider.ContactsContract
import android.telecom.Call
import android.telecom.CallScreeningService
import androidx.annotation.RequiresApi
@RequiresApi(Build.VERSION_CODES.N)
class CallBlockerService : CallScreeningService() {
override fun onScreenCall(callDetails: Call.Details) {
val prefs = getSharedPreferences(MainActivity.PREFS_NAME, Context.MODE_PRIVATE)
val isBlockerEnabled = prefs.getBoolean(MainActivity.KEY_BLOCKER_ENABLED, false)
if (!isBlockerEnabled) {
// Bloqueio desativado - permitir todas as chamadas
respondToCall(callDetails, CallResponse.Builder().build())
return
}
val phoneNumber = getPhoneNumber(callDetails)
if (phoneNumber.isNullOrEmpty()) {
// Número desconhecido/oculto - bloquear
blockCall(callDetails)
return
}
if (isNumberInContacts(phoneNumber)) {
// Número está nos contatos - permitir
allowCall(callDetails)
} else {
// Número não está nos contatos - bloquear
blockCall(callDetails)
}
}
private fun getPhoneNumber(callDetails: Call.Details): String? {
val handle = callDetails.handle ?: return null
return handle.schemeSpecificPart
}
private fun isNumberInContacts(phoneNumber: String): Boolean {
val uri = Uri.withAppendedPath(
ContactsContract.PhoneLookup.CONTENT_FILTER_URI,
Uri.encode(phoneNumber)
)
var cursor: Cursor? = null
try {
cursor = contentResolver.query(
uri,
arrayOf(ContactsContract.PhoneLookup._ID),
null,
null,
null
)
return cursor != null && cursor.count > 0
} catch (e: Exception) {
e.printStackTrace()
return false
} finally {
cursor?.close()
}
}
private fun allowCall(callDetails: Call.Details) {
val response = CallResponse.Builder()
.setDisallowCall(false)
.setRejectCall(false)
.setSilenceCall(false)
.setSkipCallLog(false)
.setSkipNotification(false)
.build()
respondToCall(callDetails, response)
}
private fun blockCall(callDetails: Call.Details) {
val response = CallResponse.Builder()
.setDisallowCall(true)
.setRejectCall(true)
.setSilenceCall(true)
.setSkipCallLog(false)
.setSkipNotification(true)
.build()
respondToCall(callDetails, response)
}
}
+153
View File
@@ -0,0 +1,153 @@
package com.callblocker
import android.Manifest
import android.app.role.RoleManager
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import com.callblocker.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private lateinit var prefs: SharedPreferences
companion object {
const val PREFS_NAME = "CallBlockerPrefs"
const val KEY_BLOCKER_ENABLED = "blocker_enabled"
}
private val requiredPermissions = arrayOf(
Manifest.permission.READ_PHONE_STATE,
Manifest.permission.READ_CALL_LOG,
Manifest.permission.READ_CONTACTS,
Manifest.permission.ANSWER_PHONE_CALLS
)
private val permissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) { permissions ->
val allGranted = permissions.values.all { it }
if (allGranted) {
requestCallScreeningRole()
} else {
binding.switchBlocker.isChecked = false
updateStatus(false)
Toast.makeText(this, R.string.permission_denied, Toast.LENGTH_LONG).show()
}
}
private val roleRequestLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { result ->
if (result.resultCode == RESULT_OK) {
enableBlocker()
} else {
binding.switchBlocker.isChecked = false
updateStatus(false)
Toast.makeText(this, "Permissão de triagem de chamadas negada", Toast.LENGTH_LONG).show()
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
prefs = getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
setupSwitch()
loadSavedState()
}
private fun setupSwitch() {
binding.switchBlocker.setOnCheckedChangeListener { _, isChecked ->
if (isChecked) {
checkAndRequestPermissions()
} else {
disableBlocker()
}
}
}
private fun loadSavedState() {
val isEnabled = prefs.getBoolean(KEY_BLOCKER_ENABLED, false)
if (isEnabled && hasAllPermissions()) {
binding.switchBlocker.isChecked = true
updateStatus(true)
} else {
binding.switchBlocker.isChecked = false
updateStatus(false)
}
}
private fun hasAllPermissions(): Boolean {
return requiredPermissions.all {
ContextCompat.checkSelfPermission(this, it) == PackageManager.PERMISSION_GRANTED
}
}
private fun checkAndRequestPermissions() {
val permissionsToRequest = requiredPermissions.filter {
ContextCompat.checkSelfPermission(this, it) != PackageManager.PERMISSION_GRANTED
}.toTypedArray()
if (permissionsToRequest.isNotEmpty()) {
permissionLauncher.launch(permissionsToRequest)
} else {
requestCallScreeningRole()
}
}
private fun requestCallScreeningRole() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
val roleManager = getSystemService(Context.ROLE_SERVICE) as RoleManager
if (roleManager.isRoleAvailable(RoleManager.ROLE_CALL_SCREENING)) {
if (!roleManager.isRoleHeld(RoleManager.ROLE_CALL_SCREENING)) {
val intent = roleManager.createRequestRoleIntent(RoleManager.ROLE_CALL_SCREENING)
roleRequestLauncher.launch(intent)
} else {
enableBlocker()
}
} else {
enableBlocker()
}
} else {
enableBlocker()
}
}
private fun enableBlocker() {
prefs.edit().putBoolean(KEY_BLOCKER_ENABLED, true).apply()
updateStatus(true)
Toast.makeText(this, "Bloqueio de chamadas ativado!", Toast.LENGTH_SHORT).show()
}
private fun disableBlocker() {
prefs.edit().putBoolean(KEY_BLOCKER_ENABLED, false).apply()
updateStatus(false)
Toast.makeText(this, "Bloqueio de chamadas desativado", Toast.LENGTH_SHORT).show()
}
private fun updateStatus(enabled: Boolean) {
binding.switchStatus.text = if (enabled) {
getString(R.string.status_enabled)
} else {
getString(R.string.status_disabled)
}
val color = if (enabled) {
ContextCompat.getColor(this, R.color.primary)
} else {
ContextCompat.getColor(this, R.color.text_secondary)
}
binding.iconShield.setColorFilter(color)
}
}
+10
View File
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#4CAF50"
android:pathData="M54,20L27,34v18c0,16.65 11.52,32.22 27,36 15.48,-3.78 27,-19.35 27,-36V34l-27,-14zM48,70l-12,-12 4.23,-4.23L48,61.51l19.77,-19.77L72,46l-24,24z"/>
</vector>
+10
View File
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#4CAF50"
android:pathData="M12,1L3,5v6c0,5.55 3.84,10.74 9,12 5.16,-1.26 9,-6.45 9,-12V5l-9,-4zM10,17l-4,-4 1.41,-1.41L10,14.17l6.59,-6.59L18,9l-8,8z"/>
</vector>
+122
View File
@@ -0,0 +1,122 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/background"
tools:context=".MainActivity">
<!-- Ícone do escudo -->
<ImageView
android:id="@+id/iconShield"
android:layout_width="120dp"
android:layout_height="120dp"
android:layout_marginTop="80dp"
android:src="@drawable/ic_shield"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:tint="@color/primary" />
<!-- Título -->
<TextView
android:id="@+id/titleText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:text="@string/app_name"
android:textColor="@color/text_primary"
android:textSize="28sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/iconShield" />
<!-- Descrição -->
<TextView
android:id="@+id/descriptionText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="32dp"
android:layout_marginTop="12dp"
android:gravity="center"
android:text="@string/description"
android:textColor="@color/text_secondary"
android:textSize="16sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/titleText" />
<!-- Card com Switch -->
<com.google.android.material.card.MaterialCardView
android:id="@+id/cardSwitch"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="24dp"
android:layout_marginTop="48dp"
app:cardBackgroundColor="@color/card_background"
app:cardCornerRadius="16dp"
app:cardElevation="4dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/descriptionText">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="20dp">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:id="@+id/switchTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/blocker_title"
android:textColor="@color/text_primary"
android:textSize="18sp"
android:textStyle="bold" />
<TextView
android:id="@+id/switchStatus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="@string/status_disabled"
android:textColor="@color/text_secondary"
android:textSize="14sp" />
</LinearLayout>
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/switchBlocker"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<!-- Status das permissões -->
<TextView
android:id="@+id/permissionStatus"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="32dp"
android:layout_marginTop="24dp"
android:gravity="center"
android:text="@string/permission_info"
android:textColor="@color/text_secondary"
android:textSize="12sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/cardSwitch" />
</androidx.constraintlayout.widget.ConstraintLayout>
+12
View File
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="primary">#4CAF50</color>
<color name="primary_dark">#388E3C</color>
<color name="accent">#8BC34A</color>
<color name="background">#F5F5F5</color>
<color name="card_background">#FFFFFF</color>
<color name="text_primary">#212121</color>
<color name="text_secondary">#757575</color>
<color name="white">#FFFFFF</color>
<color name="error">#F44336</color>
</resources>
+10
View File
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Bloqueador de Chamadas</string>
<string name="description">Bloqueia automaticamente chamadas de números que não estão na sua lista de contatos</string>
<string name="blocker_title">Bloqueio Ativo</string>
<string name="status_enabled">Protegendo suas chamadas</string>
<string name="status_disabled">Desativado</string>
<string name="permission_info">Toque no switch para ativar e conceder as permissões necessárias</string>
<string name="permission_denied">Permissões necessárias não concedidas</string>
</resources>
+9
View File
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.CallBlocker" parent="Theme.Material3.Light.NoActionBar">
<item name="colorPrimary">@color/primary</item>
<item name="colorPrimaryDark">@color/primary_dark</item>
<item name="colorAccent">@color/accent</item>
<item name="android:statusBarColor">@color/primary_dark</item>
</style>
</resources>