Files
SoulStream/handoff/rounds/ins-r29-plugin-kt.txt
T
KeelrLeek 9f0601027c android: the handoff — the pending rounds, the pipeline, and what is unverified
Everything written this session that has not been through the device: rounds 43
to 48 (each patch adversarially verified, 43's transient-notice expectation
fixed), a partial round 49, the pipeline scripts, the Android Auto design and
the icon comparison mock. Also the two harness changes that were never A/B'd —
adaptive waits, which may only shorten a declared wait and never lengthen it,
and a ship that runs the mock walk and the deploy at the same time. The A/B and
its 255-step baseline are in handoff/tools; main stays at what the phone has
actually walked.
2026-09-14 07:57:41 -05:00

77 lines
2.8 KiB
Plaintext

package com.soulstream.transfernotice
import android.Manifest
import android.app.Activity
import android.content.pm.PackageManager
import android.os.Build
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import app.tauri.annotation.Command
import app.tauri.annotation.InvokeArg
import app.tauri.annotation.TauriPlugin
import app.tauri.plugin.Invoke
import app.tauri.plugin.JSObject
import app.tauri.plugin.Plugin
/** What the notice says — the Rust `TransferNotice`, camelCase. */
@InvokeArg
class NoticeArgs {
/** how many files are still in flight; zero means take the notice down */
var count: Int = 0
var title: String? = null
var line: String? = null
var percent: Int = 0
/** nobody has stated a size yet, so the bar has no end to walk towards */
var indeterminate: Boolean = false
}
/**
* The bridge between the WebView and the service that owns the notice.
* `update` mirrors what Save is fetching (and starts the service the first
* time); `clear` takes the notice down. Its buttons come back as the plugin
* event `action` with `{ action }` — "cancel" or "open".
*/
@TauriPlugin
class TransferNoticePlugin(private val activity: Activity) : Plugin(activity) {
private var askedForNotifications = false
init {
TransferHub.onAction = { action ->
val payload = JSObject()
payload.put("action", action)
trigger("action", payload)
}
/* the channel exists from app start, so a user can silence the
downloads on their own before the first one ever runs */
TransferHub.ensureChannel(activity.applicationContext)
}
@Command
fun update(invoke: Invoke) {
val args = invoke.parseArgs(NoticeArgs::class.java)
ensureNotificationPermission()
TransferHub.update(activity.applicationContext, args)
invoke.resolve()
}
@Command
fun clear(invoke: Invoke) {
TransferHub.clear(activity.applicationContext)
invoke.resolve()
}
/** Android 13+: the notice is a notification and needs the permission;
* asked once, the first time something is fetched. Its own request code
* — 4711 is the media card's, and two requests that share one would
* answer each other. The service runs either way; without the
* permission it simply has nothing to show. */
private fun ensureNotificationPermission() {
if (Build.VERSION.SDK_INT < 33 || askedForNotifications) return
askedForNotifications = true
if (ContextCompat.checkSelfPermission(activity, Manifest.permission.POST_NOTIFICATIONS)
== PackageManager.PERMISSION_GRANTED
) return
ActivityCompat.requestPermissions(activity, arrayOf(Manifest.permission.POST_NOTIFICATIONS), 4712)
}
}