diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..6df5660
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,23 @@
+# Gradle
+.gradle/
+build/
+!gradle/wrapper/gradle-wrapper.jar
+
+# Android
+.android/
+local.properties
+*.apk
+*.aab
+
+# IDE
+.idea/
+*.iml
+.vscode/
+
+# Build outputs
+apk-output/
+output/
+
+# OS files
+.DS_Store
+Thumbs.db
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..886eae8
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,35 @@
+FROM eclipse-temurin:17-jdk-jammy
+
+# Install required dependencies
+RUN apt-get update && apt-get install -y \
+ wget \
+ unzip \
+ git \
+ && rm -rf /var/lib/apt/lists/*
+
+# Set environment variables
+ENV ANDROID_HOME=/opt/android-sdk
+ENV PATH=$PATH:$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/platform-tools
+
+# Download and install Android command line tools
+RUN mkdir -p $ANDROID_HOME/cmdline-tools && \
+ wget -q https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip -O /tmp/cmdline-tools.zip && \
+ unzip -q /tmp/cmdline-tools.zip -d $ANDROID_HOME/cmdline-tools && \
+ mv $ANDROID_HOME/cmdline-tools/cmdline-tools $ANDROID_HOME/cmdline-tools/latest && \
+ rm /tmp/cmdline-tools.zip
+
+# Accept licenses and install required SDK components
+RUN yes | sdkmanager --licenses && \
+ sdkmanager "platform-tools" "platforms;android-34" "build-tools;34.0.0"
+
+# Set working directory
+WORKDIR /app
+
+# Copy project files
+COPY . .
+
+# Make gradlew executable
+RUN chmod +x gradlew
+
+# Build the APK
+CMD ["./gradlew", "assembleDebug", "--no-daemon"]
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..8e0afbb
--- /dev/null
+++ b/README.md
@@ -0,0 +1,93 @@
+# Call Blocker
+
+Aplicativo Android para bloqueio automático de chamadas de números desconhecidos.
+
+## Sobre
+
+O **Call Blocker** bloqueia automaticamente chamadas recebidas de números que não estão salvos na sua lista de contatos. Utiliza a API CallScreeningService do Android (disponível a partir do Android 10) para interceptar e filtrar chamadas de forma transparente.
+
+## Funcionalidades
+
+- Bloqueio automático de chamadas de números desconhecidos
+- Permite chamadas de números salvos nos contatos
+- Interface simples com botão liga/desliga
+- Integração nativa com o sistema de chamadas do Android
+- Bloqueia chamadas com número oculto/indisponível
+
+## Requisitos
+
+- Android 7.0 (API 24) ou superior
+- Permissões necessárias: telefone, contatos e triagem de chamadas
+
+## Estrutura do Projeto
+
+```
+call_blocker/
+├── app/
+│ └── src/main/
+│ ├── java/com/callblocker/
+│ │ ├── MainActivity.kt # Atividade principal com UI
+│ │ └── CallBlockerService.kt # Serviço de triagem de chamadas
+│ ├── res/ # Recursos (layouts, strings, cores)
+│ └── AndroidManifest.xml
+├── gradle/wrapper/ # Gradle wrapper
+├── build.gradle.kts # Configuração do Gradle
+├── settings.gradle.kts # Configuração de módulos
+└── Dockerfile # Build via Docker
+```
+
+## Build via Docker
+
+### Pré-requisitos
+
+- Docker instalado na máquina
+
+### Construir a imagem
+
+```bash
+docker build -t call-blocker-builder .
+```
+
+### Gerar o APK
+
+```bash
+docker run --rm -v $(pwd)/output:/app/app/build/outputs call-blocker-builder
+```
+
+O APK será gerado em `./output/apk/debug/app-debug.apk`.
+
+### Comando único (build + extração)
+
+```bash
+docker build -t call-blocker-builder . && \
+docker run --rm -v $(pwd)/output:/app/app/build/outputs call-blocker-builder && \
+echo "APK gerado em: ./output/apk/debug/app-debug.apk"
+```
+
+## Build Local (sem Docker)
+
+### Pré-requisitos
+
+- JDK 17
+- Android SDK com platform-tools e build-tools 34.0.0
+
+### Gerar o APK
+
+```bash
+chmod +x gradlew
+./gradlew assembleDebug
+```
+
+O APK será gerado em `app/build/outputs/apk/debug/app-debug.apk`.
+
+## Instalação
+
+```bash
+adb install app-debug.apk
+```
+
+Ou transfira o APK para o dispositivo e instale manualmente.
+
+## Licença
+
+Este projeto é de código aberto.
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
new file mode 100755
index 0000000..b9a6491
--- /dev/null
+++ b/app/build.gradle.kts
@@ -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")
+}
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
new file mode 100755
index 0000000..dd2c6f2
--- /dev/null
+++ b/app/proguard-rules.pro
@@ -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 { *; }
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100755
index 0000000..c6fae7e
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/java/com/callblocker/CallBlockerService.kt b/app/src/main/java/com/callblocker/CallBlockerService.kt
new file mode 100755
index 0000000..b5a79b3
--- /dev/null
+++ b/app/src/main/java/com/callblocker/CallBlockerService.kt
@@ -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)
+ }
+}
diff --git a/app/src/main/java/com/callblocker/MainActivity.kt b/app/src/main/java/com/callblocker/MainActivity.kt
new file mode 100755
index 0000000..c1abbda
--- /dev/null
+++ b/app/src/main/java/com/callblocker/MainActivity.kt
@@ -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)
+ }
+}
diff --git a/app/src/main/res/drawable/ic_launcher.xml b/app/src/main/res/drawable/ic_launcher.xml
new file mode 100755
index 0000000..db922f4
--- /dev/null
+++ b/app/src/main/res/drawable/ic_launcher.xml
@@ -0,0 +1,10 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_shield.xml b/app/src/main/res/drawable/ic_shield.xml
new file mode 100755
index 0000000..7fc7e85
--- /dev/null
+++ b/app/src/main/res/drawable/ic_shield.xml
@@ -0,0 +1,10 @@
+
+
+
+
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
new file mode 100755
index 0000000..5bb635e
--- /dev/null
+++ b/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,122 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
new file mode 100755
index 0000000..c6b6072
--- /dev/null
+++ b/app/src/main/res/values/colors.xml
@@ -0,0 +1,12 @@
+
+
+ #4CAF50
+ #388E3C
+ #8BC34A
+ #F5F5F5
+ #FFFFFF
+ #212121
+ #757575
+ #FFFFFF
+ #F44336
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
new file mode 100755
index 0000000..5e42264
--- /dev/null
+++ b/app/src/main/res/values/strings.xml
@@ -0,0 +1,10 @@
+
+
+ Bloqueador de Chamadas
+ Bloqueia automaticamente chamadas de números que não estão na sua lista de contatos
+ Bloqueio Ativo
+ Protegendo suas chamadas
+ Desativado
+ Toque no switch para ativar e conceder as permissões necessárias
+ Permissões necessárias não concedidas
+
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
new file mode 100755
index 0000000..1d5dce2
--- /dev/null
+++ b/app/src/main/res/values/themes.xml
@@ -0,0 +1,9 @@
+
+
+
+
diff --git a/build.gradle.kts b/build.gradle.kts
new file mode 100755
index 0000000..3a6ec8d
--- /dev/null
+++ b/build.gradle.kts
@@ -0,0 +1,4 @@
+plugins {
+ id("com.android.application") version "8.2.0" apply false
+ id("org.jetbrains.kotlin.android") version "1.9.20" apply false
+}
diff --git a/gradle.properties b/gradle.properties
new file mode 100755
index 0000000..f0a2e55
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,4 @@
+org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
+android.useAndroidX=true
+kotlin.code.style=official
+android.nonTransitiveRClass=true
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..033e24c
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100755
index 0000000..15de902
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
new file mode 100755
index 0000000..1518cb8
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,27 @@
+#!/bin/sh
+
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+else
+ JAVACMD="java"
+fi
+
+# Add default JVM options here
+DEFAULT_JVM_OPTS='-Xmx64m -Xms64m'
+
+APP_HOME=$( cd "$( dirname "$0" )" && pwd -P ) || exit
+
+# Setup classpath
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Execute Gradle
+exec "$JAVACMD" $DEFAULT_JVM_OPTS -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/settings.gradle.kts b/settings.gradle.kts
new file mode 100755
index 0000000..f77eb5e
--- /dev/null
+++ b/settings.gradle.kts
@@ -0,0 +1,18 @@
+pluginManagement {
+ repositories {
+ google()
+ mavenCentral()
+ gradlePluginPortal()
+ }
+}
+
+dependencyResolutionManagement {
+ repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
+ repositories {
+ google()
+ mavenCentral()
+ }
+}
+
+rootProject.name = "CallBlocker"
+include(":app")