Skip to content

Repository files navigation

Hadron for Android

Native Android client for the Hadron knowledge-memory platform, and the counterpart of hadron-ios. v1 scope: sign in with your Hadron account and search across your memories.

Layout

  • app/ — the app module (Kotlin, Jetpack Compose / Material 3, min SDK 26).
  • hadronkit/ — the shared client stack: OAuth 2.1 PKCE sign-in, Keystore-backed secure storage, GraphQL client. A direct port of hadron-ios' HadronKit Swift package, kept a separate module so the UI can't reach around it into raw HTTP.
  • gradle/libs.versions.toml — the version catalog; the single place versions live.

Building

./gradlew :app:assembleDebug

Or open the project in Android Studio and run. local.properties (with sdk.dir) is generated by Studio and gitignored; create it by hand for a headless build.

Release builds are minified with R8:

./gradlew :app:bundleRelease

Signing

The upload key never lives in the repo. keystore.properties, *.jks and *.keystore are all gitignored; with none of them present the release build stays unsigned and still succeeds, so a fresh clone can run assembleRelease and lintRelease without any secrets. A Play upload needs a signed bundle, though.

Create the upload key once — keep the .jks and its passwords in a password manager, because losing them means you can no longer update the app unless you're on Play App Signing and request a key reset:

keytool -genkeypair -v -keystore hadron-upload.jks -alias hadron-upload -keyalg RSA -keysize 4096 -validity 10000

Then cp keystore.properties.example keystore.properties and fill it in. storeFile resolves relative to the repo root. CI can skip the file and set HADRON_KEYSTORE_FILE, HADRON_KEYSTORE_PASSWORD, HADRON_KEY_ALIAS and HADRON_KEY_PASSWORD instead.

keyPassword is the same value as storePassword. Since JDK 9 keytool writes PKCS12 keystores by default — even when the file is named .jks — and PKCS12 has no separate key password. There is no second secret to create.

Two guards, because an unsigned artifact that builds successfully is only discovered at upload time:

  • keystore.properties present but with a missing or blank value fails configuration, naming the offending keys.
  • A storeFile that doesn't exist fails at :app:validateSigningRelease.

With no keystore.properties and no env vars at all, the release build is unsigned and succeeds — that's the fresh-clone case, so assembleRelease and lintRelease stay runnable without secrets.

Auth

The app signs in via Hadron's OAuth 2.1 authorization server (srv.hadronmemory.com): discovery → dynamic client registration (client id cached in the secure store) → PKCE S256 authorization in a Chrome Custom Tab (provider choice: Apple, GitHub, Google, or an email magic link) → token exchange. The issued hdr_user_* key is a long-lived bearer; there is no refresh token — a 401 drops the app back to signed-out.

Because Android can kill the process while the browser is in front, the flow is split around a durable pending-authorization record rather than an in-memory continuation (see OAuthService). The email magic link, which completes in the system browser after a detour through the mail app, gets home the same way: via the manifest's com.hadronmemory.android intent-filter.

The token is sealed with an AES-256-GCM key in the AndroidKeyStore and excluded from both cloud backup and device-to-device transfer — the Android equivalent of the iOS ThisDeviceOnly posture.

Play Store checklist

Owner-side, none of it in this repo:

  • Play Console app record for com.hadronmemory.android; upload key or Play App Signing.
  • Data safety form: email address, name, user ID; purpose app functionality; no tracking; no third-party sharing. Mirrors hadron-ios/Hadron/PrivacyInfo.xcprivacy.
  • Account deletion: Play requires in-app initiation and a web deletion URL. In-app lives under the account menu → Manage Account → portal /app/account; declare https://hadronmemory.com/app/account as the deletion URL.
  • Store listing assets live in store/. Note the two have opposite alpha requirements — easy to get backwards:
    • play-icon-512.png — 512×512, 32-bit PNG with alpha. The iOS source (hadron-ios/…/AppIcon.appiconset/icon-1024.png) is 1024×1024 8-bit RGB with no alpha, so it fails both counts and the Console offers to crop it, which would chop the artwork rather than fix it. The committed file is that source box-downsampled 2:1 with an opaque alpha channel added.
    • play-feature-graphic-1024x500.png — 1024×500, 24-bit PNG, no alpha. Required to publish; the App Store has no equivalent, so there was nothing to port. Regenerate with store/make-feature-graphic.py.
    • screenshots/ — five phone shots at 1080×1920, 24-bit PNG. Play caps aspect at 2:1, so the stock emulator's 1080×2400 (2.22:1) is rejected; capture on an AVD built from the pixel_2 profile. adb screencap also emits RGBA, which Play rejects, so the shots are converted. Recapture with store/capture-screenshots.sh (see its header — it expects the app already signed in).
  • Review access: the demo account / bypass code from hadron-server#810 works verbatim.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages