AdAway is an open-source, system-level ad blocker for Android. It uses the hosts file (root mode) or a local VPN (non-root mode) to block ads, trackers, malware, and unwanted domains network-wide — across every app and browser.
AdAway ships with a modernized Material 3 UI, deep FilterLists.com integration, categorized filter management, and one-tap onboarding.
- What's New
- Features
- Screenshots
- Installing
- Architecture
- Filter Categories
- Building from Source
- How-To Guides
- Permissions
- Troubleshooting
- Contributing
- License
| Version | Highlight |
|---|---|
| v13.4.9 | Runtime-truth, crash-safety, and release-gate hardening |
| v13.4.8 | Fix literal "null" description in FilterLists.com Discover tab |
| v13.4.6 | ATK-29b: dotless-i / dotted-I Unicode injection bypass closed |
| v13.4.2 | Security hardening: 22 attack vectors fixed (SSRF, MitM CA pin, backup limits, etc.) |
| v13.3.x | Reliability: NPE/race/threading fixes, Force English locale, download error feedback |
| v13.0–v13.2 | Parallel pipeline, Conditional GET, WAL mode, FilterLists.com browser, presets |
| Mode | How it works | Root needed? |
|---|---|---|
| Root | Writes to /etc/hosts — blocks at kernel level |
✅ Yes |
| VPN | Runs a local DNS proxy via Android VPN API | ❌ No |
Both modes block at the DNS level — no content injection, no HTTPS inspection.
- 12 filter categories — ADS, YOUTUBE, PRIVACY, MALWARE, CRYPTO, SOCIAL, DEVICE, SERVICE, ANNOYANCES, REGIONAL, USER, CUSTOM
- Quick-start presets — Safe / Balanced / Aggressive chip taps on Discover
- FilterLists.com browser — 5000+ community lists, search by name, tag, or language
- Categorized source view — sources grouped and color-coded by category
- Custom lists — compatible DNS-oriented lists with explicit hosts, domains, allowlist, or redirect rules
- One-tap subscribe / unsubscribe from FilterLists.com directory
- Filter Sets — save named presets, apply with one tap
- Scheduled updates — daily or weekly automatic list refresh
- Parallel download + parse pipeline using all available CPU cores
- Conditional GET skips re-downloading unchanged lists (ETag / Last-Modified)
- Batch DB inserts (5000 hosts per transaction)
- WAL mode — reads never block writes
- Global host deduplication — same host from multiple sources stored once
- OkHttp connection pool tuning — larger pool, more parallel connections
- WaTg Safety Allowlist — wildcards for
*.whatsapp.com,*.whatsapp.net,*.fbcdn.net,*.facebook.com,*.telegram.org,*.telegram.me,*.t.meprevent blocking WhatsApp and Telegram - SOCIAL / DEVICE categories flagged as "may break services" — shown with warnings in UI
- Download errors persisted per-source — visible in the source card without re-downloading
| Home | Discover | More |
|---|---|---|
| Status, stats, one-tap toggle | Browse & subscribe FilterLists.com | Tools, settings, about |
- Go to Releases.
- Download the latest
AdAway_<version>.apkasset. - If you have the official AdAway installed, uninstall it first — different signing key.
- Open the APK on your device → allow "Install unknown apps" if prompted.
- Open AdAway and complete one-tap onboarding.
- Android 8.0 (API 26) or later
- Root mode: rooted device with
subinary - VPN mode: no root needed
Note: CI debug artifacts are signed with a debug key. Tagged GitHub releases must be production-signed and pass APK hash, package, version, and signing-certificate verification before upload. Android prevents updating over the official AdAway when signing keys differ. Uninstall first.
AdAway/
|-- app/ # Android application (Java 17)
|-- sentrystub/ # Sentry stub for release builds
`-- tcpdump/ # Source-only native packet-capture history; not packaged
Current Gradle builds include :app and :sentrystub; tcpdump is source-only for APK
purposes, and the historical local webserver is absent from the current build. The runtime
critical path is the Android app database, parser, root hosts writer, and VPN DNS proxy.
app/src/main/java/org/adaway/
├── broadcast/
│ ├── BootReceiver.java # Restart protection on reboot
│ ├── UpdateReceiver.java # Trigger source updates externally
│ └── Command.java # Broadcast command enum
├── db/
│ ├── dao/ # HostsSourceDao, HostListItemDao, HostEntryDao
│ ├── entity/ # HostsSource, HostListItem, HostEntry, HostsMeta
│ └── AppDatabase.java # Room DB (WAL mode, v11, 10 migrations)
├── model/
│ ├── source/
│ │ ├── SourceModel.java # Download → Parse → Insert pipeline
│ │ ├── SourceLoader.java # Parser (hosts/domains/adblock formats)
│ │ ├── FilterListCatalog.java # 60+ curated lists across 12 categories
│ │ ├── FilterListCategory.java # Category enum (ADS/PRIVACY/MALWARE/…)
│ │ └── FilterSetStore.java # Named presets + scheduling
│ ├── adblocking/ # Root/VPN switching logic
│ ├── backup/ # Backup/restore (JSON format)
│ ├── git/ # GitHub/GitLab/Gist source types
│ └── update/ # APK self-update check
├── ui/
│ ├── home/ # HomeActivity (nav shell) + HomeFragment + ViewModel
│ ├── discover/ # FilterLists.com browser + catalog presets
│ ├── more/ # Tools & settings navigation
│ ├── onboarding/ # First-run single-screen wizard
│ ├── lists/ # Custom blocked/allowed/redirected rules
│ ├── hosts/ # Filter sources management (HostsSourcesActivity)
│ ├── prefs/ # Preferences screens
│ ├── adware/ # Installed app adware scanner
│ ├── log/ # Real-time DNS query log
│ └── domainchecker/ # Manual domain lookup tool
├── util/
│ └── AppExecutors.java # diskIO / networkIO / mainThread pools
└── vpn/ # Local VPN + DNS proxy (non-root mode)
├── dns/ # DnsPacketProxy, DnsQuery, DnsQueryQueue
└── worker/ # VpnWorker, watchdog, throttler
diskIO() → Single-threaded executor — Room DB reads/writes
networkIO() → Fixed-size pool — OkHttp HTTP calls (NEVER mix with diskIO)
mainThread() → Android main looper — UI updates only
WorkManager → Long-running background jobs (subscribe-all, scheduled updates)
Rule: Network on networkIO(). DB on diskIO(). UI on mainThread(). Never cross streams.
hosts_sources
id, label, url, enabled, type, entityTag, last_modified_online,
last_download_error, category, generation
hosts_lists
id, host, type (0=block/1=allow/2=redirect), source_id, generation
hosts_meta
id (single row), current_generation
host_entries
id, host, type, redirect_target (runtime resolved entries for VPN)
Generation system: Each update cycle bumps the generation counter. Entries from the previous generation are cleaned up after the new generation is fully written — prevents partial state.
HTTP 304 handling (v13.3.3 fix): When a server returns 304 Not Modified, existing entries are migrated from generation G → G+1 before cleanup runs, so nothing is deleted.
| Category | Safe? | Default On | Description |
|---|---|---|---|
| ADS | ✅ Safe | ✅ Yes | Advertisement servers — rarely breaks anything |
| MALWARE | ✅ Safe | ✅ Yes | Malware, phishing, ransomware domains |
| YOUTUBE | ✅ Safe | ❌ No | YouTube in-stream ad servers |
| PRIVACY | ✅ Safe | ❌ No | Trackers, analytics, fingerprinting |
| CRYPTO | ✅ Safe | ❌ No | Browser-based cryptocurrency mining |
| ANNOYANCES | ✅ Safe | ❌ No | Cookie banners, popup overlays |
| REGIONAL | ✅ Safe | ❌ No | Region-specific ad networks |
| SERVICE | ❌ No | In-app ads (Spotify, etc.) — may break those apps | |
| SOCIAL | 🔴 Aggressive | ❌ No | Social media trackers — may break Facebook/Instagram/WhatsApp |
| DEVICE | 🔴 Aggressive | ❌ No | OEM telemetry (Samsung/Xiaomi) — may break OEM features |
| USER | — | — | Your personal blocked/allowed/redirected rules |
| CUSTOM | — | — | Custom sources added via URL |
| Preset | Categories Included |
|---|---|
| Safe | ADS + MALWARE |
| Balanced | ADS + MALWARE + PRIVACY + YOUTUBE |
| Aggressive | ADS + MALWARE + PRIVACY + YOUTUBE + CRYPTO + ANNOYANCES + REGIONAL |
| Tool | Required Version |
|---|---|
| Android Studio | Hedgehog (2023.1) or later |
| Android SDK | API 36 |
| Android Build Tools | 36.0.0 |
| Android NDK | 27.2.12479018 (exact - C native code) |
| JDK | 21 |
| Git | Any recent version |
git clone https://github.com/stevesolun/AdAway.git
cd AdAway
# Debug build (for development + testing)
./gradlew assembleDebug
# Direct-download release build
./gradlew :app:assembleDirectRelease --dependency-verification=strict
# Run unit tests
./gradlew testDebugUnitTest
# Install directly to connected device or emulator
./gradlew installDebugOutput APKs:
- Debug:
app/build/outputs/apk/debug/app-debug.apk - Direct release:
app/build/outputs/apk/directRelease/app-directRelease.apk
For pre-release UI review, run the connected UX matrix on an emulator:
powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\run-ux-matrix.ps1 `
-OutputDir app\build\reports\ux-matrixThe runner covers baseline, 1.3/1.6 font scales, and RTL pseudo-locale variants.
It pulls screenshots for the key app shells and writes
app/build/reports/ux-matrix/ux-matrix-review.md, a checklist packet for manual
review of clipping, overlap, touch targets, RTL anchoring, FAB clearance, and the
AdAway bird brand signal. The packet includes Source commit, binding the
screenshots and checklist to the source revision that generated them.
After the reviewer marks every checklist item in ux-matrix-review.md as
complete, verify the sign-off packet and write a durable report:
powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\verify-ux-signoff.ps1 `
-ReviewPacket app\build\reports\ux-matrix\ux-matrix-review.md `
-Reviewer "<name>" `
-ReportPath app\build\reports\ux-matrix\ux-signoff-report.mdFor CI-backed UX sign-off, run the manual
.github/workflows/verify-ux-signoff.yml workflow with review_packet_base64,
the base64-encoded checked ux-matrix-review.md, and the reviewer identity.
Successful runs upload a ux-signoff-report artifact with the current source
commit and the checked packet's source commit; stale packets from another
source revision are rejected.
After release artifact verification, physical release smoke, UX sign-off, and license-boundary checks have all produced reports, aggregate them into one final readiness report:
powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\verify-release-readiness.ps1 `
-ReleaseArtifactReport release-artifacts\verification-report.md `
-PhysicalSmokeReport release-smoke\release-smoke-report.md `
-UxSignOffReport app\build\reports\ux-matrix\ux-signoff-report.md `
-UxReviewPacket app\build\reports\ux-matrix\ux-matrix-review.md `
-LicenseBoundaryReport app\build\reports\license-boundary\license-boundary-report.md `
-ReportPath release-readiness-report.mdThe readiness verifier requires all proof reports to describe the same source commit.
It also requires the artifact verifier and physical smoke report to
describe the same release tag, APK name, APK SHA-256, and signing certificate
digest; do not combine proof reports from different release attempts. The
release artifact report must come from verify-release-artifacts and include
Source commit,
Checksum verification: passed, Manifest signature: passed,
Manifest payload: passed, and a checked Expected certificate SHA-256.
The physical smoke report must come from run-release-smoke.ps1 in
physical-device mode and include Release tag, Package,
Signer certificate check: True, Signer certificate SHA-256,
Device serial SHA-256, Launch pid observed, and Source commit.
Use the tagged release artifact license-boundary report for
-LicenseBoundaryReport; it must show Strict artifacts: true with the same APK and SBOM
artifact names from the release artifact verification report, not the regular CI source-only
license-boundary report.
The UX sign-off report must come from verify-ux-signoff.ps1 and include a
reviewer, review packet, Source commit, Review packet source commit,
Review packet SHA-256, checked item count, Unchecked items: 0, and
Issues: 0; do not use hand-written pass markers.
When -UxReviewPacket is provided, final readiness also hashes the checked
review packet and requires the same review packet hash as the sign-off report.
The generated release-readiness-report.md repeats the release tag, APK, APK
SHA-256, SBOM, source commit, and UX review packet hash, then records
Release artifact report SHA-256, Physical smoke report SHA-256,
UX sign-off report SHA-256, and License boundary report SHA-256 so the final
summary is tied to the exact proof reports it consumed.
For CI-backed final aggregation, run the manual
.github/workflows/verify-release-readiness.yml workflow after the post-publish
artifact verifier, physical release smoke, UX sign-off, and release
license-boundary reports exist. Provide the run IDs for
release-artifact-verification-report, physical-release-smoke-report, and
release-license-boundary-reports, plus ux_signoff_run_id, the run that
uploaded the generated ux-signoff-report and checked ux-matrix-review.md.
Successful runs upload a release-readiness-report artifact.
Add to ~/.gradle/gradle.properties:
signingStoreLocation=/path/to/keystore.jks
signingStorePassword=your_store_password
signingKeyAlias=your_key_alias
signingKeyPassword=your_key_password
updateManifestPublicKeyBase64=base64_encoded_spki_public_keyPush a signed version tag to trigger the release pipeline:
git tag -s v<version> -m "AdAway <version>"
git verify-tag v<version>
git push origin v<version>GitHub Actions (.github/workflows/fork-release-apk.yml) will:
- Verify the signed release tag with
RELEASE_TAG_PUBLIC_KEY_BASE64 - Build
assembleDirectRelease - Rename the APK to
AdAway_<version>.apk - Verify the APK package name, tag-matching version, signer SHA-256, and file SHA-256
- Generate the signed update manifest
- Generate the CycloneDX SBOM and SHA-256 checksum files
- Attest the APK, manifest, SBOM, and their
.sha256checksum sidecars - Verify those GitHub attestations with the canonical artifact verifier
- Create a GitHub Release with the APK, manifest, checksums, and SBOM attached
Before announcing a release, run the RELEASING.md artifact verifier and
release smoke. Full smoke requires an attached physical device. After the
release is created, run the manual verify-release-artifacts.yml workflow as
the CI-backed post-publish artifact and attestation check; successful runs
upload a release-artifact-verification-report artifact. For install/launch
coverage, run physical-release-smoke.yml on a self-hosted runner labeled
android-device; it downloads the tagged APK and runs the full release smoke
against a physical device. Successful physical smoke runs upload a
physical-release-smoke-report artifact with release tag, APK name, APK
SHA-256, signer certificate identity, and the observed launch result. Regular
CI also uploads a license-boundary-report; tagged direct-APK releases upload
release-license-boundary-reports covering source and packaged artifact
GPL/MIT boundary checks.
Run verify-release-readiness.yml last to download those proof artifacts,
download the generated UX sign-off report, run verify-release-readiness.ps1,
verify the same review packet hash, and upload the final
release-readiness-report artifact.
Repository Secrets (for production-signed APKs):
| Secret | Description |
|---|---|
ANDROID_KEYSTORE_BASE64 |
Base64-encoded keystore file |
ANDROID_KEYSTORE_PASSWORD |
Keystore password |
ANDROID_KEY_ALIAS |
Key alias |
ANDROID_KEY_PASSWORD |
Key password |
UPDATE_MANIFEST_PUBLIC_KEY_BASE64 |
Base64 SPKI public key embedded for signed update-manifest verification |
UPDATE_MANIFEST_PRIVATE_KEY_BASE64 |
Base64 PEM private key used to sign update manifests |
ANDROID_RELEASE_CERT_SHA256 |
Expected release APK signing certificate SHA-256 digest |
RELEASE_TAG_PUBLIC_KEY_BASE64 |
Base64 public key imported before git verify-tag |
Regular CI and CodeQL build debug artifacts. Production release builds are expected to fail closed without the signing properties, signed-tag key, and update-manifest trust material above.
| Library | Version | Purpose |
|---|---|---|
| OkHttp | 5.4.0 | HTTP downloads and update checks |
| Timber | 5.0.1 | Logging (no-op in release) |
| libsu | 6.0.0 | Root shell access |
| pcap4j | 1.8.2 | VPN packet capture/processing |
| dnsjava | 3.6.5 | DNS packet parsing |
| Guava | 33.6.0-android | Utilities |
- Open the app — the onboarding screen appears.
- Auto-detection checks root availability. If root is unavailable, VPN mode is pre-selected; rooted devices can choose Root mode or VPN mode.
- Tap Start protecting — default filter lists subscribe in the background.
- You land on Home. Protection is active.
- Tap the Discover tab.
- Use the search box or filter by tag / language.
- Tap a list → Subscribe.
- The list is added and enabled. Tap Update on Home to apply.
- Tap the Discover tab.
- Tap Safe, Balanced, or Aggressive.
- A snackbar confirms how many lists were added.
- Tap Update on Home to activate.
- Go to More → Filter Sources.
- Tap the + button.
- Enter the list URL and select the format.
- Tap Add → enable → tap Update on Home.
- Go to More → Custom Rules.
- Tap + → choose Block or Allow → enter the domain.
- Tap Save. Rules apply immediately (no update needed).
- More → Custom Rules → ⋮ → Batch Import.
- Paste domains, one per line.
- Choose Block or Allow → confirm.
- Go to More → DNS Log.
- See real-time queries. Blocked = red, allowed = green.
- Tap any entry → Block or Allow to add a custom rule.
- More → Filter Sources → ⋮ → Manage schedules.
- Set daily (time) or weekly (day + time).
- Updates run in the background at the scheduled time.
Option A — Domain Checker (no API key needed):
- More → Domain Checker.
- Type any domain (or URL — port is stripped automatically).
- Tap Check → see Blocked / Not blocked and which source blocked it.
- More → Preferences → Backup & Restore.
- Backup — exports filter sources, custom rules, and settings as JSON.
- Restore — imports from a backup file.
Some Android versions override language settings. To lock AdAway in English:
- More → Preferences → General → Force English.
- Force-stop and relaunch the app.
The Discover tab integrates the FilterLists.com public API:
| Endpoint | Used for |
|---|---|
GET /lists |
Browse all filter lists (with tagIds, languageIds, syntax) |
GET /tags |
Tag chip labels (41 tags, cached 24h in SharedPreferences) |
GET /languages |
Language spinner (81 languages, cached 24h) |
GET /lists/{id} |
Subscription URL for a specific list |
Responses are cached with a 24-hour TTL. No API key required.
| Permission | Purpose |
|---|---|
INTERNET |
Download filter lists, query FilterLists.com, and check updates |
RECEIVE_BOOT_COMPLETED |
Restart VPN / hosts protection on device reboot |
FOREGROUND_SERVICE |
Background subscribe-all worker notification |
POST_NOTIFICATIONS |
Update completion notifications (Android 13+) |
| Launcher package visibility query | Adware scanner — list launchable installed apps |
BIND_VPN_SERVICE |
Local VPN mode (non-root) |
Root mode additionally requires su access to write /etc/hosts.
| Symptom | Fix |
|---|---|
| Ads not blocked | Tap Update on Home to refresh filter lists |
| VPN disconnects randomly | Check More → Preferences → VPN settings |
| WhatsApp stops working | Keep the built-in WaTg safety allowlist enabled; if a custom source still breaks messaging, use Domain Checker or DNS Log to add a targeted allow rule |
| Samsung Pay / OEM feature broken | Disable the DEVICE category |
| Filter list not updating | Check network; try More → Filter Sources and tap the source update icon |
| App crashes on subscribe | Clear app data and re-onboard |
| Can't install over official AdAway | Uninstall the official AdAway first (different signing key) |
| "Install unknown apps" blocked | Android Settings → Apps → Special app access → Install unknown apps |
| Domain Checker shows wrong result | Port numbers (e.g., :8080) are stripped automatically since v13.3.5 |
| Force English not working | Force-stop the app after toggling the setting |
- Bug reports: this fork's issues
- PRs welcome — keep commits atomic, present tense, explain why not what
Code style:
- Java 17 target
- 4-space indent, no tabs
- 100-char line width
- Non-public fields prefixed
m - Acronyms as words:
getUrl()notgetURL()
Threading rules (mandatory):
- DB operations:
AppExecutors.getInstance().diskIO() - HTTP calls:
AppExecutors.getInstance().networkIO() - UI:
AppExecutors.getInstance().mainThread() - Never call
AppExecutors.getInstance()in astatic finalfield initializer
DB migrations: Room v11. Any schema change requires a migration file in db/migration/. Test with MigrationTest.
MIT relicensing is tracked as a future option only. The distributed app remains GPLv3+ until GPL-derived app/VPN code, assets, and third-party notice boundaries are cleared and verified; see docs/mit-relicensing-plan.md.
GPL v3 — same as the upstream AdAway project.
This is a fork of AdAway/AdAway. All credit to the original AdAway team and contributors.
Crafted with love by Steve Solun · Forked from AdAway