#!/usr/bin/env python3 """Packs an APK without aapt: binary AndroidManifest.xml, a tiny resources.arsc (icon + app name), classes.dex. Output is unsigned; build.sh signs and aligns it. usage: mkapk.py AndroidManifest.xml classes.dex icon.png out.apk """ import struct import sys import zipfile import xml.etree.ElementTree as ET ANDROID_NS = "http://schemas.android.com/apk/res/android" # android.R.attr ids (stable since API 1-4) ATTR_IDS = { "theme": 0x01010000, "label": 0x01010001, "icon": 0x01010002, "name": 0x01010003, "permission": 0x01010006, "enabled": 0x0101000e, "exported": 0x01010010, "configChanges": 0x0101001f, "minSdkVersion": 0x0101020c, "versionCode": 0x0101021b, "versionName": 0x0101021c, "windowSoftInputMode": 0x0101022b, "targetSdkVersion": 0x01010270, "installLocation": 0x010102b7, } INT_ATTRS = {"versionCode", "minSdkVersion", "targetSdkVersion"} BOOL_ATTRS = {"exported", "enabled"} FLAGS = { "configChanges": {"mcc": 1, "mnc": 2, "locale": 4, "touchscreen": 8, "keyboard": 0x10, "keyboardHidden": 0x20, "navigation": 0x40, "orientation": 0x80, "screenLayout": 0x100, "uiMode": 0x200, "fontScale": 0x40000000}, "windowSoftInputMode": {"stateUnspecified": 0, "stateHidden": 2, "stateAlwaysHidden": 3, "adjustResize": 0x10, "adjustPan": 0x20}, "installLocation": {"auto": 0, "internalOnly": 1, "preferExternal": 2}, } T_REF, T_STRING, T_INT_DEC, T_INT_HEX, T_BOOL = 0x01, 0x03, 0x10, 0x11, 0x12 # Our own resources: type id -> (type name, [(key, value)]) RES_TYPES = [("drawable", [("icon", "res/drawable/icon.png")]), ("string", [("app_name", "GingerCal")])] def res_id(ref): typ, key = ref.lstrip("@").split("/") for ti, (tname, entries) in enumerate(RES_TYPES): for ei, (k, _) in enumerate(entries): if tname == typ and k == key: return 0x7F000000 | ((ti + 1) << 16) | ei raise SystemExit("unknown resource " + ref) # ------------------------------------------------------------- string pool def string_pool(strings): """UTF-16 ResStringPool (understood by every Android version).""" offsets, data = [], b"" for s in strings: offsets.append(len(data)) u = s.encode("utf-16-le") n = len(u) // 2 if n > 0x7FFF: raise SystemExit("string too long") data += struct.pack("