Commit Graph
385 Commits
Author SHA1 Message Date
zarzet bb930827fa refactor(android): move SAF scan checkpointing out of MainActivitySafScan.kt
The resumable-scan checkpoint helpers (state file path, load,
reconcile against written rows, row count and output repair) are a
self-contained unit, so they move verbatim into SafScanCheckpoint.kt.
They were file-private and become internal because scanSafTree stays
in MainActivitySafScan.kt. The checkpoint file name, its line format
and the reconciled NDJSON keys are unchanged.
2026-10-19 05:16:33 +07:00
zarzet 461e449eb2 refactor(android): move SAF file inspection out of MainActivitySafScan.kt
MainActivitySafScan.kt mixes the library scan with unrelated SAF
helpers. resolveSafFile and inspectSafFiles (with its private request
class) serve history file checks, not the scan, so they move verbatim
into SafFileInspection.kt. Signatures and visibility are unchanged.
2026-10-19 05:16:33 +07:00
zarzet 202923fbd9 test(android): pin the extension and MIME tables
ExtensionMimeTablesTest fixes the outputs of extFromFileName,
extFromMimeType, SafDownloadHandler.normalizeExt and mimeTypeForExt, and
NativeFinalizationPolicy.normalizeExtension for every table entry plus
unknown, blank, uppercase and dotted inputs. The expected values come
from reading the code; the tables are not inverse maps, so each keeps
its own entries.

The two normalizers trim, lowercase with Locale.ROOT and add one leading
dot, with "" for blank input; checking the dot before or after
lowercasing cannot differ. normalizeExt now delegates to
normalizeExtension, which becomes internal. The test passed against
both original bodies before the delegation.
2026-10-19 04:58:25 +07:00
zarzet a19a528735 refactor(android): put the SAF IO helpers on Context receivers
The MainActivitySafIo helpers declared a MainActivity receiver although
they only use Context members and the core backend. They now take a
Context receiver, and the ones that reach the backend take it as an
explicit first parameter, so a service can call them with its own
backend. buildLibraryCoverCacheKey used neither and is now a plain
function.

Bodies are unchanged apart from passing the backend through to the
helpers they call. Activity callers pass their own coreBackend, so the
same instances reach the same code. The finalizer keeps using
SafDownloadHandler.copyContentUriToTemp; switching it would add the
MediaStore fallback and MIME-based extension, which is a behavior change.
2026-10-19 04:58:25 +07:00
zarzet 071d72b255 refactor(android): share the foreground wake lock and start helper between the two services
DownloadService and BackgroundWorkService each built the same partial
wake lock (30-minute timeout, 15-minute renew, not reference-counted),
the same dataSync startForeground SDK branch and the same content
PendingIntent to MainActivity. ForegroundWorkLock now holds the lock and
the shared constants, and two small helpers cover the start branch and
the intent.

The renew strategies stay per service: DownloadService renews on its
progress and maintenance calls once 15 minutes have passed, while
BackgroundWorkService re-acquires from a main-thread Handler timer.
Wake-lock tags, channel IDs, notification IDs, intent flags and request
codes are unchanged, and the verification intent keeps its own request
code and extras.
2026-10-19 04:58:25 +07:00
zarzet f88217dde4 refactor(android): move the lifecycle backend calls into MainActivityLifecycleCalls.kt
MainActivity.kt is now the dispatcher only. The screen wake,
verification notification, install marker, runtime state, exit,
backend implementation and memory release cases keep their labels and
order in the backend `when` and now call MainActivity.handle<Name>
functions in MainActivityLifecycleCalls.kt; processPendingExtensionCallbacks
stays inline.

Bodies move verbatim. prepareRuntimeState takes the backend that
configureFlutterEngine selected as a parameter, as the case body
captured it before, and flutterBackCallback is widened to internal for
exitApp.
2026-10-19 01:24:48 +07:00
zarzet 00b661be21 refactor(android): move the download service and worker backend calls into MainActivityServiceCalls.kt
MainActivity.kt should be the dispatcher only. downloadByStrategy, the
download foreground service, background work and native download worker
cases keep their labels and order in the backend `when`; the multi-line
ones now call MainActivity.handle<Name> functions in
MainActivityServiceCalls.kt, and the one-line stop/pause/resume/cancel
cases stay inline.

Bodies move verbatim apart from the receiver label for this@MainActivity.
2026-10-19 01:24:48 +07:00
zarzet cbf41441ff refactor(android): move the library scan backend calls into MainActivityLibraryScanCalls.kt
MainActivity.kt should be the dispatcher only. The cover cache, folder
and SAF tree scan, and scan progress/cancel/pause/resume cases keep
their labels and order in the backend `when` and now call
MainActivity.handle<Name> functions in MainActivityLibraryScanCalls.kt.
Bodies move verbatim. Every handler takes (call, result) so the
dispatch lines stay uniform, even where a body ignores the call.
2026-10-19 01:24:48 +07:00
zarzet 5d0f274414 refactor(android): move the metadata and tag backend calls into MainActivityMetadataCalls.kt
MainActivity.kt should be the dispatcher only. The metadata, tag,
cover, re-enrich, post-processing and CUE cases keep their labels and
order in the backend `when` and now call MainActivity.handle<Name>
functions in MainActivityMetadataCalls.kt.

Bodies move verbatim apart from the receiver label for this@MainActivity
and re-indenting the FLAC copy-back lines of editFileMetadata, which sat
at the wrong depth.
2026-10-19 01:24:48 +07:00
zarzet 01c51bf751 refactor(android): move the SAF backend calls into MainActivitySafCalls.kt
MainActivity.kt should be the dispatcher only. The backend `when` keeps
every case label in the same order, so the routing set check and the
dispatch order are unchanged, and each SAF case now calls a
MainActivity.handle<Name> function in MainActivitySafCalls.kt.

Bodies move verbatim; the only edit is this@MainActivity becoming the
extension function's own receiver label.
2026-10-19 01:24:48 +07:00
zarzet cf4c05cd93 refactor(android): move install state, extension intents, SAF leases and small channels out of MainActivity.kt
MainActivity.kt mixed the Activity lifecycle and the backend dispatcher
with several self-contained helpers. Give each its own file as
MainActivity extension functions, following the W2-N split:

- MainActivityInstallState.kt: ensureInstallMarker, prepareRuntimeState
- MainActivityExtensionIntents.kt: verification and OAuth intent
  handling, processPendingExtensionCallbacks, session-grant events
- MainActivitySafLeases.kt: SAF playback descriptor leases
- MainActivitySmallChannels.kt: the concert_calendar and audio_output
  MethodChannels, still registered at the same point and in the same
  order in configureFlutterEngine

The state these helpers touch stays in MainActivity and is widened from
private to internal. Bodies move verbatim except that companion members
are qualified with MainActivity. outside the class.
2026-10-19 01:24:48 +07:00
zarzet 20b4c73b8d refactor(android): move RustCoreBackend.invokeApplication into its own file
NT-16 Kotlin part. The application method dispatch (about 290 lines) moves
verbatim into RustCoreBackendApplication.kt as an internal extension
`RustCoreBackend.dispatchApplication`. The `invokeApplication` override
stays in SelectedCoreBackend.kt as a one-line delegate. `synchronized(this)`
still locks the RustCoreBackend object.

The object state and helpers the dispatch uses are widened from private to
internal; method names, argument keys, results and error strings are
unchanged.
2026-10-19 00:21:34 +07:00
zarzet b6b609f334 refactor(android): move Impeller helpers, the SAF tree picker and progress streams out of MainActivity.kt
First NT-12 seam group, following the MainActivitySafScan.kt and
MainActivitySafIo.kt receiver-function shape. Bodies are moved verbatim.

- ImpellerCompatibility.kt: the device lists and shouldDisableImpeller /
  getGpuRenderer, now file-level. ImpellerAwareFlutterFragment stays nested
  in MainActivity because proguard-rules.pro keeps it by that name.
- MainActivitySafTreePicker.kt: the pickSafTree launch (the case now calls
  launchSafTreePicker), the activity-result handler, and the storage id and
  display path helpers. The launcher registration stays a MainActivity
  property so it is still registered at construction.
- MainActivityProgressStreams.kt: both progress EventChannels, their
  start/stop/poll helpers and the channel name and polling constants.

Stream state, the picker store, the launcher, the coroutine scope, the
backend channel and the companion's pending picker result are widened from
private to internal so the receiver functions can reach them. Channel
names, payload keys, error codes and dispatch order are unchanged.
2026-10-19 00:21:34 +07:00
zarzet cc9b9f1162 test(android): assert the backend routing set matches the dispatch cases
MainActivity handles a backend call in its own `when` only when the method
name is listed in `nativeBackendMethods`; any other name falls through to the
selected core backend. A name added to only one of the two silently changes
where the call is handled. This JVM test reads MainActivity.kt as source,
extracts the set literal and the top-level string case labels of
`when (call.method)`, and fails with the symmetric difference when they drift.
2026-10-19 00:21:34 +07:00
zarzet b5cf97881f refactor(android): move finalizer history into NativeFinalizerHistory.kt
Move worker request contract validation and the download history row
and JSON mapping out of NativeDownloadFinalizer into a sibling file of
extension functions. Bodies are verbatim. NATIVE_WORKER_CONTRACT_VERSION
stays on the object with its value, so its contract test is unchanged.
The history column contract test now reads buildHistoryRow from the new
file. normalizeOptional moves with the row builder, so
cleanMetadataString widens from private to internal.
2026-10-19 00:14:47 +07:00
zarzet 631b9aa5e8 refactor(android): move finalizer post-processing into NativeFinalizerPostProcess.kt
Move the extension post-processing hook stage out of
NativeDownloadFinalizer into a sibling file as an extension function.
The body is verbatim. The shared materializeForFFmpeg and
replaceStatePath helpers stay on the object because every stage uses
them, and no object member needed wider visibility.
2026-10-19 00:14:47 +07:00
zarzet 0b4725c2c0 refactor(android): move finalizer metadata into NativeFinalizerMetadata.kt
Move the metadata embedding stage, ReplayGain tagging and verification,
the album key and duration helpers, and the final audio quality probe
out of NativeDownloadFinalizer into a sibling file of extension
functions. Bodies are verbatim. The moved code reads three JSON helpers
that stay on the object, so parseObject, optPositiveInt and
optPositiveBitrateKbps widen from private to internal.
2026-10-19 00:14:47 +07:00
zarzet ead04c00b4 refactor(android): move finalizer container into NativeFinalizerContainer.kt
Move the lossless container conversion and automatic conversion stages,
with their file-local helpers (output extension, M4A sniffing, unique
output path, preserved container rename), out of NativeDownloadFinalizer
into a sibling file of extension functions. Bodies are verbatim except
one moved comment line whose em dash is rewritten as a colon. No object
member needed wider visibility.
2026-10-19 00:14:47 +07:00
zarzet 92af56eea7 refactor(android): move finalizer decrypt into NativeFinalizerDecrypt.kt
NativeDownloadFinalizer.kt is a 1300-line god file. Move the decryption
stage (finalizeDecryption, ISO-BMFF extension normalization and key
candidate expansion) into a sibling file as NativeDownloadFinalizer
extension functions, matching NativeFinalizerMedia/FFmpeg/SafPublish.
Bodies are verbatim; file-local helpers stay private, and no object
member needed wider visibility.
2026-10-19 00:14:47 +07:00
zarzet 92adce43ef refactor(native): share errorJson and retire Go-era names in native comments
MainActivitySafIo and SafDownloadHandler each defined an identical
errorJson. Keep one internal top-level function in MainActivitySafIo
and drop the private copy. errorJson, isMediaStoreUri, extFromFileName
and extFromMimeType never used their MainActivity receiver, so they
become plain top-level functions; call sites are unchanged.

Rust is the only backend. Rename the iOS invokeGoMethod, which
dispatches the OS-owned channel methods, to invokeOsMethod, and reword
the comments that still described Go calls, the Go progress stream, the
Go metadata reader or a "native migration boundary" so they describe
the current backend. Channel method names and reply keys are unchanged.
2026-10-18 23:01:04 +07:00
zarzet fcec0942b9 refactor(android): drop SDK checks below minSdk 24
Flutter 3.47.7 sets flutter.minSdkVersion to 24, which
android/app/build.gradle.kts uses as minSdk. The N (24) checks around
StorageManager.storageVolumes and the LOLLIPOP (21) check around
finishAndRemoveTask were therefore always true, and their else branches
could never run. Keep the true-branch bodies. Checks for API levels
above 24 are unchanged.
2026-10-18 23:01:04 +07:00
zarzet 8aca5e4ef2 refactor(android): drop unreachable dispatch cases and dead channel handlers
RustCoreBackend.invokeApplication handled readFileMetadata,
editFileMetadata and cancelNativeDataJob, but MainActivity intercepts
all three first: the metadata methods are in nativeBackendMethods and
dispatchBackendApplication answers cancelNativeDataJob before it calls
invokeApplication. No Kotlin code calls invokeApplication with those
names.

Dart never sends these channel methods, so their handlers go on every
platform:

- shareMultipleContentUris (Android) and getAvailableLyricsProviders
  (Android and iOS) have no Dart caller.
- scanLibraryFolderIncrementalFromSnapshot and
  scanSafTreeIncrementalFromSnapshot are called only from the Dart host
  branch of the library scan, which runs only when
  supportsCoreBackend is false, that is never on Android or iOS. The
  unreachable Dart wrappers are removed separately.

The allowlist and the when cases in MainActivity stay in sync (70
names). The UniFFI exports get_available_lyrics_providers_json and
scan_library_folder_incremental_from_snapshot were used only by the
removed handlers; the backend methods behind them stay.
2026-10-18 23:01:04 +07:00
zarzet ca950dc35a refactor(android): remove backend switches that became constants
Rust is the only backend, so several single-backend switches had become
constants and their other branches could never run:

- CoreBackend.supportsOutputDescriptors was always false. The
  descriptor branch in SafDownloadHandler and its pfd/detachFd cleanup
  never ran, and the Rust planner rejects output_fd anyway.
- routesApplication was always true on Android and iOS. Drop the member,
  the "go" value of getBackendImplementations (the reply keys and the
  "rust" value Dart receives are unchanged), the iOS routing guard and
  the interface defaults that threw for unrouted methods.
- cleanupExtensions(), waitForDownloadProgressDelta and
  scanCueForLibrary had no production caller. Dart reaches cleanup
  through the cleanupExtensions channel method, and RustCoreBackend
  overrides openDownloadProgress, so the polling default goes too.

The UniFFI exports DownloadState.wait_progress_delta and
ExtensionManager.scan_cue_file_for_library were used only by those
Kotlin overrides. Bindings are generated at build time by
scripts/build_rust_backend.sh; no generated binding is tracked.
2026-10-18 23:01:04 +07:00
zarzet 66d480dbdc fix(storage): retain legacy Android cipher migration support 2026-10-18 04:09:16 +07:00
zarzet 75cd4c39b2 feat(android): refresh launcher widgets with adaptive expressive layouts 2026-10-18 03:10:40 +07:00
zarzet e0dfa7eb69 refactor(android): scope Rust download state handles consistently 2026-10-18 00:46:53 +07:00
zarzet 504c3cd18b refactor(android): share native album artist resolution 2026-10-18 00:46:10 +07:00
zarzet fdcd291c78 refactor(android): remove unreachable backend dispatch fallbacks 2026-10-18 00:46:09 +07:00
zarzet 3d11169f21 fix(network): request Android 17 local network access before NAS connections 2026-10-17 16:18:41 +07:00
zarzet 11c6cf9915 feat(player): synchronize music haptics with local playback 2026-10-17 16:18:37 +07:00
zarzet 40f65d3a4d feat(player): show audio source processing and output details 2026-10-17 16:18:32 +07:00
zarzet 3dc2d69051 fix(audio): preserve FLAC precision in Android bit-perfect playback 2026-10-15 00:28:17 +07:00
zarzet c0fd506c78 fix(storage): preserve audio MIME when publishing SAF downloads
Honor the requested MIME across publication paths, retain the audio suffix in temporary names, and exclude unfinished documents from full and incremental scans. Verify MIME and byte integrity across four formats and publication modes.
2026-10-13 19:38:11 +07:00
zarzet 186c66aeb8 fix(lyrics): refresh LRC sidecars in their actual SAF folder
Resolve opaque SAF document ancestry before writing adjacent lyrics. Propagate failed requested LRC writes and cover native word-synced refresh in regression tests.
2026-10-13 19:38:00 +07:00
zarzet e3b69a25ef fix(download): probe M4A streams before preserving requested FLAC
Provider and decryption extensions describe the downloaded container, not
its audio codec. A decrypted M4A/MP4 can still carry FLAC, so a requested
FLAC output now reaches the codec probe instead of being preserved early.
Lossy streams keep their native container and still receive metadata.
2026-10-13 18:27:16 +07:00
zarzet ac8523d789 feat(metadata): add per-field download tag separators 2026-10-12 12:11:06 +07:00
zarzet ebaa6c5c1e test(android): accept validated FLAC fallback across FFmpeg versions 2026-10-11 16:14:01 +07:00
zarzet d611590039 chore(android): update Gradle Kotlin NDK and native build dependencies 2026-10-11 14:33:52 +07:00
zarzet 1b9c35f322 fix(downloads): preserve browser verification and queues across restarts 2026-10-09 18:07:49 +07:00
zarzet 37258a6160 fix(setup): recover folder permissions after Android restarts 2026-10-09 18:07:38 +07:00
zarzet c7b1d0b0ec fix(metadata): preserve original artwork resolution for SAF files 2026-10-09 10:34:49 +07:00
zarzet 2ded1cff68 perf(downloads): batch queue cancellation and progress cleanup 2026-10-08 08:47:47 +07:00
zarzet c67d2a2dd2 perf(downloads): batch cancellation resets for retry 2026-10-08 06:55:26 +07:00
zarzet 6325c4769c fix(downloads): preserve actual provider audio format 2026-10-08 01:50:36 +07:00
zarzet d1de833184 perf(library): index scans and snapshots with native SQLite workers
Move full and incremental ingestion, source snapshots, path keys, and download exclusion to native transactions. Preserve schema 17, FTS, WAL coordination, cancellation, and Android SAF timestamp backfill.
2026-10-06 23:29:33 +07:00
zarzet d693e1a479 perf(theme): extract cover palettes with native quantization
Port the pinned Material quantizer and Dart RNG compatibility to Rust while keeping Material Score and final color schemes in Dart. Include exact palette fixtures, licenses, and Kotlin worker ABI coverage.
2026-10-06 23:27:38 +07:00
zarzet 867371cb67 feat(native): add cancellable data workers and streamed file hashing
Introduce Android, iOS, and Dart worker dispatch with queued cancellation, binary inputs, and background JSON codecs. Move APK verification to streamed native SHA256 while retaining the isolated fallback.
2026-10-06 23:25:39 +07:00
zarzet ca747f8ce2 fix(android): release temporary media on failed SAF operations 2026-10-06 22:00:44 +07:00
zarzet f61e815e24 perf(android): trim platform assets and release Dart symbols 2026-10-06 21:58:55 +07:00
zarzet b8c50b1527 perf(android): reduce release APK size
Keep Dart debug data outside libapp.so and archive matching symbols and R8 mappings in CI. Pack native relocations, preserve JNI exports, and recompress PNG assets losslessly.

ARM64 APK shrinks from 80,306,680 to 76,080,247 bytes (5.26%). Verified with 1,877 Flutter tests, 19 native device tests, Discord SDK integration, and release startup on a 16 KiB Android emulator.
2026-10-06 15:22:42 +07:00