#!/usr/bin/env python3 """Run real Android activity timing/EOF handlers with fixture events, without JNI. Uses the existing lean Robolectric resolver/runner. This is control-flow evidence, not decoded-media, network, or physical-device verification. """ import argparse import fcntl import hashlib import json import os from pathlib import Path import sys import zipfile import player_overlay_android_test as harness ROOT = Path(__file__).resolve().parents[1] SRC = ROOT / 'android/app/src/main/kotlin/org/reelbox/reelbox' WORK = Path('/root/.hermes/cache/scratch/reelbox-native-timing') def aar_classes(artifact, version): matches = sorted((harness.CACHE / artifact / version).glob('*/*.aar')) if not matches: raise RuntimeError(f'Missing cached AAR: {artifact}') target = WORK / (matches[-1].stem + '-classes.jar') with zipfile.ZipFile(matches[-1]) as archive: target.write_bytes(archive.read('classes.jar')) return target def main(): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument('--test') args = parser.parse_args() WORK.mkdir(parents=True, exist_ok=True) (WORK / 'tmp').mkdir(exist_ok=True) harness.WORK = WORK sdk = Path(os.environ.get('ANDROID_HOME', os.environ.get('ANDROID_SDK_ROOT', str(Path.home() / 'Android/Sdk')))) android = sdk / 'platforms/android-36/android.jar' if not android.is_file(): raise RuntimeError(f'Android 36 jar missing: {android}') with (ROOT / '.tooling.lock').open('a') as lock: fcntl.flock(lock, fcntl.LOCK_EX) harness.LOCK_FD = lock.fileno() deps = harness.resolve() stdlib = harness.cached_jar('org.jetbrains.kotlin/kotlin-stdlib', '2.4.0') vlc = aar_classes('org.videolan.android/libvlc-all', '3.7.7') core = aar_classes('androidx.core/core', '1.16.0') classes = WORK / 'classes' classes.mkdir(exist_ok=True) cp = os.pathsep.join(map(str, [classes, android, stdlib, vlc, core, *deps])) compiler = [harness.cached_jar('org.jetbrains.kotlin/kotlin-compiler-embeddable', '2.4.0'), stdlib, harness.cached_jar('org.jetbrains.kotlin/kotlin-script-runtime', '2.4.0'), harness.cached_jar('org.jetbrains.kotlin/kotlin-reflect'), harness.cached_jar('org.jetbrains.kotlinx/kotlinx-coroutines-core-jvm'), harness.cached_jar('org.jetbrains/annotations')] compiler += sorted((harness.CACHE / 'org.jetbrains.intellij.deps/trove4j').glob('*/*/*.jar'))[-1:] sources = [SRC / name for name in ('NativeUrlPolicy.kt', 'NativePlayerPolicy.kt', 'NativeFileIntents.kt', 'PlayerControlsState.kt', 'PlayerOverlayView.kt', 'VlcPlayerActivity.kt')] test = ROOT / 'android/app/src/test/java/org/reelbox/reelbox/NativePlayerTimingAndroidTest.java' hashes = {str(p): hashlib.sha256(p.read_bytes()).hexdigest() for p in [*sources, test]} (WORK / 'tested-inputs.json').write_text(json.dumps(hashes, indent=2) + '\n') harness.run([str(harness.JDK / 'bin/java'), '-Xmx256m', '-XX:+UseSerialGC', f'-Djava.io.tmpdir={WORK / "tmp"}', '-cp', os.pathsep.join(map(str, compiler)), 'org.jetbrains.kotlin.cli.jvm.K2JVMCompiler', '-jvm-target', '17', '-no-stdlib', '-no-reflect', '-classpath', cp, '-d', str(classes), *map(str, sources)], log='kotlin-compile.log') (WORK / 'OverlayHostRunner.java').write_text(harness.RUNNER) (WORK / 'OverlayHarnessSmokeTest.java').write_text(harness.SMOKE) harness.run([str(harness.JDK / 'bin/javac'), '-J-Xmx192m', '--release', '17', '-cp', cp, '-d', str(classes), str(WORK / 'OverlayHostRunner.java'), str(WORK / 'OverlayHarnessSmokeTest.java'), str(test)], log='javac-timing.log') opens = ['java.base/java.lang', 'java.base/java.util', 'java.base/java.io', 'java.base/java.net', 'java.base/java.security', 'java.base/java.text', 'java.base/jdk.internal.access', 'java.desktop/java.awt.font', 'jdk.compiler/com.sun.tools.javac.api'] java = [str(harness.JDK / 'bin/java'), '-Xmx320m', '-XX:MaxMetaspaceSize=192m', '-XX:+UseSerialGC', f'-Djava.io.tmpdir={WORK / "tmp"}', '-Drobolectric.dependency.repo.url=https://repo.maven.apache.org/maven2', f'-Dmaven.repo.local={WORK / "m2"}', '-Drobolectric.graphicsMode=LEGACY', *['--add-opens=' + p + '=ALL-UNNAMED' for p in opens], '-cp', cp, 'org.reelbox.reelbox.OverlayHostRunner'] harness.run([*java, 'org.reelbox.reelbox.OverlayHarnessSmokeTest', str(WORK / 'smoke-results.xml')], log='smoke.log') command = [*java, 'org.reelbox.reelbox.NativePlayerTimingAndroidTest', str(WORK / 'timing-results.xml')] if args.test: command.append(args.test) harness.run(command, log='timing.log') assert all(hashlib.sha256(Path(p).read_bytes()).hexdigest() == digest for p, digest in hashes.items()) print('PASS: actual activity event handlers and result gate; fixture events, no decoder/device claims') if __name__ == '__main__': main()