Open-source spam call and text blocker for Android
15+ layer detection + Gradient-Boosted Tree ML | 32,933 spam numbers | Real-time caller ID | RCS filter | No required API keys
CallShield blocks spam calls and texts using a 15+ layer on-device detection engine with a gradient-boosted tree ML scorer, campaign burst detection, RCS notification filter, and real-time caller ID overlay. Powered by a 32,933-number database with scheduled hot-list updates. Community-maintained, no accounts, no tracking.
- Roaming-safe number matching — phone identities canonicalize under the SIM's home region instead of the visited network, so blocks and whitelists keep matching abroad and the one-time identity migration cannot bake in the wrong country.
- Screening edge cases hardened — unknown-direction calls fail open with an explicit response, the explicit-block guard for per-category actions matches the real system block-list source, duplicate call-log rows no longer defeat repeated-call urgency, and lettered SMS sender IDs can no longer opt out of keyword and content screening.
- Backup and export reliability — export failures report instead of crashing, blocklist imports are transactional, restores with a typed passphrase reject unauthenticated plaintext files, legacy backups no longer duplicate log rows on repeated merges, and encrypted backups stay restorable across future KDF strengthening.
- Calmer, more accessible surfaces — settings toggles are fully tappable single TalkBack nodes, informational captions meet WCAG AA contrast in every theme, the Lookup tab reads the clipboard only when Paste is tapped, the Lookup shortcut lands on the right tab, and the role-loss alert clears the moment protection is restored.
- Safer personal trust rules — contact trust can cover every contact or only selected Android contact groups, with local-only identifiers and clear degraded behavior when contacts permission is unavailable.
- More precise call control — each detected call category can ring, go to voicemail, block, or inherit the global policy; known-risk outgoing calls can optionally show a local-only warning without interrupting the call.
- Protected portable backups — passphrase-encrypted exports use PBKDF2-HMAC-SHA256 and AES-256-GCM, while atomic restore preserves the prior settings and database state if any selected section fails.
- Stronger protection recovery — CallShield detects loss of Android's call screening role and provides a single actionable recovery notification.
- Zero-baseline release gates — ktlint and Detekt now pass without stored suppression baselines, release metadata is synchronized automatically, and signing-secret preflight rejects tracked credentials without echoing them.
- Four professional themes — choose System, Light, Graphite, or true-black AMOLED from Settings. The selection persists across restarts and applies to the main app, onboarding, dialogs, and post-call surfaces.
- Calmer visual hierarchy — a tighter type scale, restrained semantic surfaces, quieter borders, and denser section rhythm replace decorative gradients and excess card framing.
- Faster settings and navigation — shorter descriptions, compact control rows, consolidated appearance options, and one shared header for nested More destinations reduce scrolling and duplicated chrome.
- Contrast protected by tests — text, secondary text, and primary actions meet WCAG AA contrast requirements in every named palette.
- Category-level call control — debt collection, political, robocall, scam, phishing, telemarketing, survey, and nonprofit calls can independently inherit the global policy, ring, go to voicemail, or be blocked.
- Protected portable backups — optionally encrypt exports with a passphrase using PBKDF2-HMAC-SHA256 and authenticated AES-256-GCM. CallShield never saves the passphrase, and existing plaintext JSON backups remain importable.
- Private Android backups — cloud backup now excludes the Room database and its phone numbers, call history, and raw blocked-message bodies. Direct device transfer and explicit portable backups remain available.
- Smaller, auditable APK assets — builds package only the five runtime data feeds. Verification tasks fail if repository-only report payloads enter a debug or release APK.
- Safer screening boundaries — outgoing calls leave the inbound pipeline immediately, while malformed, text-only, or overlong caller IDs follow the user's unknown-caller rule instead of bypassing it.
- More complete portable backups — active temporary block/allow expiries and selected notification-screening apps now survive a validated v5 backup round trip; oversized restores are rejected before expensive processing.
- Resilient local settings — corrupt public or private DataStore files recover to safe defaults instead of repeatedly breaking startup and call screening.
- Privacy-safe diagnostics — shared crash reports redact phone-like values, credential fields, and private URL components, with bounded exception output.
- Refined secondary flows — language changes stay on the current nested screen, activity-log swipes recover correctly, and source/status labels are clear even when supported apps are not installed.
- Protection self-heals — the RCS notification filter now rebinds itself if Android unbinds it (low memory, app update, OEM kill), and app updates without a reboot re-assert all background workers and the listener. A corrupt local database is now detected and rebuilt (re-syncing from the cloud) instead of silently letting every call ring through.
- Leaner daily digest — the 24-hour blocked-activity summary is computed with aggregate database queries instead of loading the full window into memory.
- OEM battery-kill awareness — the app can now detect when an aggressive battery manager is likely to kill background protection, and when the spam database has gone stale, so those risks can be surfaced.
- More tested internals — Robolectric now exercises notification actions, SMS reassembly, real call-screening allow/silence/reject outcomes, and the SMS phishing-warning entrypoint (831 tests total).
- Safer imports — blocklist import and backup restore now read the selected file through a bounded reader (32 MB cap) and cap applied rows, guarding against out-of-memory on a huge or malformed file.
- Android 16 notifications — blocked-call alerts group and summarize cleanly so a burst stays visible instead of being cooldown-muted.
- ML transparency — the on-device model reports a health state, so a corrupt model that quietly falls back to the simpler scorer is now logged instead of silently degrading detection.
- Fewer false blocks — legitimate US/Canada callers from area codes like 678 (Atlanta), 267 (Philadelphia), 224 (Chicago), 385 (Salt Lake City) and others are no longer misblocked as international "wangiri" scams, and international numbers that merely share digits with US premium lines (e.g. Mongolia +976) are no longer flagged.
- Safer data — backup restore and hot-list refresh are now fully atomic (no half-applied state on failure); CSV log exports are hardened against spreadsheet formula injection; the phishing-URL check caps its response size.
- Correctness fixes — the after-call "Was this spam?" notification now dismisses when you tap its buttons; phishing-URL (URLhaus) warnings no longer require SMS blocking to be on; and a burst of legitimate SMS from one carrier prefix can no longer trip campaign blocking of real voice calls.
- Caller-ID overlay dismisses when the call ends instead of lingering for a fixed 20 seconds.
- RTL-safe numbers — phone numbers are bidi-isolated in notifications so they render correctly inside Arabic/Hebrew text.
- Hardening — malformed ML fallback weights are rejected instead of silently zeroed; GitHub default-branch lookups are cached to avoid rate-limit exhaustion; RCS encrypted-message placeholders are detected across more languages; CVE-2026-53914 build-cache posture documented.
- Fully free, zero API keys — the optional AbstractAPI key field has been removed from Settings. CallShield no longer has any manual credential entry; every lookup and enrichment source it uses is free and needs no sign-up. Any key saved by an earlier version is purged from the device on first launch.
- Dead code removed — the unused carrier/line-type network checker
(
NumberTypeChecker) and its certificate pin are gone.
-
Durable blocked-call logging - blocked-call decisions now enqueue an idempotent pending Room row before responding to Android, then a Hilt-backed retry worker flushes the row into the final call log without duplicating notifications or widget refreshes.
-
Room v10 migration -
call_lognow carries a nullable uniquelogKey,pending_blocked_call_logspersists retry state, and migration/DAO tests cover duplicate suppression and retry windows. -
Community-report abuse backoff - the Cloudflare Worker rate-limit and dedup helpers now have local tests, and the Android client surfaces HTTP 429 with the server-provided retry delay.
-
ASCII-only digit utility - security-sensitive phone-number extraction and UI entry/review paths now share named ASCII-only helpers instead of Unicode digit matching.
-
External blocklist subscription guardrails - custom HTTPS CSV/TXT/JSON feeds validate byte and row limits, preview add/remove/source impact before commit, and can be disabled or removed with feed-owned rows rolled back.
-
Compose BOM 2026.05.00 refresh — UI dependencies now resolve on the current Compose 1.11.1 release train, with Material 3 1.4.0 and refreshed lifecycle/core/savedstate transitive locks across debug, release, and unit test configurations.
-
Configuration-aware Compose copy — snackbar, toast, semantic, validation, and search-result count text now use Compose resource APIs instead of stale
LocalContext.getString/resourcesreads inside composables. -
WorkManager 2.11.2 refresh — background sync, hot-list refresh, and daily digest jobs now run on the current WorkManager line with Android 15+ network-constraint fixes.
-
Worker schedule contract tests — sync, manual refresh, hot-list, and digest WorkRequests now have JVM coverage for repeat intervals, network constraints, initial delay, and retry backoff.
-
DataStore privacy hardening — settings now run on DataStore 1.2.1, and the optional AbstractAPI enrichment key is stored in a private no-backup DataStore instead of the backed-up public settings file.
-
Safer backup boundary — Android cloud backup preserves non-secret public preferences but excludes the Room database, which can contain call history, phone numbers, and SMS bodies. Direct device transfer and CallShield's explicit portable backup remain available for user-controlled migration.
-
Reproducible-build groundwork — Gradle dependency locking is enabled, the resolved dependency graph is checked in, AGP VCS metadata is disabled for release APKs, and local release guards block wall-clock build metadata from being embedded into APK inputs.
-
Release hash sidecars — release builds now produce SHA256 sidecars for APK artifact integrity, with Windows helpers for signed local releases and content-level APK rebuild comparisons.
-
Network hardening — OkHttp is upgraded to 5.3.2 and all direct data, community-report, URL-safety, and caller-ID enrichment hosts are protected by centralized certificate pinning.
-
Modern Android build stack — AGP is upgraded to 8.10.1, Kotlin/KSP are aligned on 2.2.21, and Room is upgraded to 2.8.4, keeping codegen and R8 compatible with the current Kotlin metadata used by the networking stack.
-
Stats and scan feedback polish — weekly activity labels now respect locale weekday names, Statistics detection-source labels are resource-backed, and scan permission/failure copy is consistent across call-log and SMS flows.
-
Settings credential polish — the optional AbstractAPI key is masked by default, has explicit show/hide control, reports "Not configured", "Saved locally", and "Unsaved changes" states before saving, and is now kept out of backed-up public preferences.
-
Premium-polish UX refresh — shared premium action, compact-action, icon-tile, and state-card treatments now unify dashboard, lookup, details, recent-call, blocklist, diagnostics, onboarding, and settings flows.
-
Clearer recovery states — Blocked Log empty and filtered states now explain what happened and provide a direct "Show all activity" recovery action when filters hide records.
-
Trust-focused settings feedback — the trusted push-alert source picker now shows installed-source coverage and skeleton loading while package labels resolve.
-
Hardening foundation from v1.7.2 — spoof-proof ASCII phone normalization, SMS size caps, regex ReDoS validation, LRU notice gates, separated PendingIntent IDs, and atomic crash-log writes.
-
STIR/SHAKEN Authenticated Allow — carrier-authenticated caller ID can short-circuit weak heuristic / ML blocks while still yielding to every explicit user and system rule.
-
Answered-caller trust — numbers answered repeatedly inside the configured lookback window can ring through lower-confidence heuristic/ML suspicion while explicit block rules still win first.
-
Emergency callback grace — unknown callbacks can ring through after a local emergency call for a configurable window while explicit block rules still win first.
-
SMS burst protection - repeated unknown SMS senders or same-prefix floods can be blocked as
sms_burst, with blocked-SMS notification actions to mark safe or report. -
919 total JVM unit tests - the local unit suite covers detection, workers, utilities, repository contracts, and permission readiness before release.
-
Gradient-Boosted Tree ML model — 20 features, pure Kotlin, no TFLite dependency.
-
Campaign burst detection — NPA-NXX prefix clustering identifies coordinated spam waves.
-
Full accessibility — content descriptions across Compose UI, 48dp minimum touch targets.
- 32,933 confirmed spam numbers — sourced from 1.75M FCC consumer complaints (2+ reports each), FTC Do Not Call, ToastedSpam, and community reports
- 15+ layer detection + ML — database, heuristics, campaign burst detection, on-device gradient-boosted tree, SMS content/burst analysis, RCS filter, STIR/SHAKEN, and more
- Real-time caller ID overlay — parallel lookups against SkipCalls, PhoneBlock, WhoCalledMe + OpenCNAM caller name, with SIT tone anti-autodialer
- Scheduled hot list — trending spam numbers and campaign ranges refresh through the repository data pipeline
- Callback-aware — won't block callbacks from numbers you recently called, answered repeatedly, after a local emergency call, or urgent repeated callers
- Community-driven — one-tap anonymous contribution via Cloudflare Worker, daily merge into database
All detection layers implement a shared IChecker interface and run in priority order via CheckerPipeline.run — first non-null result wins, every layer is testable in isolation. Priorities are stable numbers; the ladder below is the live order.
| Priority | Layer | Verdict | How It Works |
|---|---|---|---|
| 10000 | Manual Whitelist | Allow | Numbers you've explicitly marked as always-allow |
| 9000 | Contact Whitelist | Allow | Numbers in your phone's contacts always pass through |
| 8500 | STIR/SHAKEN Failed | Block | Carrier-authenticated caller ID failure gets blocked before heuristic layers |
| 7000 | User Blocklist + Database | Block | Personal blocklist + 32,933 confirmed spam numbers + scheduled hot-list data |
| 6900 | System Block List (A4) | Block | Read-only bridge to Android's BlockedNumberContract — respects stock Phone/Messages blocks |
| 6000 | Prefix Rules | Block | Wangiri country codes, US premium rate (+1900), international premium |
| 5500 | Wildcard / Regex | Block | Custom patterns like +1832555* or full regex, now with optional schedule |
| 5400 | Range Patterns (A5) | Block | Length-locked # patterns like +33162######, with schedule + coverage safety rail |
| 5300 | STIR/SHAKEN Authenticated | Allow | Carrier-authenticated caller ID can allow through lower-confidence heuristic/ML suspicion while explicit blocks still win first |
| 5000 | Recently Dialed | Allow | Numbers you called in the last 24h — they're probably calling back |
| 4980 | Emergency Callback | Allow | Unknown callbacks can ring through after a local emergency call during the configured grace window |
| 4950 | Answered Caller | Allow | Numbers answered repeatedly inside the configured lookback window |
| 4900 | Repeated Urgent | Allow | Same number calls 2x in 5 min → allowed through |
| 4700 | Push-Alert Bridge (A3) | Allow | Uber/DoorDash/Amazon/Gmail notification about an arriving call? Let it through |
| 4500 | Campaign Recorder | — | Side-effect only; feeds burst detection below |
| 4000 | Quiet Hours | Block | Block all non-contact calls during configurable hours |
| 3500 | Frequency Auto-Block | Block | Numbers that call 3+ times in 7 days get auto-blocked |
| 3000 | Heuristic Engine | Block | VoIP ranges, neighbor spoofing, rapid-fire detection, 30+ rules |
| 2500 | Campaign Burst | Block | NPA-NXX prefix clustering detects coordinated spam waves |
| 2250 | Caller Name Rules | Block | Carrier-presented names can match bounded, user-defined */? patterns after every allow layer |
| 2000 | ML Spam Scorer | Block | 20-feature on-device gradient-boosted tree model |
SMS-specific layers (append after the shared chain): SMS Context Trust → SMS Keyword Rules (with schedule) → SMS Burst Protection → SMS Content Analysis (30+ regex patterns, URL shorteners, suspicious TLDs, spam domain blocklist).
- Caller ID Overlay — suspicious calls (heuristic score 30-59) trigger a live multi-source lookup overlay with SkipCalls, PhoneBlock, WhoCalledMe + OpenCNAM caller name
- Region & caller-name rules — opt-in offline blocking outside selected US/Canadian regions, plus bounded
*/?trust and block patterns for carrier-presented caller names; explicit number/system/prefix/wildcard blocks and all allow layers keep priority - Opt-in message notification screening — Google/Samsung Messages are enabled by default; AOSP Messages, SMS Organizer, Signal, WhatsApp, WhatsApp Business, Gmail, Outlook, and Thunderbird can be enabled individually. Private-messenger/email matches show a separate warning without removing the original notification.
- URL Safety — local spam-domain checks run before URLhaus (abuse.ch), with query-string stripping enabled by default for remote SMS/RCS URL checks
- STIR/SHAKEN — blocks calls failing carrier caller ID verification (Android 11+)
- After-Call Feedback — "Was this spam?" notification after suspicious calls, plus an optional Android 11+ post-call screen for block/report and save-contact actions
Any wildcard, range, or SMS keyword rule can be time-gated to specific days of the week and an hour window. The hour picker supports overnight wrap; daysMask = 0 is the "no gating" sentinel so rules created before v1.6 behave identically.
When a call comes in, CallShield shows a real-time overlay that queries 4 sources simultaneously:
┌──────────────────────────────────┐
│ LIKELY SPAM │
│ (212) 555-1234 │
│ New York, NY │
│ Spam Score: 80% (17 reports) │
│ JOHN DOE (OpenCNAM) │
│ ⚠ SkipCalls: Flagged │
│ ⚠ PhoneBlock: 5 reports │
│ ⚠ WhoCalledMe: 12 reports │
│ All sources checked │
│ [Search] [Block] [Dismiss] │
│ 🔈 Play SIT Tone (anti-dialer) │
└──────────────────────────────────┘
- Shows instantly with area code, then updates live as each source responds
- OpenCNAM caller name lookup (free, 60 req/hr)
- SIT Tone — ITU-T E.180 three-tone sequence tricks autodialers into removing your number
- Color-coded: green (safe) → yellow → orange → red (spam)
On-device 20-feature gradient-boosted tree model — pure Kotlin, no TFLite, no heavy ML libraries. Runs in microseconds.
| Feature | Description |
|---|---|
| toll_free | 800/888/877/etc. prefix |
| high_spam_npa | Area code in high FTC/FCC complaint set |
| voip_range | NPA-NXX in known VoIP spam carrier range |
| repeated_digits_ratio | Fraction of most-common digit |
| sequential_asc/desc_ratio | Sequential digit pairs |
| all_same_digit | All 10 digits identical |
| nxx_555 | Exchange is 555 (test numbers) |
| last4_zero | Subscriber is 0000 |
| invalid_nxx | NXX starts with 0 or 1 (NANP-invalid) |
| subscriber_all_same | Last 4 digits all same (9999) |
| alternating_pattern | Even/odd positions uniform (5050505050) |
| nxx_below_200 | Often unassigned ranges |
| low_digit_entropy | Fewer than 4 distinct digits |
| subscriber_sequential | Last 4 form ascending/descending run |
| + 6 additional | Campaign proximity, time-of-day, call frequency, area code density, prefix heat, neighbor spoof score |
Trained weekly from the CallShield database (50K positive + 50K negative samples). Threshold: 0.7 (conservative).
- Instant spam check through all 15+ detection layers with animated score gauge (0-100)
- Auto-paste from clipboard, area code lookup (330+ US/CA), haptic feedback
- Multi-source reverse lookup: SkipCalls + PhoneBlock + WhoCalledMe + OpenCNAM
- Recent calls with contact names, risk indicators, call type icons, filter chips (All/Missed/Spam)
- Blocked log with swipe-to-dismiss + undo, grouping with severity-scaled accent bars, filter chips
- Staggered entrance animations, shimmer loading skeletons
- Blocklist, Wildcards, Keywords, Whitelist, Database
- Export/import blocklists as JSON, per-rule enable/disable toggles
- Regex validation before adding wildcard rules
- Inline priority-conflict warnings name the whitelist, emergency allow, or block rule that wins before an overlapping rule is saved
- Weekly bar chart with daily breakdown
- Detection source donut chart
- Monthly trend line
- Top offenders, area code heatmap, hourly heatmap
- Smart suggestions — detects area code spam patterns, one-tap block entire area code
- Weekly trend indicator — shows if spam is increasing or decreasing vs last week
- Last blocked preview card on dashboard with tap-to-inspect
- Blocking profiles: Work / Personal / Sleep / Maximum / Off
- Callback detection + repeated urgent caller allow-through
- FTC Do Not Call complaint filing
- After-call "Was this spam?" feedback notification
- Today vs yesterday blocked count with trend indicator
- Last blocked number and time
- Quick-access to lookup and protection status
- One-tap anonymous contribution via Cloudflare Worker
- False positive reporting subtracts votes
- Share spam warnings to any app
- Selective backup/restore for rules, non-secret settings, and opt-in logs; CSV log export; auto-cleanup (7/14/30/90 days)
- Weekly full sync + scheduled hot list refresh, daily digest notification
- Quick Settings tile, app shortcuts, home screen widget
- Protection test validates all layers and permissions
- Onboarding wizard with permission requests
| Source | Method |
|---|---|
| FCC Consumer Complaints | Socrata API, 500K records, min 2 reports |
| FTC Do Not Call | api.ftc.gov (DEMO_KEY) |
| ToastedSpam | Community curated list |
| Community Reports | Anonymous via Cloudflare Worker |
| File | Contents |
|---|---|
hot_numbers.json |
Top 500 trending numbers (last 24h) |
hot_ranges.json |
NPA-NXX prefixes with 3+ active campaign numbers |
spam_domains.json |
Phishing/spam domains from community SMS reports |
| Source | What It Returns | Auth |
|---|---|---|
| SkipCalls | spam flag, 1M+ numbers | None |
| PhoneBlock.net | Votes, rating, blacklist | None |
| WhoCalledMe | Report count, notes | None |
| OpenCNAM | Caller name (CNAM) | None (60/hr) |
| Source | What It Checks |
|---|---|
| URLhaus (abuse.ch) | Phishing/malware URLs in SMS/RCS bodies after local spam-domain matching |
- Network security config — cleartext traffic disabled in production
- Signing credentials — stored in
local.properties, not hardcoded in build files - Restricted FileProvider paths — scoped to export directory only
- Scoped backup — cloud backup includes non-secret settings only; the sensitive database is limited to direct device transfer and explicit user-created portable backups
- APK privacy gate — builds package only the five runtime protection feeds; raw community submissions and maintainer files are rejected by verification
All detection runs on-device. No personal data is collected. Network requests:
- Syncing spam database from GitHub (public)
- Real-time lookups against free public APIs (number queried, not stored)
- Community reports to Cloudflare Worker (phone number only, no identity)
- URLhaus checks for SMS URL safety after local spam-domain matching; fragments and query strings are stripped by default
No API keys — none required, none optional, no credential entry anywhere in the app. No accounts. No analytics. No ads.
- Android 10+ (API 29)
- STIR/SHAKEN requires Android 11+ (API 30)
- Caller ID overlay requires "Display over other apps" permission
- RCS filter requires Notification Access permission
./gradlew verifyReleaseMetadata verifyReproducibleBuildInputs verifyReleaseApkReproducibleMetadataRequires JDK 17+. Signed APK at app/build/outputs/apk/release/app-release.apk.
Generate the release hash sidecar with:
.\scripts\write-release-sha256.ps1See docs/reproducible-builds.md for the dependency-lock and hash-comparison
runbook.
F-Droid submission prep lives in fastlane/metadata/android/en-US/,
docs/fdroid/com.sysadmindoc.callshield.yml, and
docs/fdroid-submission.md. The actual F-Droid merge request and signature-copy
verification still require an fdroiddata/GitLab environment.
Signing: Create local.properties in the project root with your keystore credentials:
RELEASE_STORE_FILE=path/to/keystore.jks
RELEASE_STORE_PASSWORD=...
RELEASE_KEY_ALIAS=...
RELEASE_KEY_PASSWORD=..../gradlew testDebugUnitTest # 919 tests
./gradlew connectedDebugAndroidTest -Pandroid.testInstrumentationRunnerArguments.class=com.sysadmindoc.callshield.platform.TargetSdkBehaviorSmokeTestRun tests, lint, release metadata checks, and artifact builds locally before publishing.
| Component | Technology |
|---|---|
| Language | Kotlin 2.2.21 |
| UI | Jetpack Compose BOM 2026.05.00 + Material 3 |
| Theme | System, Light, Graphite, and true-black AMOLED |
| Database | Room 2.8.4 (SQLite) — 8 entities |
| Networking | OkHttp 5.3.2 + certificate pinning |
| JSON | Moshi |
| ML | Pure Kotlin gradient-boosted tree (20 features) |
| Settings | DataStore Preferences 1.2.1 |
| Background | WorkManager 2.11.2 |
| Community API | Cloudflare Workers |
| URL Safety | URLhaus (abuse.ch) |
| Verification | Local Gradle, lint, and release-artifact checks |
| Tests | 919 JVM unit tests (JUnit) |
| Strings | 1160 string resources and 29 plural groups (translation-ready) |
| Accessibility | 100+ content descriptions, 48dp touch targets |
| Min SDK | 29 (Android 10) |
| Target SDK | 36 |
For deep technical details, see CLAUDE.md.
MIT
