Repository navigation
Android: ship the session service and microphone plugins as a plugin crate with an Android library module #888
Copy link
Copy link
Labels
app-shellenhancementNew feature or requestNew feature or requestmanual-testingMerged; some criteria need a manual check on real hardware or another OSMerged; some criteria need a manual check on real hardware or another OSworkflow-changeNeeds edits under .github/workflows/ (maintainer implements; not dispatched to coders)Needs edits under .github/workflows/ (maintainer implements; not dispatched to coders)
Milestone
Description
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-shellregisters 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 viamicrophone_plugin()incrates/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) andR.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 itsPROPERTY_SPECIAL_USE_FGS_SUBTYPEproperty. - Gradle dependency they need:
androidx.lifecycle:lifecycle-process:2.10.0(app/build.gradle.kts). docs/android-build.md("App-Crates, dieapp-shelleinbetten", 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 whosebuild.rsdeclares an Android library (tauri_plugin::Builder::new(COMMANDS).android_path("android")); the Tauri CLI adds the library totauri.settings.gradle/tauri.build.gradle.ktsof 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.ktandio/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.rswithtauri_plugin::Builder::new(&[]).android_path("android")(no webview commands; the Kotlin plugins are only called from Rust viarun_mobile_plugin)android/Android library module (build.gradle.ktswith its own namespace, e.g.dev.smartssh.androidhost, samecompileSdk/minSdkas the app; dependencylifecycle-process;app.tauriplugin API as Tauri plugin templates do)android/src/main/java/…/SessionService.kt,SessionServicePlugin.kt,MicrophonePlugin.ktmoved there (package = library namespace); behaviour unchanged- library
AndroidManifest.xmlwith the permissions,uses-featureand the<service>entry listed above (fully qualified service class name) - library
res/values/strings.xmlwith 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.rsandvoice_input.rsuse it instead of building ad-hoc plugins withregister_android_plugin("com.smartssh.desktop", …); desktop builds compile it as a no-op.
apps/smart-ssh-community/Cargo.tomllists 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, thelifecycle-processline (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 "addtauri-plugin-android-hostas 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-hostexists withbuild.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 onlyMainActivity.kt; the app manifest has noRECORD_AUDIO,FOREGROUND_SERVICE*,POST_NOTIFICATIONS,WAKE_LOCKorSessionServiceentries any more. - After
cargo tauri android build, the merged manifest of the APK (aapt dump xmltree … AndroidManifest.xmlorapkanalyzer manifest print) still contains all six permission/feature entries and theSessionServicewithforegroundServiceType="specialUse". New scriptscripts/check-android-apk-manifest.sh <apk>checks this and fails otherwise; theandroid-buildjob in.github/workflows/community.ymlruns it right after the 16 KB alignment check (around line 525). -
tauri.settings.gradlegenerated 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 warningspass on desktop (the crate compiles there). -
docs/android-build.mdand 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 inandroid-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/androidfiles are partially generated by the Tauri CLI; do not hand-edittauri.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 inandroid_service::start.
Related Work
- Update the Tauri stack to tauri 2.12 / tao 0.37 / wry 0.57 and drop the Android ndk-context workaround #820 (Tauri stack update, drops the Android ndk-context workaround; blocks this issue)
- Android: app aborts at launch (Keystore store without Android context, file plugin's Android part not bundled) #795, CI: start the Android debug APK in an emulator and fail on a crash at launch #797, Android: SSH sessions keep running in the background (foreground service with notification) #677, Android: voice input (microphone permission, whisper.cpp NDK build, model storage) #680
Metadata
Metadata
Assignees
Labels
app-shellenhancementNew feature or requestNew feature or requestmanual-testingMerged; some criteria need a manual check on real hardware or another OSMerged; some criteria need a manual check on real hardware or another OSworkflow-changeNeeds edits under .github/workflows/ (maintainer implements; not dispatched to coders)Needs edits under .github/workflows/ (maintainer implements; not dispatched to coders)
Type
Fields
Priority
P2 - Normal
Activity
1363 remaining items
smart-ssh-coordinator commentedon Nov 1, 2026
Merge waits for the review of the CI changes: PR #990 changes CI files under .github/; they are reviewed before the merge (head 7e51cb6).
smart-ssh-maintainer commentedon Nov 1, 2026
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 -->
smart-ssh-coordinator commentedon Nov 1, 2026
Merge waits for the review of the CI changes: PR #990 changes CI files under .github/; they are reviewed before the merge (head 7e51cb6).
smart-ssh-maintainer commentedon Nov 1, 2026
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 -->
smart-ssh-coordinator commentedon Nov 1, 2026
Merge waits for the review of the CI changes: PR #990 changes CI files under .github/; they are reviewed before the merge (head 7e51cb6).
smart-ssh-maintainer commentedon Nov 1, 2026
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 -->
smart-ssh-coordinator commentedon Nov 1, 2026
Merge waits for the review of the CI changes: PR #990 changes CI files under .github/; they are reviewed before the merge (head 7e51cb6).
smart-ssh-maintainer commentedon Nov 1, 2026
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 -->
smart-ssh-coordinator commentedon Nov 1, 2026
Merge waits for the review of the CI changes: PR #990 changes CI files under .github/; they are reviewed before the merge (head 7e51cb6).
smart-ssh-maintainer commentedon Nov 1, 2026
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 -->
smart-ssh-coordinator commentedon Nov 1, 2026
Merge waits for the review of the CI changes: PR #990 changes CI files under .github/; they are reviewed before the merge (head 7e51cb6).
smart-ssh-maintainer commentedon Nov 1, 2026
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 -->
smart-ssh-coordinator commentedon Nov 1, 2026
Merge waits for the review of the CI changes: PR #990 changes CI files under .github/; they are reviewed before the merge (head 7e51cb6).
smart-ssh-maintainer commentedon Nov 1, 2026
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 -->
smart-ssh-coordinator commentedon Nov 1, 2026
Merge not possible, back to coding: PR #990 has conflicts with develop – please resolve them.