282 lines
10 KiB
Python
282 lines
10 KiB
Python
"""Internal support module for Android builds."""
|
|
|
|
import xml.etree.ElementTree as ET
|
|
|
|
from ._proto.targeting_pb2 import Abi
|
|
from ._proto.config_pb2 import BundleConfig # pylint: disable=unused-import
|
|
from ._proto.files_pb2 import NativeLibraries # pylint: disable=unused-import
|
|
from ._proto.Resources_pb2 import ResourceTable # pylint: disable=unused-import
|
|
from ._proto.Resources_pb2 import XmlNode
|
|
|
|
|
|
AbiAlias = Abi.AbiAlias
|
|
|
|
|
|
def str_resource(id):
|
|
def compile(attrib, manifest):
|
|
attrib.resource_id = id
|
|
return compile
|
|
|
|
|
|
def int_resource(id):
|
|
def compile(attrib, manifest):
|
|
attrib.resource_id = id
|
|
if attrib.value.startswith('0x') or attrib.value.startswith('0X'):
|
|
attrib.compiled_item.prim.int_hexadecimal_value = int(attrib.value, 16)
|
|
else:
|
|
attrib.compiled_item.prim.int_decimal_value = int(attrib.value)
|
|
return compile
|
|
|
|
|
|
def bool_resource(id):
|
|
def compile(attrib, manifest):
|
|
attrib.resource_id = id
|
|
attrib.compiled_item.prim.boolean_value = {
|
|
'true': True, '1': True, 'false': False, '0': False
|
|
}[attrib.value]
|
|
return compile
|
|
|
|
|
|
def enum_resource(id, *values):
|
|
def compile(attrib, manifest):
|
|
attrib.resource_id = id
|
|
attrib.compiled_item.prim.int_decimal_value = values.index(attrib.value)
|
|
return compile
|
|
|
|
|
|
def flag_resource(id, **values):
|
|
def compile(attrib, manifest):
|
|
attrib.resource_id = id
|
|
bitmask = 0
|
|
flags = attrib.value.split('|')
|
|
for flag in flags:
|
|
bitmask = values[flag]
|
|
attrib.compiled_item.prim.int_hexadecimal_value = bitmask
|
|
return compile
|
|
|
|
|
|
def ref_resource(id):
|
|
def compile(attrib, manifest):
|
|
assert attrib.value[0] == '@'
|
|
ref_type, ref_name = attrib.value[1:].split('/')
|
|
attrib.resource_id = id
|
|
attrib.compiled_item.ref.name = ref_type + '/' + ref_name
|
|
|
|
if ref_type == 'android:style':
|
|
attrib.compiled_item.ref.id = ANDROID_STYLES[ref_name]
|
|
elif ':' not in ref_type:
|
|
attrib.compiled_item.ref.id = manifest.register_resource(ref_type, ref_name)
|
|
else:
|
|
print(f'Warning: unhandled AndroidManifest.xml reference "{attrib.value}"')
|
|
return compile
|
|
|
|
|
|
# See data/res/values/public.xml
|
|
ANDROID_STYLES = {
|
|
'Animation': 0x01030000,
|
|
'Animation.Activity': 0x01030001,
|
|
'Animation.Dialog': 0x01030002,
|
|
'Animation.Translucent': 0x01030003,
|
|
'Animation.Toast': 0x01030004,
|
|
'Theme': 0x01030005,
|
|
'Theme.NoTitleBar': 0x01030006,
|
|
'Theme.NoTitleBar.Fullscreen': 0x01030007,
|
|
'Theme.Black': 0x01030008,
|
|
'Theme.Black.NoTitleBar': 0x01030009,
|
|
'Theme.Black.NoTitleBar.Fullscreen': 0x0103000a,
|
|
'Theme.Dialog': 0x0103000b,
|
|
'Theme.Light': 0x0103000c,
|
|
'Theme.Light.NoTitleBar': 0x0103000d,
|
|
'Theme.Light.NoTitleBar.Fullscreen': 0x0103000e,
|
|
'Theme.Translucent': 0x0103000f,
|
|
'Theme.Translucent.NoTitleBar': 0x01030010,
|
|
'Theme.Translucent.NoTitleBar.Fullscreen': 0x01030011,
|
|
'Widget': 0x01030012,
|
|
'Widget.AbsListView': 0x01030013,
|
|
'Widget.Button': 0x01030014,
|
|
'Widget.Button.Inset': 0x01030015,
|
|
'Widget.Button.Small': 0x01030016,
|
|
'Widget.Button.Toggle': 0x01030017,
|
|
'Widget.CompoundButton': 0x01030018,
|
|
'Widget.CompoundButton.CheckBox': 0x01030019,
|
|
'Widget.CompoundButton.RadioButton': 0x0103001a,
|
|
'Widget.CompoundButton.Star': 0x0103001b,
|
|
'Widget.ProgressBar': 0x0103001c,
|
|
'Widget.ProgressBar.Large': 0x0103001d,
|
|
'Widget.ProgressBar.Small': 0x0103001e,
|
|
'Widget.ProgressBar.Horizontal': 0x0103001f,
|
|
'Widget.SeekBar': 0x01030020,
|
|
'Widget.RatingBar': 0x01030021,
|
|
'Widget.TextView': 0x01030022,
|
|
'Widget.EditText': 0x01030023,
|
|
'Widget.ExpandableListView': 0x01030024,
|
|
'Widget.ImageWell': 0x01030025,
|
|
'Widget.ImageButton': 0x01030026,
|
|
'Widget.AutoCompleteTextView': 0x01030027,
|
|
'Widget.Spinner': 0x01030028,
|
|
'Widget.TextView.PopupMenu': 0x01030029,
|
|
'Widget.TextView.SpinnerItem': 0x0103002a,
|
|
'Widget.DropDownItem': 0x0103002b,
|
|
'Widget.DropDownItem.Spinner': 0x0103002c,
|
|
'Widget.ScrollView': 0x0103002d,
|
|
'Widget.ListView': 0x0103002e,
|
|
'Widget.ListView.White': 0x0103002f,
|
|
'Widget.ListView.DropDown': 0x01030030,
|
|
'Widget.ListView.Menu': 0x01030031,
|
|
'Widget.GridView': 0x01030032,
|
|
'Widget.WebView': 0x01030033,
|
|
'Widget.TabWidget': 0x01030034,
|
|
'Widget.Gallery': 0x01030035,
|
|
'Widget.PopupWindow': 0x01030036,
|
|
'MediaButton': 0x01030037,
|
|
'MediaButton.Previous': 0x01030038,
|
|
'MediaButton.Next': 0x01030039,
|
|
'MediaButton.Play': 0x0103003a,
|
|
'MediaButton.Ffwd': 0x0103003b,
|
|
'MediaButton.Rew': 0x0103003c,
|
|
'MediaButton.Pause': 0x0103003d,
|
|
'TextAppearance': 0x0103003e,
|
|
'TextAppearance.Inverse': 0x0103003f,
|
|
'TextAppearance.Theme': 0x01030040,
|
|
'TextAppearance.DialogWindowTitle': 0x01030041,
|
|
'TextAppearance.Large': 0x01030042,
|
|
'TextAppearance.Large.Inverse': 0x01030043,
|
|
'TextAppearance.Medium': 0x01030044,
|
|
'TextAppearance.Medium.Inverse': 0x01030045,
|
|
'TextAppearance.Small': 0x01030046,
|
|
'TextAppearance.Small.Inverse': 0x01030047,
|
|
'TextAppearance.Theme.Dialog': 0x01030048,
|
|
'TextAppearance.Widget': 0x01030049,
|
|
'TextAppearance.Widget.Button': 0x0103004a,
|
|
'TextAppearance.Widget.IconMenu.Item': 0x0103004b,
|
|
'TextAppearance.Widget.EditText': 0x0103004c,
|
|
'TextAppearance.Widget.TabWidget': 0x0103004d,
|
|
'TextAppearance.Widget.TextView': 0x0103004e,
|
|
'TextAppearance.Widget.TextView.PopupMenu': 0x0103004f,
|
|
'TextAppearance.Widget.DropDownHint': 0x01030050,
|
|
'TextAppearance.Widget.DropDownItem': 0x01030051,
|
|
'TextAppearance.Widget.TextView.SpinnerItem': 0x01030052,
|
|
'TextAppearance.WindowTitle': 0x01030053,
|
|
}
|
|
|
|
|
|
# See data/res/values/public.xml, attrs.xml and especially attrs_manifest.xml
|
|
ANDROID_ATTRIBUTES = {
|
|
'allowBackup': bool_resource(0x1010280),
|
|
'allowClearUserData': bool_resource(0x1010005),
|
|
'allowParallelSyncs': bool_resource(0x1010332),
|
|
'allowSingleTap': bool_resource(0x1010259),
|
|
'allowTaskReparenting': bool_resource(0x1010204),
|
|
'alwaysRetainTaskState': bool_resource(0x1010203),
|
|
'clearTaskOnLaunch': bool_resource(0x1010015),
|
|
'debuggable': bool_resource(0x0101000f),
|
|
'documentLaunchMode': enum_resource(0x1010445, "none", "intoExisting", "always", "never"),
|
|
'configChanges': flag_resource(0x0101001f, mcc=0x0001, mnc=0x0002, locale=0x0004, touchscreen=0x0008, keyboard=0x0010, keyboardHidden=0x0020, navigation=0x0040, orientation=0x0080, screenLayout=0x0100, uiMode=0x0200, screenSize=0x0400, smallestScreenSize=0x0800, layoutDirection=0x2000, fontScale=0x40000000),
|
|
'enabled': bool_resource(0x101000e),
|
|
'excludeFromRecents': bool_resource(0x1010017),
|
|
'exported': bool_resource(0x1010010),
|
|
'extractNativeLibs': bool_resource(0x10104ea),
|
|
'finishOnTaskLaunch': bool_resource(0x1010014),
|
|
'fullBackupContent': bool_resource(0x10104eb),
|
|
'glEsVersion': int_resource(0x1010281),
|
|
'hasCode': bool_resource(0x101000c),
|
|
'host': str_resource(0x1010028),
|
|
'icon': ref_resource(0x1010002),
|
|
'immersive': bool_resource(0x10102c0),
|
|
'installLocation': enum_resource(0x10102b7, "auto", "internalOnly", "preferExternal"),
|
|
'isGame': bool_resource(0x010103f4),
|
|
'label': str_resource(0x01010001),
|
|
'launchMode': enum_resource(0x101001d, "standard", "singleTop", "singleTask", "singleInstance"),
|
|
'maxSdkVersion': int_resource(0x1010271),
|
|
'mimeType': str_resource(0x1010026),
|
|
'minSdkVersion': int_resource(0x101020c),
|
|
'multiprocess': bool_resource(0x1010013),
|
|
'name': str_resource(0x1010003),
|
|
'noHistory': bool_resource(0x101022d),
|
|
'pathPattern': str_resource(0x101002c),
|
|
'resizeableActivity': bool_resource(0x10104f6),
|
|
'required': bool_resource(0x101028e),
|
|
'scheme': str_resource(0x1010027),
|
|
'screenOrientation': enum_resource(0x101001e, 'landscape', 'portrait', 'user', 'behind', 'sensor', 'nosensor', 'sensorLandscape', 'sensorPortrait', 'reverseLandscape', 'reversePortrait', 'fullSensor', 'userLandscape', 'userPortrait', 'fullUser', 'locked'),
|
|
'stateNotNeeded': bool_resource(0x1010016),
|
|
'supportsRtl': bool_resource(0x010103af),
|
|
'supportsUploading': bool_resource(0x101029b),
|
|
'targetSandboxVersion': int_resource(0x101054c),
|
|
'targetSdkVersion': int_resource(0x1010270),
|
|
'theme': ref_resource(0x01010000),
|
|
'value': str_resource(0x1010024),
|
|
'versionCode': int_resource(0x101021b),
|
|
'versionName': str_resource(0x101021c),
|
|
}
|
|
|
|
|
|
class AndroidManifest:
|
|
def __init__(self):
|
|
super().__init__()
|
|
self._stack = []
|
|
self.root = XmlNode()
|
|
self.resource_types = []
|
|
self.resources = {}
|
|
|
|
def parse_xml(self, data):
|
|
parser = ET.XMLParser(target=self)
|
|
parser.feed(data)
|
|
parser.close()
|
|
|
|
def start_ns(self, prefix, uri):
|
|
decl = self.root.element.namespace_declaration.add()
|
|
decl.prefix = prefix
|
|
decl.uri = uri
|
|
|
|
def start(self, tag, attribs):
|
|
if not self._stack:
|
|
node = self.root
|
|
else:
|
|
node = self._stack[-1].child.add()
|
|
|
|
element = node.element
|
|
element.name = tag
|
|
|
|
self._stack.append(element)
|
|
|
|
for key, value in attribs.items():
|
|
attrib = element.attribute.add()
|
|
attrib.value = value
|
|
|
|
if key.startswith('{'):
|
|
attrib.namespace_uri, key = key[1:].split('}', 1)
|
|
res_compile = ANDROID_ATTRIBUTES.get(key, None)
|
|
if not res_compile:
|
|
print(f'Warning: unhandled AndroidManifest.xml attribute "{key}"')
|
|
else:
|
|
res_compile = None
|
|
|
|
attrib.name = key
|
|
|
|
if res_compile:
|
|
res_compile(attrib, self)
|
|
|
|
def end(self, tag):
|
|
self._stack.pop()
|
|
|
|
def register_resource(self, type, name):
|
|
if type not in self.resource_types:
|
|
self.resource_types.append(type)
|
|
type_id = len(self.resource_types)
|
|
self.resources[type] = []
|
|
else:
|
|
type_id = self.resource_types.index(type) + 1
|
|
|
|
resources = self.resources[type]
|
|
if name in resources:
|
|
entry_id = resources.index(name)
|
|
else:
|
|
entry_id = len(resources)
|
|
resources.append(name)
|
|
|
|
id = (0x7f << 24) | (type_id << 16) | (entry_id)
|
|
return id
|
|
|
|
def dumps(self):
|
|
return self.root.SerializeToString()
|