android: the keep rules, and the release build that proves them
Every APK this port has produced is `assembleArm64Debug`, and the generated project turns R8 on for `release` and only for `release` — while tauri finds a plugin class by string, files every `@Command` under `commands[method.name]`, and lets Jackson fill every `@InvokeArg` field by field. Minification breaks exactly that, and it breaks it without a word: the app compiles, installs, launches, shows Home, and then a tap on Library folder answers "No command pickFolder found for plugin ..." in logcat and nothing on screen. So each plugin module now carries its own `consumer-rules.pro` naming its own reflected surface — the shape tauri-android and tauri-plugin-dialog already use in this build, and one that travels with the plugin into a regenerated `gen/android`, which a rule written into that gitignored tree would not. What belongs to no single plugin is in `src-tauri/android/proguard-soulstream.pro`, copied into the generated project by the build script because the app's own Gradle file is regenerated: the runtime annotation attributes, a `-dontwarn` pair for the two `java.beans` classes jackson-databind references and Android does not have (R8 turns a missing class into a build error, so without them there may be no APK at all), and a keep for the four `@Command` methods declared on `app.tauri.plugin.Plugin` itself. Tauri's own rule matches classes annotated `@TauriPlugin` and the members they declare; the base class carries no such annotation, so nothing names `registerListener` — which is what the lock-screen card's buttons and the download notice's Cancel and Open ride on. `build.sh` grows `--release`: the release profile, `--features tauri/devtools` so the walk can still attach at all, `assembleArm64Release`, and a zipalign and sign with the same debug key the phone already trusts, so the APK installs over the app rather than forcing an uninstall that would take Kyle's settings with it. With no argument the script is what it has always been, cargo line for cargo line, so deploy.sh and ship.sh keep the path they run every twenty minutes. And it has been built. Not on the phone and not as arm64 — as **x86_64**, on an emulator, because that is what this machine can run — but the flavour is the only difference: the same Gradle, the same R8, the same rules, the same zipalign and apksigner. `minifyX86_64ReleaseWithR8` succeeded and wrote **no** `missing_rules.txt` at all, which is the `-dontwarn` pair doing its job; `lintVitalX86_64Release` passed, so the `-x lintVital` advice is for a failure that has not happened. The signed APK went over the debug app with `adb install -r` and left its data alone, exactly as the same-key argument above says it must. Then the minified app was walked: Home, the record, playback, the queue, the sheets, the lyrics, the drags, the export — **131 of 131, no exceptions, no console errors, every step as expected**, and nothing in logcat about a missing command or class. That is the failure this round exists to prevent, looked for and not found. What it does not cover is the hands-only list in ANDROID.md — the pickers, the notification buttons, the share sheet — which no walk reaches, and the four `@Command` methods on `Plugin` itself remain a precaution rather than a diagnosed bug. The document says so, command by command.
This commit is contained in:
1 parent
9c3273e6af
commit
07dad65ca5
13 files changed
+452
-11
No files matched your search
@@ -9,6 +9,10 @@ android {
|
||||
|
||||
defaultConfig {
|
||||
minSdk = 26
|
||||
// the plugin's own R8 keep rules, handed to whatever app links this
|
||||
// module — inert unless that app minifies, which only the release
|
||||
// build type does
|
||||
consumerProguardFiles("consumer-rules.pro")
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
# Consumer R8/ProGuard rules for the media-session plugin. Inert unless the
|
||||
# consuming app minifies, and only the `release` build type does, so the
|
||||
# debug APK the deploy pipeline builds is untouched by this file.
|
||||
#
|
||||
# MediaSessionPlugin is found by the string
|
||||
# "com/soulstream/mediasession/MediaSessionPlugin" (register_android_plugin →
|
||||
# FindClass), constructed through "(Landroid/app/Activity;)V", and its two
|
||||
# @Command methods — update and clear — are dispatched by NAME through
|
||||
# PluginHandle's commands map. NowPlayingArgs is filled by Jackson field by
|
||||
# field. Rename any of it and the app still builds, installs and plays; the
|
||||
# lock-screen card simply never appears.
|
||||
#
|
||||
# The other half of this plugin is in the app-wide file: the card's buttons
|
||||
# come back as the plugin event "action", which the WebView subscribes to
|
||||
# through Plugin.registerListener — declared on tauri's base class, which no
|
||||
# rule of tauri's own names. See src-tauri/android/proguard-soulstream.pro.
|
||||
#
|
||||
# MediaService and androidx's MediaButtonReceiver are declared in this
|
||||
# module's AndroidManifest.xml, so AGP's own merged-manifest keep rules hold
|
||||
# them by name. Not repeated here: one place decides each of them.
|
||||
-keep class com.soulstream.mediasession.MediaSessionPlugin { *; }
|
||||
-keep class com.soulstream.mediasession.NowPlayingArgs { *; }
|
||||
@@ -9,6 +9,10 @@ android {
|
||||
|
||||
defaultConfig {
|
||||
minSdk = 26
|
||||
// the plugin's own R8 keep rules, handed to whatever app links this
|
||||
// module — inert unless that app minifies, which only the release
|
||||
// build type does
|
||||
consumerProguardFiles("consumer-rules.pro")
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
# Consumer R8/ProGuard rules for the share plugin. Inert unless the consuming
|
||||
# app minifies, and only the `release` build type does, so the debug APK the
|
||||
# deploy pipeline builds is untouched by this file.
|
||||
#
|
||||
# SharePlugin is found by the string "com/soulstream/share/SharePlugin"
|
||||
# (register_android_plugin → FindClass), constructed through
|
||||
# "(Landroid/app/Activity;)V", and its one @Command method — pendingShare —
|
||||
# is dispatched by NAME through PluginHandle's commands map. It takes no
|
||||
# arguments, so there is no @InvokeArg class here. Rename it and text shared
|
||||
# to SoulStream from another app arrives nowhere: the activity still runs and
|
||||
# still brings the app to the front, and the WebView is never handed what it
|
||||
# carried.
|
||||
#
|
||||
# ShareActivity is declared in this module's AndroidManifest.xml, so AGP's
|
||||
# own merged-manifest keep rules hold it by name. Not repeated here: one
|
||||
# place decides it.
|
||||
-keep class com.soulstream.share.SharePlugin { *; }
|
||||
@@ -9,6 +9,10 @@ android {
|
||||
|
||||
defaultConfig {
|
||||
minSdk = 26
|
||||
// the plugin's own R8 keep rules, handed to whatever app links this
|
||||
// module — inert unless that app minifies, which only the release
|
||||
// build type does
|
||||
consumerProguardFiles("consumer-rules.pro")
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
# Consumer R8/ProGuard rules for the storage plugin. Gradle hands a consumer
|
||||
# file to whatever app links this module, and it is INERT unless that app
|
||||
# minifies: src-tauri/gen/android/app/build.gradle.kts sets
|
||||
# isMinifyEnabled = true for the `release` build type and false for `debug`,
|
||||
# and the debug type names no proguard files at all. The deploy pipeline
|
||||
# builds debug, so nothing in this file reaches it.
|
||||
#
|
||||
# Nothing below is called from Kotlin, which is the whole problem. Tauri
|
||||
# reaches this plugin by STRING, at three layers:
|
||||
#
|
||||
# 1. register_android_plugin("com.soulstream.storage", "StoragePlugin") is
|
||||
# turned into "com/soulstream/storage/StoragePlugin" and handed to
|
||||
# FindClass; the instance is built through the JNI signature
|
||||
# "(Landroid/app/Activity;)V" (tauri/src/plugin/mobile.rs).
|
||||
# 2. PluginHandle.indexMethods() files every @Command under
|
||||
# commands[method.name] and every @ActivityCallback under
|
||||
# startActivityCallbackMethods[method.name]; invoke() looks up the name
|
||||
# Rust asked for and throws "No command pickFolder found for plugin ..."
|
||||
# when it is not there.
|
||||
# 3. Jackson fills the @InvokeArg classes FIELD by field — PluginManager
|
||||
# sets FIELD visibility to ANY — so field names weigh the same as
|
||||
# method names.
|
||||
#
|
||||
# Six commands (pickFolder, allFilesAccess, requestAllFilesAccess, pickImage,
|
||||
# pickFile, createDocument) and four activity callbacks (folderPicked,
|
||||
# imagePicked, filePicked, documentCreated). createDocument is the easy one
|
||||
# to miss by hand: it has no JS-facing command at all, it is called from Rust
|
||||
# by playlist export.
|
||||
#
|
||||
# Tauri's own consumer rules already match all of this by annotation
|
||||
# (-keep @TauriPlugin public class * { @Command public <methods>; ... }).
|
||||
# These name it outright anyway: theirs are generic, ours are specific, the
|
||||
# failure is silent and user-facing, and nobody has built a release APK of
|
||||
# this app yet to find out which of us was needed. Narrow them once a release
|
||||
# build has actually been smoke-tested command by command.
|
||||
-keep class com.soulstream.storage.StoragePlugin { *; }
|
||||
-keep class com.soulstream.storage.CreateDocumentArgs { *; }
|
||||
-keep class com.soulstream.storage.PickFileArgs { *; }
|
||||
@@ -9,6 +9,10 @@ android {
|
||||
|
||||
defaultConfig {
|
||||
minSdk = 26
|
||||
// the plugin's own R8 keep rules, handed to whatever app links this
|
||||
// module — inert unless that app minifies, which only the release
|
||||
// build type does
|
||||
consumerProguardFiles("consumer-rules.pro")
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
# Consumer R8/ProGuard rules for the transfer-notice plugin. Inert unless the
|
||||
# consuming app minifies, and only the `release` build type does, so the
|
||||
# debug APK the deploy pipeline builds is untouched by this file.
|
||||
#
|
||||
# TransferNoticePlugin is found by the string
|
||||
# "com/soulstream/transfernotice/TransferNoticePlugin"
|
||||
# (register_android_plugin → FindClass), constructed through
|
||||
# "(Landroid/app/Activity;)V", and its two @Command methods — update and
|
||||
# clear — are dispatched by NAME through PluginHandle's commands map.
|
||||
# NoticeArgs is filled by Jackson field by field. Rename any of it and a Save
|
||||
# runs to completion with no notice in the shade at all.
|
||||
#
|
||||
# The notice's own Cancel and Open come back as the plugin event "action",
|
||||
# which the WebView subscribes to through Plugin.registerListener — declared
|
||||
# on tauri's base class, which no rule of tauri's own names. See
|
||||
# src-tauri/android/proguard-soulstream.pro.
|
||||
#
|
||||
# TransferService is declared in this module's AndroidManifest.xml, so AGP's
|
||||
# own merged-manifest keep rules hold it by name. Not repeated here: one
|
||||
# place decides it.
|
||||
-keep class com.soulstream.transfernotice.TransferNoticePlugin { *; }
|
||||
-keep class com.soulstream.transfernotice.NoticeArgs { *; }
|
||||
Reference in new issue
Block a user