#!/bin/sh # Package an ltk app built as a cdylib into an APK, with Debian's own tools: # aapt, zipalign, apksigner and android-framework-res. The app is a # NativeActivity with no Java code, whose library android-activity loads as # `libmain.so`. ASSETS_DIR, when given, becomes the APK's `assets/`; the # `ltk/` tree from android-assets.sh goes there. # # Usage: scripts/android-apk.sh OUT.apk LIB.so PACKAGE LABEL [ASSETS_DIR] # # Environment, with its defaults: # ANDROID_ABI arm64-v8a # MIN_SDK 26 # TARGET_SDK 34 # VERSION_CODE 1 # VERSION_NAME 0.1 # PERMISSIONS (none) space-separated, e.g. "android.permission.INTERNET" # ICON (none) the launcher icon, SVG (through rsvg-convert) or PNG # KEEP_ALIVE (none) 1 adds java/'s KeepAlive service, which ltk::keep_running starts # KEEP_ALIVE_USE what keeps the app running, which Android asks of the service # ANDROID_JAR /usr/lib/android-sdk/platforms/android-34/android.jar, to compile it against, # and, unless FRAMEWORK_RES is set, the resources aapt links against # KEYSTORE ~/.android/debug.keystore, created when missing # KS_PASS android # BUILD_TOOLS /usr/lib/android-sdk/build-tools/debian # FRAMEWORK_RES /usr/share/android-framework-res/framework-res.apk set -eu if [ $# -lt 4 ] then echo "usage: $0 OUT.apk LIB.so PACKAGE LABEL [ASSETS_DIR]" >&2 exit 2 fi out=$1 lib=$2 package=$3 label=$( printf '%s' "$4" | sed 's/&/\&/g; s/&2 exit 1 fi done work=$( mktemp -d ) trap 'rm -rf "$work"' EXIT icon_attr= res_args= if [ -n "${ICON:-}" ] then for density in mdpi:48 hdpi:72 xhdpi:96 xxhdpi:144 xxxhdpi:192 do dir=$work/res/mipmap-${density%%:*} mkdir -p "$dir" case $ICON in *.svg) command -v rsvg-convert > /dev/null || { echo "android-apk: rsvg-convert missing; install librsvg2-bin" >&2; exit 1; } rsvg-convert -w "${density##*:}" -h "${density##*:}" -o "$dir/ic_launcher.png" "$ICON" ;; *) cp "$ICON" "$dir/ic_launcher.png" ;; esac done icon_attr=' android:icon="@mipmap/ic_launcher"' res_args="-S $work/res" fi has_code=false if [ "${KEEP_ALIVE:-}" = 1 ] then android_jar=${ANDROID_JAR:-/usr/lib/android-sdk/platforms/android-34/android.jar} java_dir=$( cd "$( dirname "$0" )/.." && pwd )/java for tool in javac dx "$android_jar" do if ! command -v "$tool" > /dev/null && [ ! -e "$tool" ] then echo "android-apk: $tool missing; install default-jdk-headless, art-standalone and google-android-platform-34-installer" >&2 exit 1 fi done mkdir -p "$work/classes" javac -nowarn -source 8 -target 8 -bootclasspath "$android_jar" -d "$work/classes" $( find "$java_dir" -name '*.java' ) dx --dex --min-sdk-version=26 --output="$work/classes.dex" "$work/classes" has_code=true # Debian's framework-res lacks the service type `specialUse`, which Android 14 added. framework=${FRAMEWORK_RES:-$android_jar} PERMISSIONS="${PERMISSIONS:-} android.permission.FOREGROUND_SERVICE android.permission.FOREGROUND_SERVICE_SPECIAL_USE android.permission.POST_NOTIFICATIONS" fi { echo '' echo '" echo " " echo ' ' for permission in ${PERMISSIONS:-} do echo " " done echo " ' echo '' } > "$work/AndroidManifest.xml" mkdir -p "$work/lib/$abi" cp "$lib" "$work/lib/$abi/libmain.so" if [ -n "$assets" ] then "$tools/aapt" package -f -M "$work/AndroidManifest.xml" -I "$framework" $res_args -A "$assets" -F "$work/unaligned.apk" else "$tools/aapt" package -f -M "$work/AndroidManifest.xml" -I "$framework" $res_args -F "$work/unaligned.apk" fi ( cd "$work" && "$tools/aapt" add unaligned.apk "lib/$abi/libmain.so" > /dev/null ) if [ "$has_code" = true ] then ( cd "$work" && "$tools/aapt" add unaligned.apk classes.dex > /dev/null ) fi zipalign -f -p 4 "$work/unaligned.apk" "$work/aligned.apk" if [ ! -f "$keystore" ] then mkdir -p "$( dirname "$keystore" )" keytool -genkeypair -keystore "$keystore" -storepass "$pass" -keypass "$pass" \ -alias androiddebugkey -keyalg RSA -keysize 2048 -validity 10000 \ -dname "CN=Android Debug,O=Android,C=US" fi mkdir -p "$( dirname "$out" )" "$tools/apksigner" sign --ks "$keystore" --ks-pass "pass:$pass" --out "$out" "$work/aligned.apk" echo "android-apk: $out"