Javis OS
Javis OS는 기업가를 돕는 에이전시 AI로, 업무를 지원하고 함께 성장합니다
District AI for Android는 Apache-2.0 오픈소스이며, 서비스는 비공개이고 로그인에 District AI 계정 필요
District AI for Android, the native Android app, is open source under Apache-2.0. The service it talks to is not. Signing in needs a District AI account.
비즈니스Kotlin · PythonGitHub @distronode-corporation온라인운영자 미확인
써 보셨어요? 한 번만 눌러 주세요. 로그인은 필요 없어요.
District AI for Android: the native client (Kotlin, Jetpack Compose)
저장소 README에서 가져옴
README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
The native Android client for District AI, by
Distronode. It is the app published on
Google Play as
com.distronode.districtai: calls and transcripts, the shared inbox, contacts and their
intelligence dossiers, multi-party rooms, a self-managed softphone that places and answers
calls through Android's Telecom framework, push notifications, and workspace settings.
Kotlin, Jetpack Compose and Material 3, on a small set of Gradle modules. Apache-2.0.README 전문 보기 ↗The native Android client for District AI, by
Distronode. It is the app published on
Google Play as
com.distronode.districtai: calls and transcripts, the shared inbox, contacts and their
intelligence dossiers, multi-party rooms, a self-managed softphone that places and answers
calls through Android's Telecom framework, push notifications, and workspace settings.
Kotlin, Jetpack Compose and Material 3, on a small set of Gradle modules. Apache-2.0. It runs on Android 8.0 (API level 26) and later.
Languages: English and Canadian French (fr-CA), since 3.0. The app follows the device's language or, on Android 13 and later, its own language setting; any French reads in Canadian French, and every other language in English.
The app is a client of the District AI API. Everything it shows comes from that service, and signing in needs a District AI account. There is no offline mode and no local server.
Without an account with us. Today this app needs a District AI account to sign in. We want the District AI apps to work without an account with us too. We have not worked out what that looks like or whether it can work, and the answer depends on what people would use them with, so we are asking before we build anything: tell us what you would connect them to.
Links: project page · GitLab mirror (read-only mirror; issues and pull requests live on GitHub) · CHANGELOG
android-37.0 (compileSdk 37) and build tools 37.0.0.
targetSdk is 36 and minSdk is 26. Point the build at the SDK with ANDROID_HOME or a
local.properties file containing sdk.dir=... (it is gitignored)../gradlew assembleDebug # debug APK: app/build/outputs/apk/debug/
./gradlew testDebugUnitTest # every module's unit tests (Robolectric included)
./gradlew detekt lintDebug # static analysis and Android Lint
./gradlew :koverLogUnit :koverVerifyUnit # aggregate coverage and its floors
scripts/verify-release-minification.sh # R8 release build, serializers retained
That is also what CI runs, in one job: see .github/workflows/ci.yml and CONTRIBUTING.md.
The debug build installs as com.distronode.districtai.debug, so it can sit beside a release
install on the same device.
Every configuration is dependency-locked (<module>/gradle.lockfile). Changing a version in
gradle/libs.versions.toml does nothing until the lockfiles are regenerated; the command is
in the "Regenerating the lockfiles" note in build.gradle.kts.
app/ The application: screens (ui/), navigation, Telecom and calls,
push, app links, sign-in, and the wiring in AppContainer.
core/core-model/ API response and request types, and the contract fixture tests.
core/core-network/ The HTTP client over OkHttp, the API interfaces, and the mapping of
the API's error envelopes into one result type.
core/core-auth/ Token storage (Android Keystore) and the refresh coordinator.
core/core-data/ Repositories over core-network, and the active-workspace state.
core/core-designsystem/ Colour, type, shape and the primitive composables. Depends on no
other module.
core/core-media/ The LiveKit wrapper behind a CallEngine interface.
build-logic/ Convention plugins shared by every module.
contracts/ API contract fixtures generated by the server (see below).
parity/ A snapshot of the iOS client's endpoint list (see below).
config/detekt/ detekt configuration.
scripts/ Verification scripts; see scripts/README.md.
.maestro/ A Maestro smoke flow, run locally with scripts/smoke-emulator.sh.
docs/ARCHITECTURE.md describes how the modules fit together.
This repository has no generated API client and the API has no published schema. What stops a
server-side rename from shipping silently is contracts/: JSON bodies produced by the
server's own test suite from its real route handlers, committed here, and decoded in
:core:core-model (and a few other modules) with a strict decoder that rejects unknown keys.
The gate has two halves, one in each repository:
The fixtures are generated in the server repository, which is private. Refreshing them is a
maintainer task: regenerate them there, copy the changed *.json files into contracts/
byte for byte, and update the types (and ContractManifest's expected count, if a fixture was
added) in the same commit. Never edit a fixture by hand, and never relax the strict decoder to
make a failure go away; the failure is the signal.
parity/EndpointID.swift does the same job against the iOS client: EndpointParityTest diffs
it against this client's API interfaces. parity/README.md says where the
snapshot came from and how to refresh it, or how to test against a live iOS checkout instead.
Releases are built, signed and uploaded to Google Play by
.github/workflows/release.yml, from a v* tag (tags are
protected: they cannot be moved or deleted) or, for a test build, from a dispatch on main.
Every build goes to the internal testing track. Submission for store review is a separate
step that runs only with the maintainers' explicit approval, either in the same run or later
through .github/workflows/submit.yml; it releases the
already uploaded build to production with per-language release notes: the version's
CHANGELOG.md section for en-US, and release-notes/<language>/<version>.txt (for example
release-notes/fr-CA/2.1.txt, plain text exactly as the store shows it) for every other
language. Every language under release-notes/ needs a file for each version, and every
language the store listing has needs notes: a missing one stops the release, and English is
never sent in its place. Each language is held to Google Play's 500 characters. Once that submission has succeeded, the same workflow publishes the tag's
GitHub Release, marked
Latest, with that section and links to the runs that built and submitted it. Releases here are
immutable, so it is published once, complete, and a re-run leaves an existing release alone.
No signing key or store credential is stored in this repository or in GitHub. The jobs run in
a release environment that only main and v* tags can use, and borrow the upload key, the
Google Play publishing credential and the Sentry upload token from Distronode's Google Cloud
for the length of one run, through workload identity federation pinned to this repository,
that environment and those refs. On a tag the workflow refuses to build unless the tag equals
versionName and names a commit on main, and it checks the release notes before building.
Versions up to and including 1.0 were built and signed on a maintainer's machine before this workflow existed.
The scripts the workflows run are in scripts/ (release-version.sh, release-build.sh,
release-notes.sh, play-publish.sh, gsm-secret.sh, github-release.sh), and
scripts/release-scripts.test.sh tests them in CI against stubs of the Google APIs and the
gh CLI. To build a release yourself, for example
for a fork signed with your own key:
versionCode is BUILD_NUMBER_OFFSET (default 4101) plus git rev-list --count HEAD, so
build releases from a full clone, not a shallow one. app/build.gradle.kts explains why the
offset exists.
Signing reads ANDROID_UPLOAD_KEYSTORE_PATH, ANDROID_UPLOAD_KEYSTORE_PASSWORD and
ANDROID_UPLOAD_KEY_ALIAS: all three, or none for an unsigned build. A partial set fails the
build.
Crash reporting and mapping upload need the Sentry values supplied, because every default
is empty. A local release build that should report crashes and upload its ProGuard mapping
must be given all of these, as Gradle properties (for example in ~/.gradle/gradle.properties)
or as environment variables:
| Gradle property | Environment variable | What it is |
|---|---|---|
districtSentryDsn | DISTRICT_SENTRY_DSN | The project's DSN, baked into the app. Empty means crash reporting is off. |
districtSentryUrl | DISTRICT_SENTRY_URL | The Sentry host for the organisation's region, e.g. https://de.sentry.io for an EU organisation. |
districtSentryOrg | DISTRICT_SENTRY_ORG | The organisation slug the mapping is uploaded to. |
districtSentryProject | DISTRICT_SENTRY_PROJECT | The project slug the mapping is uploaded to. |
| (none) | SENTRY_AUTH_TOKEN | The upload token. With it the mapping and a bundle of the app's and core modules' source (source context) are uploaded; without it the mapping is packaged but not uploaded, and no source is bundled. |
release.yml supplies all five for Distronode's own releases.
A property wins over its environment variable. A build with no DSN says so in one line
("Sentry disabled: ...") when Gradle configures the project. A run that reuses the
configuration cache skips configuration and so does not repeat the line; build a release with
--no-configuration-cache to be sure of seeing it.
./gradlew :app:bundleRelease # app/build/outputs/bundle/release/app-release.aab
Keep app/build/outputs/mapping/release/mapping.txt from the same build. It is not committed,
and without it release stack traces cannot be read.
There are no release credentials here, and none in GitHub: no signing key, store credential or crash reporting token (the release workflow borrows them per run; see Releases). Some values look like credentials and are not. They are listed so nobody has to guess:
app/src/main/kotlin/.../push/DistrictFirebase.kt:
the project id, sender id, the two Android app ids and the Android API key. This is the
configuration every Firebase app ships inside its APK (normally via google-services.json);
it identifies the project to Google's client SDKs and grants nothing on its own. It is
included deliberately, and DistrictFirebaseTest pins it. A fork that sends push needs its
own Firebase project and must replace it.https://www.distronode.com) in app/build.gradle.kts.contracts/, generated from the server's test suite with fictional
data. They include LiveKit end-to-end-encryption keys that exist only in that test suite and
protect nothing; see SECURITY.md.Nothing here is a secret. If you find something that is, report it as described in SECURITY.md rather than in an issue.
Questions about a District AI account, number or bill go to District AI support.
Apache License 2.0. See LICENSE and NOTICE.
District AI, Distronode and the District AI and Distronode logos and app icons are trademarks of Distronode Corporation. They are not licensed under the Apache License 2.0: a build you distribute must use its own name, icon and applicationId. NOTICE has the details and the third-party notices.