A gesture-first Android browser with Material 3 Expressive design, local privacy tools, and a tab system built for visual navigation.
- Made for gestures. Switch tabs from the address bar, swipe into the visual overview, and dismiss cards with spring motion and haptic feedback.
- Private by design. Filtering, history, favorites, profiles, and privacy telemetry stay local.
- Feels at home on Android. Dynamic color, edge-to-edge content, Predictive Back, Autofill, passkeys, downloads, sharing, printing, and default-browser integration.
- Floating bottom chrome over edge-to-edge WebView content
- Pull to refresh, direct URL navigation, QR scanning, local domain completion, and optional provider-backed search suggestions (disabled in private tabs)
- Google, DuckDuckGo, Bing, Brave, Ecosia, Startpage, and Qwant search
- Address commands with
>for tab, profile, cache, cookie, and navigation actions - Background tabs, downloads, sharing, printing, external apps, and assistant summaries
- Persistent tabs with saved page previews, favicons, pinning, reordering, and automatic cleanup
- Cover flow, compact grid, and preview-free list layouts
- Optional per-profile WebView storage isolation where the installed provider supports it
- Private tabs that keep their session and journey data in memory only
- Candy Trails: persistent branching navigation graphs with pan, zoom, direct navigation, and forkable paths
- Site Capsules: profile-bound home-screen shortcuts with configurable navigation boundaries and minimal browser chrome
- EasyList/EasyPrivacy hosts and domain-scoped cosmetic CSS plus a pinned, safely representable uAssets subset
- Third-party-cookie blocking and cosmetic cookie-banner hiding
- Privacy X-Ray: live per-tab block counts, categories, domains, and exceptions
- Filter Studio: global or profile rules, import/export, and confirmed HTTPS subscriptions
- Safe Browsing, TLS failure handling, blocked unsafe schemes, and external-scheme allowlisting
Candy Browser requires Android 14 (API 34) or newer.
Production builds check GitHub for updates at startup and offer the signed APK for download. Android still requires you to open the downloaded file and approve installation.
Requirements: Android SDK 35 and JDK 17. Point JAVA_HOME to your JDK 17 installation.
./gradlew testDebugUnitTest lintDebug assembleDebugDebug APK: app/build/outputs/apk/debug/app-debug.apk. It installs as
dev.sk2andy.materialbrowser.debug, uses the label Candy Browser Debug, and has a badged launcher
icon, so it can stay installed beside the release app.
Release builds are minified with R8 and require a signing key. Never commit a keystore or its credentials. Configure signing locally with either an ignored project file or environment variables.
For the first release only, create a long-lived key if no release key exists yet. Reuse that same key for every later release:
mkdir -p .signing
keytool -genkeypair \
-keystore .signing/candy-release.keystore \
-storetype PKCS12 \
-alias candy \
-keyalg RSA \
-keysize 4096 \
-validity 10000For an ignored project file:
cp keystore.properties.example keystore.properties
# Replace every placeholder in keystore.properties, then:
./gradlew assembleLocalReleaseAlternatively, export the same values from ~/.zshrc.shared:
export CANDY_RELEASE_KEYSTORE_PATH=/absolute/path/to/project/.signing/candy-release.keystore
export CANDY_RELEASE_STORE_PASSWORD='replace-me'
export CANDY_RELEASE_KEY_ALIAS='candy'
export CANDY_RELEASE_KEY_PASSWORD='replace-me'Signed local APK: app/build/outputs/apk/localRelease/app-localRelease.apk
localRelease installs beside the GitHub build as dev.sk2andy.materialbrowser.local and uses a
separate launcher icon and the label Candy Browser Local. GitHub update prompts are disabled for
this side-by-side build because production APKs cannot update its package. The GitHub workflow
continues to use assembleRelease, preserving the production application ID and icon.
The manual Release Android APK workflow tests the selected source revision, builds and verifies a
signed APK, creates a v<version> source tag, and publishes the APK plus its SHA-256 checksum. Add
four repository secrets once:
base64 < "$CANDY_RELEASE_KEYSTORE_PATH" | gh secret set CANDY_RELEASE_KEYSTORE_BASE64
printf '%s' "$CANDY_RELEASE_STORE_PASSWORD" | gh secret set CANDY_RELEASE_STORE_PASSWORD
printf '%s' "$CANDY_RELEASE_KEY_ALIAS" | gh secret set CANDY_RELEASE_KEY_ALIAS
printf '%s' "$CANDY_RELEASE_KEY_PASSWORD" | gh secret set CANDY_RELEASE_KEY_PASSWORDPin the public certificate fingerprint separately. This prevents an accidentally replaced keystore from publishing an APK that installed copies cannot update to:
keytool -exportcert \
-keystore "$CANDY_RELEASE_KEYSTORE_PATH" \
-storepass:env CANDY_RELEASE_STORE_PASSWORD \
-alias "$CANDY_RELEASE_KEY_ALIAS" \
| shasum -a 256 \
| awk '{print $1}' \
| gh variable set CANDY_RELEASE_CERTIFICATE_SHA256The workflow uses the release GitHub environment. Configure required reviewers for that environment
if releases should require a manual approval after dispatch.
Then dispatch a release from GitHub Actions or with GitHub CLI:
gh workflow run release.yml \
-f version=0.2 \
-f prerelease=falseAndroid versionCode is the monotonically increasing GitHub workflow run number plus the current
source-code base of 1, so the first automated release starts at 2. Tags and releases are created
only after tests, release build checks, APK signing, certificate pinning, and signature verification
succeed. Back up the original keystore and credentials securely: Android updates must always use the
same signing key.
Candy uses Android System WebView as its only browser engine and renderer. It does not bundle a Chromium fork, extension runtime, proxy, VPN, or second rendering engine. Filtering stays inside each WebView through local request interception, document-start CSS/DOM rules, and service-worker interception. WebSockets, CNAME cloaking, some redirects, and content inside closed cross-origin or Shadow DOM contexts may get through. Candy pre-registers cosmetic CSS for known GET navigations. Android WebView does not expose cross-origin form POST targets early enough for origin-scoped document-start registration; those navigations receive the existing page-commit fallback and can show a brief cosmetic flash.
Privacy X-Ray includes only blocked requests that WebView can reliably attribute to one tab. Service-worker requests are filtered but excluded from per-tab telemetry when no reliable tab ID is available.
Candy Rules and filter-source details
Candy Rules v1 supports exact host block/allow rules, positive site-to-host pairs, HOSTS entries, and origin-scoped standard CSS selectors. It deliberately rejects JavaScript, regular expressions, redirects, response-header filters, scriptlets, and advanced cosmetic operators rather than approximating them.
Bundled EasyList/EasyPrivacy and uAssets cosmetics are compiled separately from user rules and
merged in memory so exceptions work across sources. Only domain-specific standard ##selector
rules, their domain exclusions, and matching #@# exceptions are retained. Generic selectors,
conditional capability blocks, and procedural operators are skipped. Runtime lookup resolves
selectors for the current host before registering a bounded, exact-origin document-start script;
invalid browser-specific selectors fail independently through insertRule. Mail, Maps, and
Accounts Google hosts intentionally receive no bundled cosmetic CSS.
Bundled defaults may additionally use audited, declarative WebView rules: a site-scoped literal request-path prefix or a known Reject/Remind-later consent control. These rules cannot run imported JavaScript, never click Accept, and never intercept a main-frame navigation. An exact bundled path rule may override the general same-party escape only for its audited page-host, request-host, and literal non-root path prefix.
Imports are bounded and validated atomically. HTTPS subscriptions update only after a user-requested fetch, show a diff, and require confirmation. Private-profile imports remain in memory.
EasyList, EasyPrivacy, and EasyList Cookie data are distributed under CC BY-SA 3.0 or later. The
uAssets-derived network and cosmetic subsets are generated from one pinned revision of the official
uBlock Origin Ads source and distributed under GPL-3.0. Exact sources, revisions, transformations,
and notices ship in app/src/main/assets/; maintenance entry points live in scripts/update_*.sh.
English, German, French, Portuguese, and Spanish.
Candy Browser source code is available under the Mozilla Public License 2.0.
Third-party components and filter data retain their own licenses; bundled notices are available in
app/src/main/assets/third_party_notices.txt and inside the app.









