Network measurement for Android. It collects, visualizes, and exports cellular performance data with FCC BDC compliant bulk submissions.
- Android client for active measurements (download, upload, latency)
- Supabase backend for storage, APIs, and server-side processing
- One-click JSON export aligned with FCC Broadband Data Collection (BDC) mobile speed test spec
- Active development; public API and data schema may change between releases
- FCC BDC export supports hierarchical JSON with download/upload/latency tests nested per submission
/app Android app (Kotlin, Gradle, minSdk 29, targetSdk 34)
/supabase Local Supabase project (config, schema, functions, seed)
High level
- Android client collects location + throughput/latency metrics
- Supabase (Postgres + Auth + Storage + Edge Functions) stores measurement groups (one group is one submeasurement: one measurement is made of one latency, upload and download speed) and serves export endpoints
- Export builder groups valid FCC submissions and emits JSON per spec
- Android Studio Giraffe+ (JDK 17)
- Node 18+ (for Supabase CLI), Docker (for local Postgres)
- Supabase CLI
supabase >= 1.180 - Git LFS (if storing large sample datasets)
git clone https://github.com/CellWatch/cellwatch-app.git
cd cellwatch-appcd supabase
supabase start
supabase db reset # loads full_schema.sql and seed.sqlcd ../app
./gradlew assembleDebug
./gradlew :app:installDebugThe debug build points to your local Supabase instance when configured via
cellwatch.properties(see below). Release builds use the cloud Supabase settings.
Create a cellwatch.properties file at the repository root with the following keys. Debug vs. release behavior is determined in the app’s build config.
# your local development machine's IP address - used for debug build
SUPABASE_LOCAL_URL="http://127.0.0.1:54321"
SUPABASE_LOCAL_API_KEY=
#SUPABASE_LOCAL_URL=
#SUPABASE_LOCAL_API_KEY=
# Supabase cloud URL - used for release build
SUPABASE_URL=
SUPABASE_API_KEY=
# Changing MSAK_SERVER_ENV from "local" to "prod" or "staging" will use M-Lab's
# production or staging servers during local development. The production
# servers are always used in release builds.
MSAK_SERVER_ENV="local"
MSAK_LOCAL_SERVER_HOST="10.0.2.2:8080"
MSAK_LOCAL_SERVER_SECURE=false
MSAK_LATENCY_PORT=1053
MSAK_LOCAL_LATENCY_PORT=1053
MAPBOX_DOWNLOADS_TOKEN=
TCP_TUPLE_URL="http://34.201.124.67/"Notes
SUPABASE_LOCAL_URL/SUPABASE_LOCAL_API_KEYare used by debug builds to talk to your local Supabase started withsupabase start.SUPABASE_URL/SUPABASE_API_KEYare used by release builds to talk to your hosted Supabase project.MSAK_*control the measurement servers (M-Lab). UseMSAK_SERVER_ENV="local"for local testing; change tostagingorprodto point debug builds at those environments.MAPBOX_DOWNLOADS_TOKENis required for map downloads if mapping features are enabled.TCP_TUPLE_URLis used for tuple reporting in the measurement pipeline.
Crash reporting is required. Supply a valid google-services.json in /app and ensure Gradle is configured for Crashlytics uploads in both debug and release as per your org’s policy.
Folder contents:
/supabase
config.toml
full_schema.sql
post_fcc_submission_func.sql
seed.sql
Local dev commands:
cd supabase
supabase start
supabase db reset
full_schema.sqlcreates tables/views/indexes;post_fcc_submission_func.sqlregisters server-side logic for grouping/exports;seed.sqladds minimal sample data.
Safety tip: If you generate or update migration dumps, inspect for unintended data:
grep -E "INSERT INTO|COPY " supabase/migrations/dump-migrations.sql | headMeasurementGroup= top-level run with timestamps, device/network metadataMeasurement= individual samples (download/upload/latency)CellandLocationentities attached where available- Valid FCC submissions flagged via associated
FccSubmissionmetadata
- One JSON object with:
contactandsubmission_categorysubmissions[]where each entry = oneMeasurementGroup- Top-level device/app/network metadata
tests→download/upload/latencysections
- Aligns with FCC BDC mobile speed test hierarchical format
- FCC Bulk JSON Export Process Overview
- Export timestamp and server IP/port reuse the same logic used during upload
# Kotlin style / lint
./gradlew ktlintCheck
./gradlew detekt
# Unit tests (Android)
./gradlew testDebugUnitTest
# Run Supabase migrations (reset local db)
cd supabase && supabase db reset- Conventional Commits
mainprotected; feature branches viafeat/<topic>orfix/<topic>- PRs must include:
- Screenshots for UI changes
- Sample export JSON diffs for data model changes
- GitGuardian scan passing (see below)
We enforce secret scanning on every commit and PR.
Suggested local setup:
# install
pip install ggshield
# authenticate (one-time; follow prompts)
ggshield auth login
# scan staged changes before commit
ggshield secret scan pre-commit
# or scan the whole repo
ggshield secret scan repo .
# CI will run: ggshield secret scan ci- Android unit tests for data processing and export assembly
- Instrumented tests for permission flows and long-running services
- Backend SQL/Edge-function tests using Supabase CLI runners
- Golden-file tests for FCC export: compare JSON against fixtures
We welcome contributions!
- Fork and create a feature branch
- Make changes with tests
- Run ggshield locally
- Submit a PR with context, steps to reproduce, and screenshots/JSON diffs
- Report vulnerabilities privately to
cellwatch@groups.gatech.eduor via GitHub Security Advisory - Keep all API keys in
cellwatch.propertieslocally and in CI secret stores for builds - Enable pre-commit scans to prevent key leaks
Thanks to contributors and upstream projects (Android, Kotlin, Supabase, Mapbox, M-Lab).
Maintainers: https://cellwatch.cc.gatech.edu/. For roadmap and discussions, see GitHub Issues/Discussions.