Skip to content

Android: ship the session service and microphone plugins as a plugin crate with an Android library module #888

Description

@smart-ssh-planner

Goal

The Android code that belongs to the core's Rust code (the foreground session service, its Tauri plugin and the microphone permission plugin, with their manifest entries, strings and Gradle dependencies) ships with the core as a Tauri plugin crate that has its own Android library module. Any app crate that embeds app-shell gets it through Gradle and manifest merge instead of copying Kotlin files into its own gen/android tree.

Context

  • app-shell registers Kotlin plugins that it does not ship:
    • crates/app-shell/src/android_service.rs:98-130 – api.register_android_plugin("com.smartssh.desktop", "SessionServicePlugin")
    • crates/app-shell/src/voice_input.rs:108-117 – api.register_android_plugin("com.smartssh.desktop", "MicrophonePlugin"); wired via microphone_plugin() in crates/app-shell/src/lib.rs:1206-1211.
  • The Kotlin classes live in the app project apps/smart-ssh-community/gen/android/app/src/main/java/com/smartssh/desktop/: SessionService.kt (92 lines), SessionServicePlugin.kt (122), MicrophonePlugin.kt (40). They use app resources: R.string.session_channel_name, R.plurals.session_notification_title, R.string.session_notification_disconnect_all, R.string.session_notification_denied_hint (res/values/strings.xml) and R.mipmap.ic_launcher (SessionService.kt:83).
  • Manifest entries they need (gen/android/app/src/main/AndroidManifest.xml): RECORD_AUDIO, uses-feature android.hardware.microphone required=false, FOREGROUND_SERVICE, FOREGROUND_SERVICE_SPECIAL_USE, POST_NOTIFICATIONS, WAKE_LOCK, and the <service android:name=".SessionService" … foregroundServiceType="specialUse"> with its PROPERTY_SPECIAL_USE_FGS_SUBTYPE property.
  • Gradle dependency they need: androidx.lifecycle:lifecycle-process:2.10.0 (app/build.gradle.kts).
  • docs/android-build.md ("App-Crates, die app-shell einbetten", Issue Android: app aborts at launch (Keystore store without Android context, file plugin's Android part not bundled) #795) tells every embedding app to copy these pieces. Tauri's supported way for this is a plugin crate whose build.rs declares an Android library (tauri_plugin::Builder::new(COMMANDS).android_path("android")); the Tauri CLI adds the library to tauri.settings.gradle / tauri.build.gradle.kts of the app, and Android merges its manifest and resources.
  • Constraint from Android: app aborts at launch (Keystore store without Android context, file plugin's Android part not bundled) #795: the CLI only picks up Android projects of direct dependencies of the app crate.
  • Out of scope: MainActivity.kt and io/crates/keyring/Keyring.kt (app identity, NDK context init) stay in the app tree.

Requirements

  • New crate crates/tauri-plugin-android-host (Apache-2.0, workspace member, publish = false):
    • build.rs with tauri_plugin::Builder::new(&[]).android_path("android") (no webview commands; the Kotlin plugins are only called from Rust via run_mobile_plugin)
    • android/ Android library module (build.gradle.kts with its own namespace, e.g. dev.smartssh.androidhost, same compileSdk/minSdk as the app; dependency lifecycle-process; app.tauri plugin API as Tauri plugin templates do)
    • android/src/main/java/…/SessionService.kt, SessionServicePlugin.kt, MicrophonePlugin.kt moved there (package = library namespace); behaviour unchanged
    • library AndroidManifest.xml with the permissions, uses-feature and the <service> entry listed above (fully qualified service class name)
    • library res/values/strings.xml with the four session strings
    • the notification small icon no longer references the app's R.mipmap.ic_launcher: use the host app's icon at runtime (applicationInfo.icon), so each embedding app keeps its own icon
    • Rust side: the crate exposes the Tauri plugin(s) that register the two Kotlin plugins; android_service.rs and voice_input.rs use it instead of building ad-hoc plugins with register_android_plugin("com.smartssh.desktop", …); desktop builds compile it as a no-op.
  • apps/smart-ssh-community/Cargo.toml lists the crate as a direct dependency ([target.'cfg(target_os = "android")'.dependencies] is enough if the crate is Android-only in effect, otherwise a normal dependency).
  • The community app tree loses the moved files: the three Kotlin classes, the four strings, the six permission/feature lines and the <service> entry, the lifecycle-process line (if nothing else in the app needs it).
  • docs/android-build.md: replace the copy instructions in the Android: app aborts at launch (Keystore store without Android context, file plugin's Android part not bundled) #795 section with "add tauri-plugin-android-host as a direct dependency"; keep point 1 (NDK context) as is.
  • New ADR: Android code that belongs to core Rust code ships as a Tauri plugin crate with an Android library module (decided by maintainer); embedding apps keep only app identity in gen/android (namespace/application id, MainActivity, label, icons).

Acceptance Criteria

  • crates/tauri-plugin-android-host exists with build.rs, android/ library, the three Kotlin classes, manifest and strings as above.
  • apps/smart-ssh-community/gen/android/app/src/main/java/com/smartssh/desktop/ contains only MainActivity.kt; the app manifest has no RECORD_AUDIO, FOREGROUND_SERVICE*, POST_NOTIFICATIONS, WAKE_LOCK or SessionService entries any more.
  • After cargo tauri android build, the merged manifest of the APK (aapt dump xmltree … AndroidManifest.xml or apkanalyzer manifest print) still contains all six permission/feature entries and the SessionService with foregroundServiceType="specialUse". New script scripts/check-android-apk-manifest.sh <apk> checks this and fails otherwise; the android-build job in .github/workflows/community.yml runs it right after the 16 KB alignment check (around line 525).
  • tauri.settings.gradle generated by the CLI lists the new library (checked in CI after the build).
  • The existing Android CI job (debug APK build, 16 KB alignment check, emulator launch smoke test from CI: start the Android debug APK in an emulator and fail on a crash at launch #797) passes.
  • cargo test --workspace, cargo clippy --workspace --all-targets -- -D warnings pass on desktop (the crate compiles there).
  • docs/android-build.md and the new ADR updated as described; Spec 0114 section 4 mentions where the background service code lives.
  • Manual check on an emulator or device (reviewer steps): open a session, background the app → the notification with "Disconnect all" appears with the app's icon; voice input asks for the microphone permission.

Technical Notes

  • Files: new crates/tauri-plugin-android-host/**, Cargo.toml (workspace, if members are not globbed – they are: crates/*), crates/app-shell/Cargo.toml, crates/app-shell/src/android_service.rs, crates/app-shell/src/voice_input.rs, crates/app-shell/src/lib.rs (microphone_plugin), apps/smart-ssh-community/Cargo.toml, apps/smart-ssh-community/gen/android/**, scripts/check-android-apk-manifest.sh (new), .github/workflows/community.yml (one new step in android-build), docs/android-build.md, docs/adr/<next>-*.md, docs/specs/0114-android.md.
  • CI change: only the Android job gets an extra verification step after the APK build; explain it in the PR. Label workflow-change.
  • The microphone plugin must stay registered after unlock in password mode (it is part of register_unlocked_plugins_on, lib.rs:1079-1118); keep that.
  • Not security-critical for filter/risk/redaction; the foreground service only reflects the session count.
  • gen/android files are partially generated by the Tauri CLI; do not hand-edit tauri.settings.gradle/tauri.build.gradle.kts (they are generated and ignored).

Spec impact: update 0114

Open questions

  • One Tauri plugin that registers both Kotlin plugins, or two plugins from the same crate (session_service(), microphone()): recommended two plugin constructors, so the microphone plugin keeps its place in the after-unlock list and the session service keeps its own lifecycle in android_service::start.

Related Work

Activity

  1. added this to the 0.8.0 milestone on Oct 26, 2026
  2. added
    enhancementNew feature or request
    workflow-changeNeeds edits under .github/workflows/ (maintainer implements; not dispatched to coders)
    on Oct 26, 2026
  3. 1363 remaining items

  4. smart-ssh-coordinator commented on Nov 1, 2026

    @smart-ssh-coordinator
    Contributor

    Merge waits for the review of the CI changes: PR #990 changes CI files under .github/; they are reviewed before the merge (head 7e51cb6).

  5. smart-ssh-maintainer commented on Nov 1, 2026

    @smart-ssh-maintainer
    Contributor

    CI changes reviewed: PR #990 7e51cb6: Adds one verification step to the Android build job: it checks that tauri.settings.gradle lists the new plugin library and runs the new scripts/check-android-apk-manifest.sh, which reads the built APK's merged manifest with aapt and fails if the expected permissions or the SessionService entry are missing. No existing step is changed, and the step has no network access, no downloads and no secrets.
    <!-- ci-approved: 7e51cb6 -->

  6. smart-ssh-coordinator commented on Nov 1, 2026

    @smart-ssh-coordinator
    Contributor

    Merge waits for the review of the CI changes: PR #990 changes CI files under .github/; they are reviewed before the merge (head 7e51cb6).

  7. smart-ssh-maintainer commented on Nov 1, 2026

    @smart-ssh-maintainer
    Contributor

    CI changes reviewed: PR #990 7e51cb6: Adds one verification step to the android-build job, after the 16 KB alignment check. It greps tauri.settings.gradle for the new plugin library and runs the new scripts/check-android-apk-manifest.sh on each built APK. The script only reads the APK with a local aapt and checks for the expected permissions and the SessionService. It has no network access, uploads or eval. No existing step is removed or weakened, and the change matches the issue.
    <!-- ci-approved: 7e51cb6 -->

  8. smart-ssh-coordinator commented on Nov 1, 2026

    @smart-ssh-coordinator
    Contributor

    Merge waits for the review of the CI changes: PR #990 changes CI files under .github/; they are reviewed before the merge (head 7e51cb6).

  9. smart-ssh-maintainer commented on Nov 1, 2026

    @smart-ssh-maintainer
    Contributor

    CI changes reviewed: PR #990 7e51cb6: Adds one verification step to the Android build job, after the 16 KB alignment check. It checks that tauri.settings.gradle lists the new plugin library, then runs the new scripts/check-android-apk-manifest.sh on each built APK. The script only reads the APK locally with aapt and greps the merged manifest. It makes no network calls, evals nothing and prints no environment. No existing step is removed or weakened.
    <!-- ci-approved: 7e51cb6 -->

  10. smart-ssh-coordinator commented on Nov 1, 2026

    @smart-ssh-coordinator
    Contributor

    Merge waits for the review of the CI changes: PR #990 changes CI files under .github/; they are reviewed before the merge (head 7e51cb6).

  11. smart-ssh-maintainer commented on Nov 1, 2026

    @smart-ssh-maintainer
    Contributor

    CI changes reviewed: PR #990 7e51cb6: Adds one Android CI step after the APK build: it checks that the Gradle settings list the android-host plugin and runs scripts/check-android-apk-manifest.sh, which inspects the merged manifest with aapt for the expected permissions and service. It is a purely additional local check with no network access, no event input and no existing gate changed.
    <!-- ci-approved: 7e51cb6 -->

  12. smart-ssh-coordinator commented on Nov 1, 2026

    @smart-ssh-coordinator
    Contributor

    Merge waits for the review of the CI changes: PR #990 changes CI files under .github/; they are reviewed before the merge (head 7e51cb6).

  13. smart-ssh-maintainer commented on Nov 1, 2026

    @smart-ssh-maintainer
    Contributor

    CI changes reviewed: PR #990 7e51cb6: Adds one verification step to the android-build job after the 16 KB alignment check, as the issue asks. It greps tauri.settings.gradle and runs the new scripts/check-android-apk-manifest.sh, which only reads the built APK with aapt. No existing step is changed, and the new script has no downloads, eval or outbound access.
    <!-- ci-approved: 7e51cb6 -->

  14. smart-ssh-coordinator commented on Nov 1, 2026

    @smart-ssh-coordinator
    Contributor

    Merge waits for the review of the CI changes: PR #990 changes CI files under .github/; they are reviewed before the merge (head 7e51cb6).

  15. smart-ssh-maintainer commented on Nov 1, 2026

    @smart-ssh-maintainer
    Contributor

    CI changes reviewed: PR #990 7e51cb6: Adds one verification step to the android-build job after the 16 KB alignment check. It checks that tauri.settings.gradle lists the new plugin library and runs the new local script scripts/check-android-apk-manifest.sh (aapt dump of the merged manifest) on each built APK. No existing step is changed or weakened. There is no network access, download or upload. The script only reads the APK and uses no event data.
    <!-- ci-approved: 7e51cb6 -->

  16. smart-ssh-coordinator commented on Nov 1, 2026

    @smart-ssh-coordinator
    Contributor

    Merge waits for the review of the CI changes: PR #990 changes CI files under .github/; they are reviewed before the merge (head 7e51cb6).

  17. smart-ssh-maintainer commented on Nov 1, 2026

    @smart-ssh-maintainer
    Contributor

    CI changes reviewed: PR #990 7e51cb6: Adds one verification step to the Android build job, after the APK build and before the artifact upload. It greps the generated Gradle settings for the new plugin library and runs the new scripts/check-android-apk-manifest.sh on each built APK. The script only reads the APK with aapt and checks the merged manifest entries. It does no network access, downloads or uploads, and no existing step is removed or weakened.
    <!-- ci-approved: 7e51cb6 -->

  18. smart-ssh-coordinator commented on Nov 1, 2026

    @smart-ssh-coordinator
    Contributor

    Merge not possible, back to coding: PR #990 has conflicts with develop – please resolve them.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    app-shellenhancementNew feature or requestmanual-testingMerged; some criteria need a manual check on real hardware or another OSworkflow-changeNeeds edits under .github/workflows/ (maintainer implements; not dispatched to coders)

    Type

    Fields

    Priority

    P2 - Normal

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions