Files
jellytau/scripts/sync-android-sources.sh
dtourolle caebf2d139 fix(android): keep the display awake while video plays
Android counts its display timeout from the last user input, and watching
something is exactly the case where there is none — so the screen dimmed and
slept mid-playback unless the user kept tapping it.

Nothing held it. FLAG_KEEP_SCREEN_ON appeared nowhere in the app, and neither
renderer supplies a hold for free: ExoPlayer's setWakeMode is a CPU/wifi wake
lock that says nothing about the display, and it draws into the TextureView we
own (DR-192) rather than media3's PlayerView, which is the widget that would
otherwise set keepScreenOn itself; the webview <video> path is no better,
because the display wake lock Chrome takes for video lives in the browser layer
and not in an embedded WebView.

ScreenWakeManager toggles FLAG_KEEP_SCREEN_ON on the Activity window — window
scoped, so it stops applying the moment the app is not visible and cannot
outlive a crash the way an acquired PowerManager.WakeLock can, and it needs no
permission. The two rendering paths are independent holders OR-ed in the pure
ScreenWakeState: the native path follows onIsPlayingChanged plus surface
teardown, so the hold tracks what ExoPlayer reports rather than what the UI
intends, and the webview path reuses the setHtml5VideoState report the frontend
already sends for PiP. Audio is deliberately not a holder — screen-off music is
the point of that path.

Also the repo's first Kotlin JVM unit tests: ScreenWakeState is framework-free,
so the decision is testable off-device with

    ./gradlew :app:testUniversalDebugUnitTest

(note the variant — plain testDebugUnitTest is ambiguous here). sync-android
-sources.sh mirrors src/test into the gen tree alongside the main sources.

TRACES: UR-003, UR-004 | DR-202 | UT-199
2026-08-18 14:56:08 +02:00

157 lines
7.3 KiB
Bash
Executable File

#!/bin/bash
# Sync Android source files from src-tauri/android to src-tauri/gen/android
# This ensures the generated build directory has the latest source files
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
SOURCE_DIR="$PROJECT_ROOT/src-tauri/android/src/main/java/com/dtourolle/jellytau"
TARGET_DIR="$PROJECT_ROOT/src-tauri/gen/android/app/src/main/java/com/dtourolle/jellytau"
echo "Syncing Android sources..."
echo " From: $SOURCE_DIR"
echo " To: $TARGET_DIR"
# Create target directory if it doesn't exist
mkdir -p "$TARGET_DIR"
# Remove old copies of player and security directories
rm -rf "$TARGET_DIR/player" "$TARGET_DIR/security"
# Copy the directories
cp -r "$SOURCE_DIR/player" "$TARGET_DIR/"
cp -r "$SOURCE_DIR/security" "$TARGET_DIR/"
# JVM unit tests (src/test). Plain JUnit over the pure decision helpers — no
# Android framework classes — run with `./gradlew :app:testDebugUnitTest` from
# gen/android. Mirrored here so the canonical tree stays the only place tests
# are edited.
TEST_SOURCE_DIR="$PROJECT_ROOT/src-tauri/android/src/test/java/com/dtourolle/jellytau"
TEST_TARGET_DIR="$PROJECT_ROOT/src-tauri/gen/android/app/src/test/java/com/dtourolle/jellytau"
if [ -d "$TEST_SOURCE_DIR" ]; then
rm -rf "$TEST_TARGET_DIR"
mkdir -p "$TEST_TARGET_DIR"
cp -r "$TEST_SOURCE_DIR"/. "$TEST_TARGET_DIR/"
echo " Copied unit tests: src/test"
fi
# Copy individual Kotlin files (like VideoOverlayManager.kt)
for kt_file in "$SOURCE_DIR"/*.kt; do
if [ -f "$kt_file" ]; then
cp "$kt_file" "$TARGET_DIR/"
echo " Copied: $(basename "$kt_file")"
fi
done
# Restore the app module build.gradle.kts (media3 deps + release signing config).
# gen/android is regenerated by `tauri android init`, so this tracked template is
# the source of truth and must be copied back after any (re)generation.
APP_GRADLE_SRC="$PROJECT_ROOT/src-tauri/android/app/build.gradle.kts"
APP_GRADLE_DST="$PROJECT_ROOT/src-tauri/gen/android/app/build.gradle.kts"
if [ -f "$APP_GRADLE_SRC" ]; then
cp "$APP_GRADLE_SRC" "$APP_GRADLE_DST"
echo " Copied: app/build.gradle.kts"
fi
# AndroidManifest.xml. `tauri android init` regenerates gen/android from
# tauri.conf.json and would drop our hand-maintained entries (media playback
# service + permissions, hardware acceleration, picture-in-picture attributes
# on MainActivity), so this tracked copy is the source of truth and must be
# restored after a regen. Gradle reads ONLY the gen/ copy - there is no
# manifest-merger hook here, so this must be the complete manifest.
MANIFEST_SRC="$PROJECT_ROOT/src-tauri/android/src/main/AndroidManifest.xml"
MANIFEST_DST="$PROJECT_ROOT/src-tauri/gen/android/app/src/main/AndroidManifest.xml"
if [ -f "$MANIFEST_SRC" ]; then
cp "$MANIFEST_SRC" "$MANIFEST_DST"
echo " Copied: app/src/main/AndroidManifest.xml"
fi
# Custom ProGuard/R8 keep rules. Required for minified release builds:
# the player/ and security/ Kotlin classes are loaded by name via JNI from
# Rust, so R8 can't see the references and would strip them without this.
# build.gradle.kts globs **/*.pro, so dropping it in app/ is enough.
PROGUARD_SRC="$PROJECT_ROOT/src-tauri/android/app/proguard-jellytau.pro"
PROGUARD_DST="$PROJECT_ROOT/src-tauri/gen/android/app/proguard-jellytau.pro"
if [ -f "$PROGUARD_SRC" ]; then
cp "$PROGUARD_SRC" "$PROGUARD_DST"
echo " Copied: app/proguard-jellytau.pro"
fi
# Launcher icons / adaptive-icon mipmaps. `tauri android init` generates
# low-quality launcher icons from tauri.conf.json (which has no high-res
# Android source), so overwrite them with the real committed mipmaps.
RES_SRC="$PROJECT_ROOT/src-tauri/android/src/main/res"
RES_DST="$PROJECT_ROOT/src-tauri/gen/android/app/src/main/res"
if [ -d "$RES_SRC" ]; then
for dir in "$RES_SRC"/mipmap-*; do
[ -d "$dir" ] || continue
name="$(basename "$dir")"
mkdir -p "$RES_DST/$name"
cp "$dir"/* "$RES_DST/$name/"
echo " Copied res: $name"
done
# values/ (themes.xml): status-bar styling that `tauri android init` does
# not generate. Previously this directory was tracked but never copied, so
# the theme customizations below never reached a build.
if [ -d "$RES_SRC/values" ]; then
mkdir -p "$RES_DST/values"
cp "$RES_SRC"/values/*.xml "$RES_DST/values/"
echo " Copied res: values"
fi
# xml/ (network_security_config.xml): referenced from the manifest, so a
# missing copy fails the resource link rather than degrading quietly.
# Merged into Tauri's generated xml/ (which holds file_paths.xml) rather
# than replacing it.
if [ -d "$RES_SRC/xml" ]; then
mkdir -p "$RES_DST/xml"
cp "$RES_SRC"/xml/*.xml "$RES_DST/xml/"
echo " Copied res: xml"
fi
# We ship only the color adaptive icon (background + foreground). Drop any
# monochrome layer Tauri may generate: the themed-icon monochrome doesn't
# render well, and our adaptive-icon xml no longer references it, so a stray
# ic_launcher_monochrome.png would just be dead weight.
rm -f "$RES_DST"/mipmap-*/ic_launcher_monochrome.png
# `tauri android init` also emits the Android Studio DEFAULT adaptive icon
# as API-qualified VECTOR drawables:
# drawable/ic_launcher_background.xml (solid #3DDC84 green)
# drawable-v24/ic_launcher_foreground.xml (the Android robot)
# Because drawable-v24 is a more specific match than our unqualified
# mipmap-*/ic_launcher_*.png, on API 24+ the vector WINS and the app ships
# the green square robot instead of our jellyfish. Remove them so the
# adaptive-icon xml resolves @mipmap/ic_launcher_{background,foreground}
# to the real committed PNGs.
rm -f "$RES_DST"/drawable/ic_launcher_background.xml \
"$RES_DST"/drawable-v24/ic_launcher_foreground.xml \
"$RES_DST"/drawable*/ic_launcher_foreground.xml \
"$RES_DST"/drawable*/ic_launcher_background.xml
fi
# Gradle wrapper distribution. `tauri android init` regenerates the wrapper
# pointing at services.gradle.org, so each build downloads ~130MB of Gradle —
# slow, and a hard failure when the CDN drops the connection mid-transfer
# ("Unexpected end of file from server"), which is what broke the release APK
# job. The builder image ships the matching distribution under /opt/gradle/dist,
# so when it's present repoint the wrapper at that local zip and build offline.
# Outside the image (dev machines) the properties file is left untouched and the
# wrapper downloads as usual.
WRAPPER_PROPS="$PROJECT_ROOT/src-tauri/gen/android/gradle/wrapper/gradle-wrapper.properties"
if [ -f "$WRAPPER_PROPS" ]; then
WANTED_VERSION="$(sed -n 's#.*/gradle-\([0-9.]*\)-\(bin\|all\)\.zip.*#\1#p' "$WRAPPER_PROPS")"
LOCAL_DIST="/opt/gradle/dist/gradle-${WANTED_VERSION}-bin.zip"
if [ -n "$WANTED_VERSION" ] && [ -f "$LOCAL_DIST" ]; then
# distributionUrl is a java.util.Properties value: ':' must stay escaped.
sed -i "s#^distributionUrl=.*#distributionUrl=file\\\\:///opt/gradle/dist/gradle-${WANTED_VERSION}-bin.zip#" \
"$WRAPPER_PROPS"
echo " Gradle wrapper -> local distribution ($WANTED_VERSION, offline)"
elif [ -n "$WANTED_VERSION" ]; then
echo " Gradle wrapper: $WANTED_VERSION not in image, will download"
fi
fi
echo "✓ Android sources synced successfully"