Skip to content

Commit b1fcef1

Browse files
redblack168claude
andcommitted
Initial commit: Silent Block v0.1.0
Tiny Android app that silently rejects incoming calls per SIM via CallScreeningService. Three modes: Off / Unknown / All. Master toggle overrides per-SIM rules. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
0 parents  commit b1fcef1

25 files changed

Lines changed: 1090 additions & 0 deletions

.gitignore

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
*.iml
2+
.gradle/
3+
local.properties
4+
.idea/
5+
.DS_Store
6+
build/
7+
captures/
8+
.externalNativeBuild
9+
.cxx
10+
*.apk
11+
*.aab
12+
*.keystore
13+
*.jks

README.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Silent Block
2+
3+
Tiny Android app that silently rejects incoming calls. No ringing, no missed-call notification — calls are dropped before the dialer reacts.
4+
5+
## Features
6+
7+
- **Master switch** — one toggle to block every incoming call across all SIMs.
8+
- **Per-SIM rules** — independent mode for each SIM:
9+
- **Off** — let calls ring normally.
10+
- **Unknown** — block calls from numbers not in your Contacts.
11+
- **All** — block every call to this SIM.
12+
- Uses Android's `CallScreeningService` (API 29+) — clean, modern, no reflection.
13+
14+
## Install
15+
16+
Download the APK from the [latest release](https://github.com/redblack168/silent-call-blocker/releases) and install it on your phone (enable *Install unknown apps* for your browser/file manager).
17+
18+
After install:
19+
1. Open **Silent Block**.
20+
2. Tap **Set as screener** → grant the Call Screening role.
21+
3. Tap **Grant permissions** → Contacts + Phone.
22+
4. Adjust per-SIM rules as desired.
23+
24+
## Build from source
25+
26+
```bash
27+
JAVA_HOME=/path/to/jdk17 ./gradlew :app:assembleDebug
28+
# APK at app/build/outputs/apk/debug/app-debug.apk
29+
```
30+
31+
Requires Android SDK 34, build-tools 34.0.0, JDK 17. Min device: Android 10 (API 29).

app/build.gradle.kts

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
plugins {
2+
id("com.android.application")
3+
id("org.jetbrains.kotlin.android")
4+
}
5+
6+
android {
7+
namespace = "com.cola.callblock"
8+
compileSdk = 34
9+
10+
defaultConfig {
11+
applicationId = "com.cola.callblock"
12+
minSdk = 29
13+
targetSdk = 34
14+
versionCode = 1
15+
versionName = "0.1.0"
16+
vectorDrawables { useSupportLibrary = true }
17+
}
18+
19+
buildTypes {
20+
debug {
21+
isMinifyEnabled = false
22+
isDebuggable = true
23+
}
24+
release {
25+
isMinifyEnabled = false
26+
signingConfig = signingConfigs.getByName("debug")
27+
}
28+
}
29+
30+
compileOptions {
31+
sourceCompatibility = JavaVersion.VERSION_17
32+
targetCompatibility = JavaVersion.VERSION_17
33+
}
34+
35+
kotlinOptions {
36+
jvmTarget = "17"
37+
freeCompilerArgs += listOf(
38+
"-opt-in=androidx.compose.material3.ExperimentalMaterial3Api",
39+
)
40+
}
41+
42+
buildFeatures {
43+
compose = true
44+
}
45+
46+
composeOptions {
47+
kotlinCompilerExtensionVersion = "1.5.10"
48+
}
49+
50+
sourceSets {
51+
getByName("main") {
52+
java.srcDirs("src/main/kotlin")
53+
}
54+
}
55+
56+
packaging {
57+
resources {
58+
excludes += "/META-INF/{AL2.0,LGPL2.1}"
59+
}
60+
}
61+
}
62+
63+
dependencies {
64+
val composeBom = platform("androidx.compose:compose-bom:2024.04.01")
65+
implementation(composeBom)
66+
67+
implementation("androidx.core:core-ktx:1.12.0")
68+
implementation("androidx.appcompat:appcompat:1.6.1")
69+
implementation("androidx.activity:activity-compose:1.8.2")
70+
71+
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0")
72+
implementation("androidx.lifecycle:lifecycle-runtime-compose:2.7.0")
73+
74+
implementation("androidx.compose.ui:ui")
75+
implementation("androidx.compose.ui:ui-graphics")
76+
implementation("androidx.compose.material3:material3")
77+
78+
implementation("androidx.datastore:datastore-preferences:1.1.1")
79+
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.0")
80+
81+
debugImplementation("androidx.compose.ui:ui-tooling")
82+
}

app/proguard-rules.pro

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Default rules — release isn't minified, this file exists only to satisfy AGP.

app/src/main/AndroidManifest.xml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
3+
4+
<uses-permission android:name="android.permission.READ_CONTACTS" />
5+
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
6+
<uses-permission android:name="android.permission.READ_PHONE_NUMBERS" />
7+
8+
<application
9+
android:name=".BlockerApp"
10+
android:allowBackup="false"
11+
android:icon="@mipmap/ic_launcher"
12+
android:label="@string/app_name"
13+
android:supportsRtl="true"
14+
android:theme="@style/Theme.CallBlocker">
15+
16+
<activity
17+
android:name=".MainActivity"
18+
android:exported="true"
19+
android:launchMode="singleTask">
20+
<intent-filter>
21+
<action android:name="android.intent.action.MAIN" />
22+
<category android:name="android.intent.category.LAUNCHER" />
23+
</intent-filter>
24+
</activity>
25+
26+
<service
27+
android:name=".ScreenerService"
28+
android:exported="true"
29+
android:permission="android.permission.BIND_SCREENING_SERVICE">
30+
<intent-filter>
31+
<action android:name="android.telecom.CallScreeningService" />
32+
</intent-filter>
33+
</service>
34+
</application>
35+
</manifest>
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package com.cola.callblock
2+
3+
import android.app.Application
4+
5+
class BlockerApp : Application()
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package com.cola.callblock
2+
3+
import android.Manifest
4+
import android.content.Context
5+
import android.content.pm.PackageManager
6+
import android.net.Uri
7+
import android.provider.ContactsContract
8+
import androidx.core.content.ContextCompat
9+
10+
object ContactLookup {
11+
fun isContact(ctx: Context, number: String?): Boolean {
12+
if (number.isNullOrBlank()) return false
13+
val granted = ContextCompat.checkSelfPermission(
14+
ctx, Manifest.permission.READ_CONTACTS
15+
) == PackageManager.PERMISSION_GRANTED
16+
if (!granted) return false
17+
val uri = Uri.withAppendedPath(
18+
ContactsContract.PhoneLookup.CONTENT_FILTER_URI,
19+
Uri.encode(number)
20+
)
21+
return runCatching {
22+
ctx.contentResolver.query(
23+
uri,
24+
arrayOf(ContactsContract.PhoneLookup._ID),
25+
null, null, null
26+
)?.use { it.moveToFirst() } ?: false
27+
}.getOrDefault(false)
28+
}
29+
}

0 commit comments

Comments
 (0)