first commit
This commit is contained in:
Executable
+47
@@ -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")
|
||||
}
|
||||
+6
@@ -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 { *; }
|
||||
Executable
+43
@@ -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
@@ -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
@@ -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)
|
||||
}
|
||||
}
|
||||
Executable
+10
@@ -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>
|
||||
Executable
+10
@@ -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>
|
||||
Executable
+122
@@ -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>
|
||||
Executable
+12
@@ -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>
|
||||
Executable
+10
@@ -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>
|
||||
Executable
+9
@@ -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>
|
||||
Reference in New Issue
Block a user