Skip to content

Repository files navigation

🌌 HeadsUp — A Decentralized, Local-First Ambient Mindfulness & Geospatial Sync Application

Android Build Kotlin Compose License

"Notice: as you see digital absence in others, where does your own presence go?"

HeadsUp is a local-first Android application designed to map collective digital attention patterns and assist users in cultivating physical presence. Instead of traditional screen-time trackers that treat digital distraction as an individual failing, HeadsUp reframes it as a collective, atmospheric condition—like fog or pollution—that we generate together.

Observers use a hardware-key, screenless logger to register incidents of phone absorption around them. This data is geohashed, stored locally in a Room database, and synced in real-time to a serverless, user-owned Google Sheets backend to construct an ambient geospatial ledger of human attention.


🎨 Philosophy & Concept: Reclaiming the Attention Commons

Modern mobile interfaces are built to capture and commodify human attention, creating spaces of digital absence—where people are physically present but mentally detached. HeadsUp provides two key mechanisms to break this loop:

1. The Screenless Observational Tap

To break the cycle of checking one's own phone, the user does not interact with a UI. Instead:

  • While holding the phone down by their side or in their pocket, the user presses the Volume Up or Volume Down button upon noticing screen absorption.
  • The app intercepts this hardware event in the background, fetches a coarse location, encodes it, commits a record to the local SQLite database, and plays a subtle haptic feedback tick.
  • The action serves as a micro-meditation: by noticing screen distraction, the user immediately pulls themselves back into physical awareness.

2. The Constellation of Shared Attention

Logged events are aggregated into Geohash-5 zones (approximately 4.9km × 4.9km squares) and mapped.

  • Active distraction zones glow in warm brass geometric grids.
  • Inactive zones settle into a deep graphite hue.
  • The map visualizes attention not as a coordinate of surveillance, but as an atmospheric weather condition.

🏗️ Technical Architecture & Data Flow

HeadsUp is built using a local-first, decentralized, and serverless architecture:

graph TD
    A[Hardware Volume Click / Screen Tap] --> B[Room Local SQLite DB]
    B --> C[HeadsUpRepository]
    C -->|Unsynced Taps| D[SheetsNetworkClient - Retrofit]
    D -->|HTTPS POST JSON| E[Google Apps Script Web App]
    E -->|Write Row| F[(Google Sheets Database)]
    F -->|Read Stream| E
    E -->|HTTPS GET JSON| C
    C -->|Live Event Flow| G[HeadsUpViewModel]
    G -->|Compose State| H[Jetpack Compose UI & Leaflet Map WebView]
Loading

1. Data Models & SQLite Schema (Room)

The local database stores two primary entities to support offline capabilities and caching:

@Entity(tableName = "taps")
data class TapRecord(
    @PrimaryKey(autoGenerate = true) val id: Long = 0,
    val latitude: Double,
    val longitude: Double,
    val geohash5: String,
    val geohash7: String,
    val createdAt: Long = System.currentTimeMillis(),
    val isSynced: Boolean = false
)

@Entity(tableName = "user_session")
data class UserSession(
    @PrimaryKey val id: Int = 1,
    val uid: String = UUID.randomUUID().toString(),
    val nickname: String = "Observer_" + uid.take(6),
    val createdAt: Long = System.currentTimeMillis(),
    val totalTaps: Int = 0,
    val currentStreak: Int = 0,
    val longestStreak: Int = 0,
    val lastTapAt: Long = 0L,
    val lastTapGeohash: String = "",
    val publicOnMap: Boolean = true,
    val onboardingCompleted: Boolean = false,
    val googleSheetsUrl: String
)

2. Serverless Synchronization Layer (Google Sheets API)

To avoid server maintenance costs, the app uses a serverless, user-owned Google Sheets backend deployed via Google Apps Script:

  • doPost(e): Appends incoming JSON Tap records into the Google Sheet. Generates UUIDs if missing.
  • doGet(e): Reads the spreadsheet rows, aggregates taps by Geohash-5, sorts them, and returns a JSON payload containing the last 50 events and all spatial aggregates.
  • Refer to BACKEND_SETUP.md for full script source code and deployment instructions.

💻 Local Development & Compilation

Prerequisites

  • Android Studio (Koala or later)
  • Java Development Kit (JDK) 17 or higher
  • Android SDK 36 (targetSdk 36, minSdk 24)

1. Project Initialization

Configure your local environment parameters by copying the example environment template:

cp .env.example .env

In your .env file, specify your Google Sheets script URL under backend_url if you wish to initialize the app with a default endpoint.

Specify your local Android SDK location inside local.properties:

sdk.dir=C\:\\Users\\YourUsername\\AppData\\Local\\Android\\Sdk

2. Gradle Build Tasks

Use the Gradle wrapper to clean build, compile, and run:

  • Clean Build Cache:
    ./gradlew clean
  • Compile Debug APK:
    ./gradlew assembleDebug
    Output binary: releases/HeadsUp-debug.apk
  • Compile Production Release APK:
    ./gradlew assembleRelease
    Output binary: releases/HeadsUp-release.apk
  • Run Unit Tests:
    ./gradlew test

🔑 Build Optimization & Signing Fallbacks

Release Signing Fallback

app/build.gradle.kts is pre-configured to build without crashing on missing environment keys by falling back to the debug keystore when production environment variables are not found:

signingConfigs {
  create("release") {
    val keystorePath = System.getenv("KEYSTORE_PATH")
    if (keystorePath != null && keystorePath.isNotEmpty()) {
      storeFile = file(keystorePath)
      storePassword = System.getenv("STORE_PASSWORD")
      keyAlias = "upload"
      keyPassword = System.getenv("KEY_PASSWORD")
    } else {
      storeFile = file("${rootDir}/debug.keystore")
      storePassword = "android"
      keyAlias = "androiddebugkey"
      keyPassword = "android"
    }
  }
}

ProGuard Rules

The map view is rendered in a custom dark-themed Leaflet JS template inside an Android WebView. To protect the Kotlin-to-WebView interface bridge from code obfuscation during minification, the following rule is configured in app/proguard-rules.pro:

-keepclassmembers class * {
    @android.webkit.JavascriptInterface <methods>;
}

🔒 Security & Privacy Architecture

  • Coarse Geo-Fencing: Exact GPS coordinates are never leaked to the shared map. The app decodes coordinates into a Geohash-5 string (representing a grid boundary of ~4.9km × 4.9km) before syncing, protecting the observer's exact location.
  • Zero Tracker Telemetry: The app does not compile with third-party tracking, analytics, crash-telemetry, or advertising SDKs.
  • Data Sovereignty: Observations are written directly to your own Google Sheet. No corporate databases are utilized. You own, edit, and retain 100% of your data logs.

🤝 Contribution & Pull Request Guidelines

Contributions that align with the philosophy of reclaiming attention and cultivating human presence are welcome. Please refer to:

  • CONTRIBUTING.md for coding and design standards.
  • SUPPORT.md for support guidelines and reporting bugs.

About

A local-first, privacy-respecting Android application built with Jetpack Compose that maps collective digital absence (mindless screen absorption) via screenless hardware gestures to help users reclaim physical presence.

Topics

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Contributors

Languages