fix(xml): extract API classes from aaptcompiler to :xml:resources-api

This commit is contained in:
Akash Yadav
2024-07-02 19:27:43 +05:30
parent 6fcb177f98
commit 1d68499189
96 changed files with 1900 additions and 1061 deletions
+1
View File
@@ -271,6 +271,7 @@
<module name="AndroidIDE.treeview" target="17" />
<module name="AndroidIDE.uidesigner" target="17" />
<module name="AndroidIDE.xml-inflater" target="17" />
<module name="AndroidIDE.xml.resources-api.test" target="11" />
<module name="ATS.android-tree-sitter" target="17" />
<module name="ATS.app" target="17" />
<module name="ATS.tree-sitter-aidl" target="17" />
@@ -19,7 +19,6 @@ package com.itsaky.androidide.projects.android
import com.android.SdkConstants
import com.android.aaptcompiler.AaptResourceType
import com.android.aaptcompiler.ResourceTable
import com.android.builder.model.v2.ide.LibraryType.ANDROID_LIBRARY
import com.android.builder.model.v2.ide.LibraryType.JAVA_LIBRARY
import com.android.builder.model.v2.ide.LibraryType.PROJECT
@@ -38,6 +37,7 @@ import com.itsaky.androidide.tooling.api.models.BasicAndroidVariantMetadata
import com.itsaky.androidide.tooling.api.models.GradleTask
import com.itsaky.androidide.tooling.api.util.findPackageName
import com.itsaky.androidide.utils.withStopWatch
import com.itsaky.androidide.xml.res.IResourceTable
import com.itsaky.androidide.xml.resources.ResourceTableRegistry
import com.itsaky.androidide.xml.versions.ApiVersions
import com.itsaky.androidide.xml.versions.ApiVersionsRegistry
@@ -314,7 +314,7 @@ open class AndroidModule( // Class must be open because BaseXMLTest mocks this..
}
/** Get the resource table for this module i.e. without resource tables for dependent modules. */
fun getResourceTable(): ResourceTable? {
fun getResourceTable(): IResourceTable? {
val namespace = this.namespace ?: return null
val resDirs = mainSourceSet?.sourceProvider?.resDirectories ?: return null
@@ -336,11 +336,11 @@ open class AndroidModule( // Class must be open because BaseXMLTest mocks this..
}
/**
* Get the [ResourceTable] instance for this module's compile SDK.
* Get the [IResourceTable] instance for this module's compile SDK.
*
* @return The [ApiVersions] for this module.
*/
fun getFrameworkResourceTable(): ResourceTable? {
fun getFrameworkResourceTable(): IResourceTable? {
val platformDir = getPlatformDir()
if (platformDir != null) {
return ResourceTableRegistry.getInstance().forPlatformDir(platformDir)
@@ -354,7 +354,7 @@ open class AndroidModule( // Class must be open because BaseXMLTest mocks this..
*
* @return The set of resource tables. Empty when project is not initalized.
*/
fun getSourceResourceTables(): Set<ResourceTable> {
fun getSourceResourceTables(): Set<IResourceTable> {
val set = mutableSetOf(getResourceTable() ?: return emptySet())
getCompileModuleProjects().filterIsInstance<AndroidModule>().forEach {
it.getResourceTable()?.also { table -> set.add(table) }
@@ -363,8 +363,8 @@ open class AndroidModule( // Class must be open because BaseXMLTest mocks this..
}
/** Get the resource tables for external dependencies (not local module project dependencies). */
fun getDependencyResourceTables(): Set<ResourceTable> {
return mutableSetOf<ResourceTable>().also {
fun getDependencyResourceTables(): Set<IResourceTable> {
return mutableSetOf<IResourceTable>().also {
var deps: Int
it.addAll(libraryMap.values.filter { library ->
library.type == ANDROID_LIBRARY && library.androidLibraryData!!.resFolder.exists() && library.findPackageName() != UNKNOWN_PACKAGE
@@ -390,7 +390,10 @@ open class AndroidModule( // Class must be open because BaseXMLTest mocks this..
*
* @param pck The package to look for.
*/
fun findResourceTableForPackage(pck: String, hasGroup: AaptResourceType? = null): ResourceTable? {
fun findResourceTableForPackage(
pck: String,
hasGroup: AaptResourceType? = null
): IResourceTable? {
return findAllResourceTableForPackage(pck, hasGroup).let {
if (it.isNotEmpty()) {
return it.first()
@@ -406,18 +409,18 @@ open class AndroidModule( // Class must be open because BaseXMLTest mocks this..
*/
fun findAllResourceTableForPackage(
pck: String, hasGroup: AaptResourceType? = null
): List<ResourceTable> {
): List<IResourceTable> {
if (pck == SdkConstants.ANDROID_PKG) {
return getFrameworkResourceTable()?.let { listOf(it) } ?: emptyList()
}
val tables: List<ResourceTable> = mutableListOf<ResourceTable>().apply {
val tables: List<IResourceTable> = mutableListOf<IResourceTable>().apply {
getResourceTable()?.let { add(it) }
addAll(getSourceResourceTables())
addAll(getDependencyResourceTables())
}
val result = mutableListOf<ResourceTable>()
val result = mutableListOf<IResourceTable>()
for (table in tables) {
val resPck = table.findPackage(pck) ?: continue
if (hasGroup == null) {
@@ -439,8 +442,8 @@ open class AndroidModule( // Class must be open because BaseXMLTest mocks this..
*
* @return The associated resource tables.
*/
fun getAllResourceTables(): Set<ResourceTable> {
return mutableSetOf<ResourceTable>().apply {
fun getAllResourceTables(): Set<IResourceTable> {
return mutableSetOf<IResourceTable>().apply {
getResourceTable()?.let { add(it) }
getFrameworkResourceTable()?.let { add(it) }
addAll(getSourceResourceTables())
@@ -449,7 +452,7 @@ open class AndroidModule( // Class must be open because BaseXMLTest mocks this..
}
/** Get the resource table for the attrs_manifest.xml file. */
fun getManifestAttrTable(): ResourceTable? {
fun getManifestAttrTable(): IResourceTable? {
val platform = getPlatformDir() ?: return null
return ResourceTableRegistry.getInstance().getManifestAttrTable(platform)
}
+1
View File
@@ -177,6 +177,7 @@ include(
":xml:aaptcompiler",
":xml:dom",
":xml:lsp",
":xml:resources-api",
":xml:utils",
)
+3 -2
View File
@@ -25,8 +25,9 @@ plugins {
dependencies {
implementation(libs.androidx.annotation)
implementation(libs.androidx.collection)
api(libs.androidx.annotation)
api(libs.androidx.collection)
implementation(libs.google.guava)
implementation(libs.google.gson)
@@ -60,6 +60,9 @@ import com.android.aaptcompiler.tryParseInt
import com.android.aaptcompiler.tryParseReference
import com.itsaky.androidide.inflater.drawable.DrawableParserFactory
import com.itsaky.androidide.inflater.utils.module
import com.itsaky.androidide.xml.res.IResourceEntry
import com.itsaky.androidide.xml.res.IResourceTable
import com.itsaky.androidide.xml.res.IResourceTablePackage
import org.slf4j.LoggerFactory
import java.io.File
import java.text.SimpleDateFormat
@@ -72,16 +75,16 @@ private const val DEFAULT_STRING_VALUE = "AndroidIDE"
private val stringResolver =
fun(it: Value?): String? {
return when (it) {
is BasicString -> it.ref.value()
is RawString -> it.value.value()
is StyledString -> it.ref.value()
is com.android.aaptcompiler.BasicString -> it.ref.value()
is com.android.aaptcompiler.RawString -> it.value.value()
is com.android.aaptcompiler.StyledString -> it.ref.value()
else -> null
}
}
private val intResolver =
fun(it: Value?): Int? {
return if (it is BinaryPrimitive) {
return if (it is com.android.aaptcompiler.BinaryPrimitive) {
it.resValue.data
} else null
}
@@ -89,14 +92,14 @@ private val intResolver =
val colorResolver: (Value?) -> Int? =
fun(it): Int? {
// TODO(itsaky) : Implement color state list parser
if (it is BinaryPrimitive) {
if (it is com.android.aaptcompiler.BinaryPrimitive) {
return it.resValue.data
}
return null
}
inline fun <reified T> ((Value?) -> T?).arrayResolver(value: Value?): Array<T>? {
return if (value is ArrayResource) {
return if (value is com.android.aaptcompiler.ArrayResource) {
Array(value.elements.size) { invoke(value.elements[it]) ?: return null }
} else emptyArray()
}
@@ -151,7 +154,7 @@ fun parseInteger(value: String, def: Int = 0): Int {
return def
}
if (value.isDigitsOnly()) {
tryParseInt(value)?.resValue?.apply {
com.android.aaptcompiler.tryParseInt(value)?.resValue?.apply {
return data
}
}
@@ -169,14 +172,14 @@ fun parseBoolean(value: String, def: Boolean = false): Boolean {
return def
}
tryParseBool(value)?.resValue?.apply {
com.android.aaptcompiler.tryParseBool(value)?.resValue?.apply {
return data == -1
}
if (value[0] == '@') {
val resolver: (Value?) -> Boolean? =
fun(resValue): Boolean {
return if (resValue is BinaryPrimitive) {
return if (resValue is com.android.aaptcompiler.BinaryPrimitive) {
resValue.resValue.data == -1
} else def
}
@@ -190,7 +193,7 @@ fun parseBoolean(value: String, def: Boolean = false): Boolean {
fun parseDrawable(context: Context, value: String, def: Drawable = unknownDrawable()): Drawable {
val drawableResolver: (Value?) -> Drawable? =
fun(it): Drawable? {
if (it is FileReference) {
if (it is com.android.aaptcompiler.FileReference) {
val file = File(it.path.value())
if (!file.exists() || file.extension != EXT_XML) {
return null
@@ -229,7 +232,7 @@ fun parseLayoutReference(value: String): File? {
val layoutResolver: (Value?) -> File? =
fun(it): File? {
return if (it is FileReference) {
return if (it is com.android.aaptcompiler.FileReference) {
File(it.source.path)
} else null
}
@@ -310,7 +313,7 @@ fun parseDimension(
return complexToDimension(data, displayMetrics)
} else if (c == '@') {
val resolver: (Value?) -> Float? = {
if (it is BinaryPrimitive) {
if (it is com.android.aaptcompiler.BinaryPrimitive) {
complexToDimension(it.resValue.data, displayMetrics)
} else null
}
@@ -357,8 +360,8 @@ fun parseGravity(value: String, def: Int = defaultGravity()): Int {
return parseFlag(attr = attr, value = value, def = def)
}
fun parseFlag(attr: AttributeResource, value: String, def: Int = -1): Int {
return tryParseFlagSymbol(attr, value)?.resValue?.data ?: def
fun parseFlag(attr: com.android.aaptcompiler.AttributeResource, value: String, def: Int = -1): Int {
return com.android.aaptcompiler.tryParseFlagSymbol(attr, value)?.resValue?.data ?: def
}
fun defaultGravity(): Int {
@@ -441,7 +444,7 @@ fun <T> resolveQualifiedResourceReference(
}
fun <T> resolveResourceReference(
table: ResourceTable,
table: IResourceTable,
type: AaptResourceType,
pck: String,
name: String,
@@ -449,7 +452,7 @@ fun <T> resolveResourceReference(
resolver: (Value?) -> T?
): T {
val result =
table.findResource(ResourceName(pck = pck, type = type, entry = name))
table.findResource(com.android.aaptcompiler.ResourceName(pck = pck, type = type, entry = name))
?: run { throw IllegalArgumentException("$type resource '$name' not found") }
return resolveResourceReference(
table,
@@ -463,9 +466,9 @@ fun <T> resolveResourceReference(
}
fun <T> resolveResourceReference(
table: ResourceTable,
pck: ResourceTablePackage,
entry: ResourceEntry,
table: IResourceTable,
pck: IResourceTablePackage,
entry: IResourceEntry,
type: AaptResourceType,
name: String,
def: T,
@@ -21,21 +21,21 @@ import com.android.SdkConstants
import com.android.aaptcompiler.AaptResourceType
import com.android.aaptcompiler.AttributeResource
import com.android.aaptcompiler.ConfigDescription
import com.android.aaptcompiler.ResourceEntry
import com.android.aaptcompiler.ResourceGroup
import com.android.aaptcompiler.ResourceName
import com.android.aaptcompiler.ResourceTable
import com.android.aaptcompiler.ResourceTablePackage
import com.itsaky.androidide.inflater.utils.module
import com.itsaky.androidide.xml.res.IResourceEntry
import com.itsaky.androidide.xml.res.IResourceGroup
import com.itsaky.androidide.xml.res.IResourceTable
import com.itsaky.androidide.xml.res.IResourceTablePackage
import org.slf4j.LoggerFactory
private val log = LoggerFactory.getLogger("ParseLookupUtils")
internal data class LookupResult(
val table: ResourceTable,
val group: ResourceGroup,
val pack: ResourceTablePackage,
val entry: ResourceEntry
val table: IResourceTable,
val group: IResourceGroup,
val pack: IResourceTablePackage,
val entry: IResourceEntry
)
internal fun lookupUnqualifedResource(
@@ -57,10 +57,10 @@ internal fun lookupUnqualifedResource(
}
internal fun findUnqualifiedResourceEntry(type: AaptResourceType, name: String): LookupResult? {
var resTable: ResourceTable? = null
var resGrp: ResourceGroup? = null
var resPck: ResourceTablePackage? = null
var resEntry: ResourceEntry? = null
var resTable: IResourceTable? = null
var resGrp: IResourceGroup? = null
var resPck: IResourceTablePackage? = null
var resEntry: IResourceEntry? = null
for (t in module.getAllResourceTables()) {
val entries =
t.packages.mapNotNull {
@@ -93,7 +93,7 @@ internal fun findQualifedResourceEntry(
pack: String,
type: AaptResourceType,
name: String
): ResourceEntry? {
): IResourceEntry? {
return module
.findResourceTableForPackage(pack, type)
?.findResource(ResourceName(pack, type, name))
@@ -18,10 +18,7 @@
package com.itsaky.androidide.inflater.utils
import com.android.aaptcompiler.AaptResourceType
import com.android.aaptcompiler.BlameLogger
import com.android.aaptcompiler.ResourceFile
import com.android.aaptcompiler.ResourceFile.Type.ProtoXml
import com.android.aaptcompiler.ResourceName
import com.android.aaptcompiler.XmlProcessor
import com.android.aaptcompiler.extractPathData
import com.itsaky.androidide.aapt.logging.IDELogger
@@ -86,7 +83,7 @@ fun newAttribute(
* @return The pair of [XmlProcessor] (processed) instance and the [AndroidModule] instance for the
* given file.
*/
fun processXmlFile(file: File, expectedType: AaptResourceType): Pair<XmlProcessor, AndroidModule> {
fun processXmlFile(file: File, expectedType: com.android.aaptcompiler.AaptResourceType): Pair<XmlProcessor, AndroidModule> {
val pathData = extractPathData(file)
if (pathData.type != expectedType) {
throw InflateException("File is not a layout file.")
@@ -100,14 +97,14 @@ fun processXmlFile(file: File, expectedType: AaptResourceType): Pair<XmlProcesso
IProjectManager.getInstance().getWorkspace()?.findModuleForFile(file, false) as? AndroidModule
?: throw InflateException("Cannot find module for given file. Is the project initialized?")
val resFile =
ResourceFile(
ResourceName(module.namespace, pathData.type!!, pathData.name),
com.android.aaptcompiler.ResourceFile(
com.android.aaptcompiler.ResourceName(module.namespace, pathData.type!!, pathData.name),
pathData.config,
pathData.source,
ProtoXml
)
val processor = XmlProcessor(pathData.source, BlameLogger(IDELogger))
val processor = XmlProcessor(pathData.source, com.android.aaptcompiler.BlameLogger(IDELogger))
processor.process(resFile, file.inputStream())
return processor to module
}
+9
View File
@@ -0,0 +1,9 @@
# aaptcompiler
This module has been adapted
from [Android Studio sources](https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-main:build-system/aaptcompiler/;l=1?q=aaptcompiler&sq=).
## Modifications
Some of the classes/files have been moved to the `:xml:resources-api` module. Also, interfaces have been introduced
in order to provide a more generic API for accessing module resources.
+2 -19
View File
@@ -22,11 +22,8 @@ import com.itsaky.androidide.build.config.BuildConfig
plugins {
id("com.android.library")
id("kotlin-android")
id("com.google.protobuf")
}
android {
namespace = "${BuildConfig.packageName}.aaptcompiler"
@@ -37,22 +34,6 @@ android {
}
}
protobuf {
protoc {
artifact = "com.google.protobuf:protoc:4.27.0"
}
generateProtoTasks {
all().forEach {
it.builtins {
id("java") {
option("lite")
}
}
}
}
}
dependencies {
implementation(libs.common.kotlin)
implementation(libs.androidx.collection)
@@ -61,6 +42,8 @@ dependencies {
implementation(projects.logging.logger)
implementation(projects.utilities.shared)
api(projects.xml.resourcesApi)
api(libs.aapt2.annotations)
api(libs.aapt2.common)
api(libs.google.protobuf)
@@ -1,94 +0,0 @@
package com.android.aaptcompiler
import com.android.aaptcompiler.android.ResTableConfig
private const val ANIM_TAG = "anim"
private const val ANIMATOR_TAG = "animator"
private const val ARRAY_TAG = "array"
private const val ATTR_TAG = "attr"
private const val ATTR_PRIVATE_TAG = "^attr-private"
private const val BOOL_TAG = "bool"
private const val COLOR_TAG = "color"
private const val CONFIG_VARYING_TAG = "configVarying"
private const val DIMEN_TAG = "dimen"
private const val DRAWABLE_TAG = "drawable"
private const val FONT_TAG = "font"
private const val FRACTION_TAG = "fraction"
private const val ID_TAG = "id"
private const val INTEGER_TAG = "integer"
private const val INTERPOLATOR_TAG = "interpolator"
private const val LAYOUT_TAG = "layout"
private const val MACRO_TAG = "macro"
private const val MENU_TAG = "menu"
private const val MIPMAP_TAG = "mipmap"
private const val NAVIGATION_TAG = "navigation"
private const val PLURALS_TAG = "plurals"
private const val RAW_TAG = "raw"
private const val STRING_TAG = "string"
private const val STYLE_TAG = "style"
private const val STYLEABLE_TAG = "styleable"
private const val TRANSITION_TAG = "transition"
private const val XML_TAG = "xml"
fun resourceTypeFromTag(tag: String) =
when(tag) {
ANIM_TAG -> AaptResourceType.ANIM
ANIMATOR_TAG -> AaptResourceType.ANIMATOR
ARRAY_TAG -> AaptResourceType.ARRAY
ATTR_TAG -> AaptResourceType.ATTR
ATTR_PRIVATE_TAG -> AaptResourceType.ATTR_PRIVATE
BOOL_TAG -> AaptResourceType.BOOL
COLOR_TAG -> AaptResourceType.COLOR
CONFIG_VARYING_TAG -> AaptResourceType.CONFIG_VARYING
DIMEN_TAG -> AaptResourceType.DIMEN
DRAWABLE_TAG -> AaptResourceType.DRAWABLE
FONT_TAG -> AaptResourceType.FONT
FRACTION_TAG -> AaptResourceType.FRACTION
ID_TAG -> AaptResourceType.ID
INTEGER_TAG -> AaptResourceType.INTEGER
INTERPOLATOR_TAG -> AaptResourceType.INTERPOLATOR
LAYOUT_TAG -> AaptResourceType.LAYOUT
MACRO_TAG -> AaptResourceType.MACRO
MENU_TAG -> AaptResourceType.MENU
MIPMAP_TAG -> AaptResourceType.MIPMAP
NAVIGATION_TAG -> AaptResourceType.NAVIGATION
PLURALS_TAG -> AaptResourceType.PLURALS
RAW_TAG -> AaptResourceType.RAW
STRING_TAG -> AaptResourceType.STRING
STYLE_TAG -> AaptResourceType.STYLE
STYLEABLE_TAG -> AaptResourceType.STYLEABLE
TRANSITION_TAG -> AaptResourceType.TRANSITION
XML_TAG -> AaptResourceType.XML
else -> null
}
enum class AaptResourceType(val tagName: String) {
ANIM(ANIM_TAG),
ANIMATOR(ANIMATOR_TAG),
ARRAY(ARRAY_TAG),
ATTR(ATTR_TAG),
ATTR_PRIVATE(ATTR_PRIVATE_TAG),
BOOL(BOOL_TAG),
COLOR(COLOR_TAG),
CONFIG_VARYING(CONFIG_VARYING_TAG),
DIMEN(DIMEN_TAG),
DRAWABLE(DRAWABLE_TAG),
FONT(FONT_TAG),
FRACTION(FRACTION_TAG),
ID(ID_TAG),
INTEGER(INTEGER_TAG),
INTERPOLATOR(INTERPOLATOR_TAG),
LAYOUT(LAYOUT_TAG),
MACRO(MACRO_TAG),
MENU(MENU_TAG),
MIPMAP(MIPMAP_TAG),
NAVIGATION(NAVIGATION_TAG),
PLURALS(PLURALS_TAG),
RAW(RAW_TAG),
STRING(STRING_TAG),
STYLE(STYLE_TAG),
STYLEABLE(STYLEABLE_TAG),
TRANSITION(TRANSITION_TAG),
UNKNOWN(""),
XML(XML_TAG)
}
@@ -1,240 +0,0 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.aaptcompiler
import com.android.aaptcompiler.android.ResTableConfig
class ConfigDescription(base: ResTableConfig = ResTableConfig()): ResTableConfig(base) {
override fun equals(other: Any?): Boolean {
if (other is ConfigDescription) {
return compareTo(other) == 0
}
return false
}
private fun compatible(first: Int, second: Int) = first == 0 || second == 0 || first == second
}
const val WILDCARD_NAME: String = "any"
fun parse(config: String): ConfigDescription {
val parts = config.split('-').map { it.lowercase()}
var index = 0
val configDescription = ConfigDescription()
val locale = LocaleValue()
if (parts.isEmpty() || config.isEmpty()) {
return applyVersionForCompatibility(configDescription)
}
if (parseMcc(parts[index], configDescription)) {
index++
if (index == parts.size) {
return applyVersionForCompatibility(configDescription)
}
}
if (parseMnc(parts[index], configDescription)) {
index++
if (index == parts.size) {
return applyVersionForCompatibility(configDescription)
}
}
// Locale spans a few '-' separators, so we let it control the index
index += locale.initFromParts(parts, index)
locale.writeTo(configDescription)
if (index == parts.size) {
return configDescription
}
if (parseGrammaticalInflection(parts[index], configDescription)) {
index++
if (index == parts.size) {
return applyVersionForCompatibility(configDescription)
}
}
if(parseLayoutDirection(parts[index], configDescription)) {
index++
if (index == parts.size) {
return applyVersionForCompatibility(configDescription)
}
}
if (parseSmallestScreenWidthDp(parts[index], configDescription)) {
index++
if (index == parts.size) {
return applyVersionForCompatibility(configDescription)
}
}
if (parseScreenWidthDp(parts[index], configDescription)) {
index++
if (index == parts.size) {
return applyVersionForCompatibility(configDescription)
}
}
if (parseScreenHeightDp(parts[index], configDescription)) {
index++
if (index == parts.size) {
return applyVersionForCompatibility(configDescription)
}
}
if (parseScreenLayoutSize(parts[index], configDescription)) {
index++
if (index == parts.size) {
return applyVersionForCompatibility(configDescription)
}
}
if (parseScreenLayoutLong(parts[index], configDescription)) {
index++
if (index == parts.size) {
return applyVersionForCompatibility(configDescription)
}
}
if (parseScreenRound(parts[index], configDescription)) {
index++
if (index == parts.size) {
return applyVersionForCompatibility(configDescription)
}
}
if (parseWideColorGamut(parts[index], configDescription)) {
index++
if (index == parts.size) {
return applyVersionForCompatibility(configDescription)
}
}
if (parseHdr(parts[index], configDescription)) {
index++
if (index == parts.size) {
return applyVersionForCompatibility(configDescription)
}
}
if (parseOrientation(parts[index], configDescription)) {
index++
if (index == parts.size) {
return applyVersionForCompatibility(configDescription)
}
}
if (parseUiModeType(parts[index], configDescription)) {
index++
if (index == parts.size) {
return applyVersionForCompatibility(configDescription)
}
}
if (parseUiModeNight(parts[index], configDescription)) {
index++
if (index == parts.size) {
return applyVersionForCompatibility(configDescription)
}
}
if (parseDensity(parts[index], configDescription)) {
index++
if (index == parts.size) {
return applyVersionForCompatibility(configDescription)
}
}
if (parseTouchscreen(parts[index], configDescription)) {
index++
if (index == parts.size) {
return applyVersionForCompatibility(configDescription)
}
}
if (parseKeysHidden(parts[index], configDescription)) {
index++
if (index == parts.size) {
return applyVersionForCompatibility(configDescription)
}
}
if (parseKeyboard(parts[index], configDescription)) {
index++
if (index == parts.size) {
return applyVersionForCompatibility(configDescription)
}
}
if (parseNavHidden(parts[index], configDescription)) {
index++
if (index == parts.size) {
return applyVersionForCompatibility(configDescription)
}
}
if (parseNavigation(parts[index], configDescription)) {
index++
if (index == parts.size) {
return applyVersionForCompatibility(configDescription)
}
}
if (parseScreenSize(parts[index], configDescription)) {
index++
if (index == parts.size) {
return applyVersionForCompatibility(configDescription)
}
}
if (parseVersion(parts[index], configDescription)) {
index++
if (index == parts.size) {
return applyVersionForCompatibility(configDescription)
}
}
throw error("Unrecognized part '${parts[index]}' in configuration $config")
}
private fun applyVersionForCompatibility(config: ConfigDescription): ConfigDescription {
val minSdk = when {
config.uiModeType() == ResTableConfig.UI_MODE.TYPE_VR_HEADSET ||
config.wideColorGamut() != ResTableConfig.COLOR_MODE.WIDE_GAMUT_ANY ||
config.hdr() != ResTableConfig.COLOR_MODE.HDR_ANY -> SDKConstants.SDK_O
config.layoutRound() != 0.toByte() -> SDKConstants.SDK_MARSHMALLOW
config.density == ResTableConfig.DENSITY.ANY -> SDKConstants.SDK_LOLLIPOP
config.smallestScreenWidthDp != 0 ||
config.screenWidthDp != 0 ||
config.screenHeightDp != 0-> SDKConstants.SDK_HONEYCOMB_MR2
config.uiModeType() != ResTableConfig.UI_MODE.TYPE_ANY ||
config.uiModeNight() != ResTableConfig.UI_MODE.NIGHT_ANY -> SDKConstants.SDK_FROYO
config.layoutSize() != ResTableConfig.SCREEN_LAYOUT.SIZE_ANY ||
config.layoutLong() != ResTableConfig.SCREEN_LAYOUT.SCREENLONG_ANY ||
config.density != ResTableConfig.DENSITY.DEFAULT -> SDKConstants.SDK_DONUT
config.grammaticalInflection != ResTableConfig.GRAMMATICAL_GENDER.ANY -> SDKConstants.SDK_U
else -> 0
}
if (minSdk > config.sdkVersion) {
config.sdkVersion = minSdk
}
return config
}
@@ -46,7 +46,8 @@ internal fun mergeSpans(string: StyledString): List<UnifiedSpan> {
}
internal fun pseudolocalizeStyledString(
original: StyledString, method: Pseudolocalizer.Method, pool: StringPool): StyledString {
original: StyledString, method: Pseudolocalizer.Method, pool: StringPool
): StyledString {
val localizer = Pseudolocalizer(method)
@@ -136,7 +137,8 @@ internal fun pseudolocalizeStyledString(
}
internal fun pseudolocalizeBasicString(
original: BasicString, method: Pseudolocalizer.Method, pool: StringPool): BasicString {
original: BasicString, method: Pseudolocalizer.Method, pool: StringPool
): BasicString {
try {
val localizer = Pseudolocalizer(method)
@@ -170,7 +172,8 @@ internal fun pseudolocalizeBasicString(
}
internal fun pseudolocalizePlural(
original: Plural, method: Pseudolocalizer.Method, pool: StringPool): Plural {
original: Plural, method: Pseudolocalizer.Method, pool: StringPool
): Plural {
val localizedPlural = Plural()
@@ -272,6 +272,7 @@ class PseudoMethodAccent: PseudoMethodImpl() {
var currentChar = source[currentIndex]
val htmlPartial = StringBuilder()
@Suppress("KotlinConstantConditions")
htmlTag@ while (currentIndex < size) {
if (currentChar == '&') {
@@ -17,6 +17,8 @@
package com.android.aaptcompiler
import com.android.aaptcompiler.ResourceFile.Type.ProtoXml
import com.android.aaptcompiler.ResourceFile.Type.Unknown
import com.android.aaptcompiler.proto.serializeTableToPb
import com.itsaky.androidide.layoutlib.resources.ResourceType
import com.itsaky.androidide.layoutlib.resources.ResourceVisibility
@@ -106,11 +108,12 @@ fun canCompileResourceInJvm(file: File, requirePngCrunching: Boolean): Boolean {
* See [canCompileResourceInJvm] to see what is supported.
*/
fun compileResource(
file: File, outputDirectory: File, options: ResourceCompilerOptions, logger: BlameLogger) {
file: File, outputDirectory: File, options: ResourceCompilerOptions, logger: BlameLogger
) {
// Skip hidden files.
if (file.isHidden) {
logger?.warning("Omitting file ${file.absolutePath} because it is hidden.")
logger.warning("Omitting file ${file.absolutePath} because it is hidden.")
return
}
@@ -122,7 +125,7 @@ fun compileResource(
try {
compileFunction(pathData, outputDirectory, options, logger)
} catch (e: Exception) {
logger?.info("Failed to compile file", blameSource(pathData.source))
logger.info("Failed to compile file", blameSource(pathData.source))
val message =
"Resource compilation failed (${e.message}. Cause: ${e.cause}). " +
"Check logs for more details."
@@ -141,7 +144,7 @@ private fun getCompileMethod(pathData: ResourcePathData, logger: BlameLogger):
if (type == null) {
val errorMsg = "Invalid resource type '${pathData.resourceDirectory}'" +
" for file ${pathData.file.absolutePath}"
logger?.warning(errorMsg)
logger.warning(errorMsg)
error(errorMsg)
}
if (type != ResourceType.RAW) {
@@ -170,9 +173,10 @@ private fun compileTable(
pathData: ResourcePathData,
outputDirectory: File,
options: ResourceCompilerOptions,
logger: BlameLogger) {
logger: BlameLogger
) {
val outputFile = File(outputDirectory, pathData.getIntermediateContainerFilename())
logger?.info("Compiling XML table ${pathData.file.absolutePath} to $outputFile")
logger.info("Compiling XML table ${pathData.file.absolutePath} to $outputFile")
val table = ResourceTable(logger = logger)
@@ -221,14 +225,14 @@ private fun compileTable(
val javaType = if (group.type == AaptResourceType.STYLEABLE) "int[]" else "int"
group.entries.forEach { entry ->
val visibility = getVisibility(entry.value.values, group.type)
builder.appendln("${visibility.getName()} $javaType ${group.type.tagName} ${entry.key}")
builder.appendLine("${visibility.getName()} $javaType ${group.type.tagName} ${entry.key}")
if (group.type == AaptResourceType.STYLEABLE) {
// We also need to write down styleable children (entries)
group.getStyleable(entry).entries.forEach {
val childPackage =
if (!it.name.pck.isNullOrEmpty()) "_${it.name.pck}" else ""
// styleables and their children are always public
builder.appendln("public int styleable ${entry.key}${childPackage}_${it.name.entry}")
builder.appendLine("public int styleable ${entry.key}${childPackage}_${it.name.entry}")
}
}
}
@@ -275,7 +279,7 @@ private fun compileFile(
ResourceName("", resourceTypeFromTag(pathData.resourceDirectory)!!, pathData.name),
pathData.config,
pathData.source,
ResourceFile.Type.Unknown
Unknown
)
val container = Container(outputFile.outputStream(), 1)
@@ -317,7 +321,7 @@ private fun compileXml(
ResourceName("", pathData.type!!, pathData.name),
pathData.config,
pathData.source,
ResourceFile.Type.ProtoXml
ProtoXml
)
pathData.file.inputStream().use {
@@ -341,9 +345,9 @@ private fun compileXml(
if (options.partialRFile != null) {
val builder = StringBuilder()
builder.appendln("default int ${pathData.type.tagName} ${pathData.name}")
builder.appendLine("default int ${pathData.type!!.tagName} ${pathData.name}")
fileToProcess.exportedSymbols.forEach { id ->
builder.appendln("default int id ${id.name.entry}")
builder.appendLine("default int id ${id.name.entry}")
}
FileUtils.writeToFile(options.partialRFile, builder.toString())
}
@@ -3,6 +3,11 @@ package com.android.aaptcompiler
import androidx.collection.mutableIntObjectMapOf
import com.android.aapt.Resources
import com.itsaky.androidide.layoutlib.resources.ResourceVisibility
import com.itsaky.androidide.xml.res.IResourceEntry
import com.itsaky.androidide.xml.res.IResourceGroup
import com.itsaky.androidide.xml.res.IResourceTable
import com.itsaky.androidide.xml.res.IResourceTablePackage
import com.itsaky.androidide.xml.res.ISearchResult
import java.io.File
import java.util.SortedMap
import java.util.TreeMap
@@ -16,7 +21,8 @@ import java.util.TreeMap
* names validated. While [addResourceMangled] and [addFileReferenceMangled] are used to add
* resources from libraries and are not validated.
*/
class ResourceTable(val validateResources: Boolean = false, val logger: BlameLogger? = null) {
class ResourceTable(val validateResources: Boolean = false, val logger: BlameLogger? = null) :
IResourceTable {
/**
* The string pool used by this resource table. Values that reference strings must use
* this pool to create their strings.
@@ -24,7 +30,7 @@ class ResourceTable(val validateResources: Boolean = false, val logger: BlameLog
val stringPool = StringPool()
/** The list of packages in this table. */
val packages = mutableListOf<ResourceTablePackage>()
override val packages = mutableListOf<ResourceTablePackage>()
/**
* Set of dynamic packages that this table may reference. Their package names get encoded into the
@@ -39,7 +45,10 @@ class ResourceTable(val validateResources: Boolean = false, val logger: BlameLog
}
data class SearchResult(
val tablePackage: ResourceTablePackage, val group: ResourceGroup, val entry: ResourceEntry)
override val tablePackage: ResourceTablePackage,
override val group: ResourceGroup,
override val entry: ResourceEntry
) : ISearchResult
/**
* Adds a resource to the table with the given value.
@@ -54,9 +63,11 @@ class ResourceTable(val validateResources: Boolean = false, val logger: BlameLog
* the table.
*/
fun addResource(
name: ResourceName, config: ConfigDescription, product: String, value: Value): Boolean =
name: ResourceName, config: ConfigDescription, product: String, value: Value
): Boolean =
addResourceImpl(
name, 0, config, product, value, ::resourceNameValidator, ::resolveValueCollision)
name, 0, config, product, value, ::resourceNameValidator, ::resolveValueCollision
)
/**
@@ -77,9 +88,11 @@ class ResourceTable(val validateResources: Boolean = false, val logger: BlameLog
id: Int,
config: ConfigDescription,
product: String,
value: Value): Boolean =
value: Value
): Boolean =
addResourceImpl(
name, id, config, product, value, ::resourceNameValidator, ::resolveValueCollision)
name, id, config, product, value, ::resourceNameValidator, ::resolveValueCollision
)
/**
* Adds a file reference resource to the table with the given file path.
@@ -94,9 +107,11 @@ class ResourceTable(val validateResources: Boolean = false, val logger: BlameLog
* the table.
*/
fun addFileReference(
name: ResourceName,config: ConfigDescription, source: Source, path: String): Boolean =
name: ResourceName, config: ConfigDescription, source: Source, path: String
): Boolean =
addFileReferenceImpl(
name, config, source, path, null, ::resourceNameValidator)
name, config, source, path, null, ::resourceNameValidator
)
/**
* Same as [addFileReference], but doesn't verify the name of the file reference resource. This is
@@ -118,7 +133,8 @@ class ResourceTable(val validateResources: Boolean = false, val logger: BlameLog
config: ConfigDescription,
source: Source,
path: String,
file: File): Boolean =
file: File
): Boolean =
addFileReferenceImpl(name, config, source, path, file, ::skipNameValidator)
/**
@@ -134,9 +150,11 @@ class ResourceTable(val validateResources: Boolean = false, val logger: BlameLog
* there exists a conflict with a resource already in the table.
*/
fun addResourceMangled(
name: ResourceName, config: ConfigDescription, product: String, value: Value): Boolean =
name: ResourceName, config: ConfigDescription, product: String, value: Value
): Boolean =
addResourceImpl(
name, 0, config, product, value, ::skipNameValidator, ::resolveValueCollision)
name, 0, config, product, value, ::skipNameValidator, ::resolveValueCollision
)
/**
* Same as [addResourceWithId], but doesn't verify the name of the resource. This is used when
@@ -157,9 +175,11 @@ class ResourceTable(val validateResources: Boolean = false, val logger: BlameLog
id: Int,
config: ConfigDescription,
product: String,
value: Value) =
value: Value
) =
addResourceImpl(
name, id, config, product, value, ::skipNameValidator, ::resolveValueCollision)
name, id, config, product, value, ::skipNameValidator, ::resolveValueCollision
)
/**
* Sets the resource with the given name to the set visibility. If this resource has no value in
@@ -194,7 +214,7 @@ class ResourceTable(val validateResources: Boolean = false, val logger: BlameLog
fun setOverlayableMangled(name: ResourceName, overlayable: OverlayableItem) =
setOverlayableImpl(name, overlayable, ::skipNameValidator)
fun findResource(name: ResourceName): SearchResult? {
override fun findResource(name: ResourceName): SearchResult? {
val tablePackage = findPackage(name.pck!!)
tablePackage ?: return null
@@ -216,7 +236,7 @@ class ResourceTable(val validateResources: Boolean = false, val logger: BlameLog
* @return the [ResourceTablePackage] with the requested name or {@code null} if that package
* does not exist in the table.
*/
fun findPackage(name: String): ResourceTablePackage? {
override fun findPackage(name: String): ResourceTablePackage? {
return packages.find { it.name == name }
}
@@ -233,6 +253,7 @@ class ResourceTable(val validateResources: Boolean = false, val logger: BlameLog
tablePackage.id = id
return tablePackage
}
tablePackage.id != id -> return null
}
}
@@ -263,6 +284,7 @@ class ResourceTable(val validateResources: Boolean = false, val logger: BlameLog
packages.add(newPackage)
newPackage
}
else -> tablePackage
}
}
@@ -286,13 +308,15 @@ class ResourceTable(val validateResources: Boolean = false, val logger: BlameLog
}
private fun validateName(
nameValidator: (String) -> String, name: ResourceName, source: Source): Boolean {
nameValidator: (String) -> String, name: ResourceName, source: Source
): Boolean {
val badCodePoint = nameValidator.invoke(name.entry!!)
if (badCodePoint.isNotEmpty()) {
logError(
blameSource(source),
"Resource '$name' has invalid entry name '${name.entry}'. " +
"Invalid character '$badCodePoint'.")
"Invalid character '$badCodePoint'."
)
return false
}
return true
@@ -305,7 +329,8 @@ class ResourceTable(val validateResources: Boolean = false, val logger: BlameLog
product: String,
value: Value,
nameValidator: (String) -> String,
conflictResolver: (Value, Value) -> CollisionResult): Boolean {
conflictResolver: (Value, Value) -> CollisionResult
): Boolean {
val source = value.source
@@ -319,7 +344,8 @@ class ResourceTable(val validateResources: Boolean = false, val logger: BlameLog
logError(
blameSource(value.source),
"Failed to add resource '$name' with ID ${id.toString(16)} because " +
"package '${tablePackage.name}' already has ID ${packageId.toString(16)}.")
"package '${tablePackage.name}' already has ID ${packageId.toString(16)}."
)
return false
}
@@ -333,7 +359,8 @@ class ResourceTable(val validateResources: Boolean = false, val logger: BlameLog
logError(
blameSource(value.source),
"Failed to add resource '$name' with ID ${id.toString(16)} because type " +
"'${resourceGroup.type.tagName}' already has ID ${groupId.toString(16)}.")
"'${resourceGroup.type.tagName}' already has ID ${groupId.toString(16)}."
)
return false
}
@@ -344,7 +371,8 @@ class ResourceTable(val validateResources: Boolean = false, val logger: BlameLog
logError(
blameSource(value.source),
"Failed to add resource '$name' with ID ${id.toString(16)}, because resource already" +
" has ID ${resourceIdFromParts(packageId!!, groupId!!, entryId).toString(16)}.")
" has ID ${resourceIdFromParts(packageId!!, groupId!!, entryId).toString(16)}."
)
return false
}
@@ -362,9 +390,11 @@ class ResourceTable(val validateResources: Boolean = false, val logger: BlameLog
logError(
blameSource(value.source),
"Duplicate value for resource '$name' with config '$config' and product '$product'. " +
"Resource was previously defined here: $previousSource.")
"Resource was previously defined here: $previousSource."
)
return false
}
CollisionResult.KEEP_ORIGINAL -> {}
}
}
@@ -384,19 +414,22 @@ class ResourceTable(val validateResources: Boolean = false, val logger: BlameLog
source: Source,
path: String,
file: File?,
nameValidator: (String) -> String): Boolean {
nameValidator: (String) -> String
): Boolean {
val fileReference = FileReference(stringPool.makeRef(path))
fileReference.source = source
fileReference.file = file
return addResourceImpl(
name, 0, config, "", fileReference, nameValidator, ::resolveValueCollision)
name, 0, config, "", fileReference, nameValidator, ::resolveValueCollision
)
}
private fun setVisibilityImpl(
name: ResourceName,
visibility: Visibility,
id: Int,
nameValidator: (String) -> String) : Boolean {
nameValidator: (String) -> String
): Boolean {
val source = visibility.source
if (!validateName(nameValidator, name, source)) {
return false
@@ -408,7 +441,8 @@ class ResourceTable(val validateResources: Boolean = false, val logger: BlameLog
logError(
blameSource(source),
"Failed to add resource '$name' with ID ${id.toString(16)} because package " +
"'${tablePackage.name}' already has ID ${packageId.toString(16)}.")
"'${tablePackage.name}' already has ID ${packageId.toString(16)}."
)
return false
}
@@ -422,7 +456,8 @@ class ResourceTable(val validateResources: Boolean = false, val logger: BlameLog
logError(
blameSource(source),
"Failed to add resource '$name' with ID ${id.toString(16)} because type " +
"'${resourceGroup.type.tagName}' already has ID ${groupId.toString(16)}.")
"'${resourceGroup.type.tagName}' already has ID ${groupId.toString(16)}."
)
return false
}
@@ -433,7 +468,8 @@ class ResourceTable(val validateResources: Boolean = false, val logger: BlameLog
logError(
blameSource(source),
"Failed to add resource '$name' with ID ${id.toString(16)}, because resource already " +
"has ID ${resourceIdFromParts(packageId!!, groupId!!, entryId).toString(16)}.")
"has ID ${resourceIdFromParts(packageId!!, groupId!!, entryId).toString(16)}."
)
return false
}
@@ -453,22 +489,27 @@ class ResourceTable(val validateResources: Boolean = false, val logger: BlameLog
resourceEntry.visibility.level != ResourceVisibility.UNDEFINED -> {
// We can't undefine a symbol. Ignore
}
visibility.level == ResourceVisibility.PRIVATE &&
resourceEntry.visibility.level == ResourceVisibility.PUBLIC -> {
logError(
blameSource(source),
"Failed to add resource '$name' as private (java-symbol) because it was " +
"previously defined as public.")
"previously defined as public."
)
return false
}
visibility.level == ResourceVisibility.PUBLIC &&
resourceEntry.visibility.level == ResourceVisibility.PRIVATE -> {
logError(
blameSource(source),
"Failed to add resource '$name' as public because it was previously defined as " +
"private (java-symbol).")
"private (java-symbol)."
)
return false
}
else -> {
// This symbol definition takes precedence.
resourceEntry.visibility = visibility
@@ -479,7 +520,8 @@ class ResourceTable(val validateResources: Boolean = false, val logger: BlameLog
}
private fun setAllowNewImpl(
name: ResourceName, allowNew: AllowNew, nameValidator: (String) -> String): Boolean {
name: ResourceName, allowNew: AllowNew, nameValidator: (String) -> String
): Boolean {
if (!validateName(nameValidator, name, allowNew.source)) {
return false
}
@@ -492,7 +534,8 @@ class ResourceTable(val validateResources: Boolean = false, val logger: BlameLog
}
private fun setOverlayableImpl(
name: ResourceName, overlayable: OverlayableItem, nameValidator: (String) -> String): Boolean {
name: ResourceName, overlayable: OverlayableItem, nameValidator: (String) -> String
): Boolean {
if (!validateName(nameValidator, name, overlayable.source)) {
return false
}
@@ -508,7 +551,8 @@ class ResourceTable(val validateResources: Boolean = false, val logger: BlameLog
logError(
blameSource(overlayable.source),
"Failed to add overlayable declaration for resource '$name', because resource already " +
"has an overlayable defined here: $previousSource.")
"has an overlayable defined here: $previousSource."
)
return false
}
entry.overlayable = overlayable
@@ -565,17 +609,20 @@ class ResourceTable(val validateResources: Boolean = false, val logger: BlameLog
class Visibility(
val source: Source = Source(""),
val comment: String = "",
val level: ResourceVisibility = ResourceVisibility.UNDEFINED)
val level: ResourceVisibility = ResourceVisibility.UNDEFINED
)
/** Represents <add-resource> in an overlay */
class AllowNew(
val source: Source,
val comment: String)
val comment: String
)
class Overlayable(
val name: String,
val actor: String,
val source: Source) {
val source: Source
) {
constructor() : this("", "", Source(""))
@@ -596,7 +643,8 @@ class OverlayableItem(
val overlayable: Overlayable,
val policies: Int = Policy.NONE,
val comment: String = "",
val source: Source = Source("")) {
val source: Source = Source("")
) {
override fun equals(other: Any?): Boolean {
if (other is OverlayableItem) {
@@ -608,32 +656,40 @@ class OverlayableItem(
/** Represents the types of overlays that are allowed to overlay the resource. */
object Policy {
const val NONE = 0
/** The resource can be overlaid by any overlay. */
const val PUBLIC = 1 shl 0
/** The resource can be overlaid by any overlay on the system partition. */
const val SYSTEM = 1 shl 1
/** The resource can be overlaid by any overlay on the vendor partition. */
const val VENDOR = 1 shl 2
/** The resource can be overlaid by any overlay on the product partition. */
const val PRODUCT = 1 shl 3
/** The resource can be overlaid by any overlay signed with the same signature as its actor. */
const val SIGNATURE = 1 shl 4
/** The resource can be overlaid by any overlay on the odm partition. */
const val ODM = 1 shl 5
/** The resource can be overlaid by any overlay on the oem partition. */
const val OEM = 1 shl 6
}
}
/** Represents all groups by type under a single package. */
class ResourceTablePackage(var name: String = "", var id: Byte? = null) {
class ResourceTablePackage(override var name: String = "", var id: Byte? = null) :
IResourceTablePackage {
internal val groups = mutableListOf<ResourceGroup>()
fun findGroup(type: AaptResourceType, groupId: Byte? = null) =
override fun findGroup(type: AaptResourceType, groupId: Byte?) =
if (groupId != null) {
groups.find {type == it.type && groupId == it.id} ?:
groups.find {type == it.type && it.id == null}
groups.find { type == it.type && groupId == it.id }
?: groups.find { type == it.type && it.id == null }
} else {
groups.find { type == it.type }
}
@@ -647,6 +703,7 @@ class ResourceTablePackage(var name: String = "", var id: Byte? = null) {
groups.add(newGroup)
newGroup
}
else -> group
}
}
@@ -656,7 +713,7 @@ class ResourceTablePackage(var name: String = "", var id: Byte? = null) {
* Represents all resource entries grouped under a resource type (eg. string, drawable, layout,
* etc.).
*/
class ResourceGroup(val type : AaptResourceType) {
class ResourceGroup(val type: AaptResourceType) : IResourceGroup {
var id: Byte? = null
var visibility = ResourceVisibility.UNDEFINED
@@ -675,7 +732,7 @@ class ResourceGroup(val type : AaptResourceType) {
return styleableContainer[0].value!! as Styleable
}
fun findEntry(name: String, entryId: Short? = null): ResourceEntry? {
override fun findEntry(name: String, entryId: Short?): ResourceEntry? {
val nameGroup = entries[name] ?: return null
return if (entryId != null) {
nameGroup[entryId] ?: nameGroup[null]
@@ -684,6 +741,17 @@ class ResourceGroup(val type : AaptResourceType) {
}
}
override fun findEntries(entryId: Short?, predicate: (String) -> Boolean): List<IResourceEntry> {
return entries.mapNotNull { (key, value) ->
if (!predicate(key)) return@mapNotNull null
if (entryId != null) {
value[entryId] ?: value[null]
} else {
value[value.firstKey()]
}
}
}
fun findOrCreateEntry(name: String, entryId: Short? = null): ResourceEntry {
val entry = findEntry(name, entryId)
return when (entry) {
@@ -693,13 +761,14 @@ class ResourceGroup(val type : AaptResourceType) {
entries.getOrPut(name) { TreeMap(nullsFirst()) }[entryId] = newEntry
newEntry
}
else -> entry
}
}
}
/** Represents a resource entry, which may have varying values for each defined configuration. */
class ResourceEntry(val name : String) {
class ResourceEntry(override val name: String) : IResourceEntry {
var id: Short? = null
var visibility = Visibility(Source(""), "", ResourceVisibility.UNDEFINED)
@@ -708,7 +777,7 @@ class ResourceEntry(val name : String) {
val values = mutableListOf<ResourceConfigValue>()
fun findValue(config: ConfigDescription, product: String = ""): ResourceConfigValue? {
override fun findValue(config: ConfigDescription, product: String): ResourceConfigValue? {
return values.find { it.config == config && it.product == product }
}
@@ -720,19 +789,8 @@ class ResourceEntry(val name : String) {
values.add(newConfigValue)
newConfigValue
}
else -> configValue
}
}
}
/**
* Represents a single value for an entry for a given Configuration.
*
* @property config The configuration for which this value is defined.
* @property product The product name for which this value is defined.
* @property value The actual Value.
*/
data class ResourceConfigValue(
val config: ConfigDescription,
val product : String,
var value: Value? = null)
@@ -1,6 +1,11 @@
package com.android.aaptcompiler
import com.android.aapt.Resources
import com.android.aapt.Resources.Attribute.FormatFlags
import com.android.aaptcompiler.AaptResourceType.ID
import com.android.aaptcompiler.AaptResourceType.STYLE
import com.android.aaptcompiler.StringPool.Context
import com.android.aaptcompiler.StringPool.Context.Priority.NORMAL
import com.android.aaptcompiler.android.stringToInt
import com.itsaky.androidide.layoutlib.resources.ResourceVisibility
import java.io.InputStream
@@ -148,7 +153,8 @@ class TableExtractor(
val source: Source,
val config: ConfigDescription,
val options: TableExtractorOptions,
val logger: BlameLogger) {
val logger: BlameLogger
) {
fun extract(inputFile: InputStream) {
var eventReader : XMLEventReader? = null
@@ -349,7 +355,7 @@ class TableExtractor(
// Grab the name of the resource. This will be validated later, as not all XML resources
// require a name.
parsedResource.name =
parsedResource.name.copy(type = AaptResourceType.ID, entry = nameAttribute.value)
parsedResource.name.copy(type = ID, entry = nameAttribute.value)
parseItem(element, eventReader, parsedResource, resourceFormat)
val item = parsedResource.value
@@ -361,7 +367,7 @@ class TableExtractor(
// A null reference also means there is no inner element when ids are in the form:
// <id name="name"/>
parsedResource.value = Id()
(item is Reference && item.name.type != AaptResourceType.ID) || item !is Reference -> {
(item is Reference && item.name.type != ID) || item !is Reference -> {
// if an inner element exists, the inner element must be a reference to another id
logError(
blameSource(parsedResource.source),
@@ -518,7 +524,8 @@ class TableExtractor(
return StyledString(
table.stringPool.makeRef(
flattenedXml.styleString,
StringPool.Context(StringPool.Context.Priority.NORMAL.priority, config)),
Context(NORMAL.priority, config)
),
flattenedXml.untranslatableSections)
}
@@ -543,7 +550,8 @@ class TableExtractor(
// use trimmed escaped string.
return BasicString(
table.stringPool.makeRef(
flattenedXml.styleString.str, StringPool.Context(config = config)),
flattenedXml.styleString.str, Context(config = config)
),
flattenedXml.untranslatableSections)
}
@@ -566,7 +574,7 @@ class TableExtractor(
.removeSurrounding("\"")
}
return RawString(
table.stringPool.makeRef(raw, StringPool.Context(config=config)))
table.stringPool.makeRef(raw, Context(config=config)))
}
return null
@@ -708,12 +716,12 @@ class TableExtractor(
val resValue = stringToInt(valueAttribute.value)
if (resValue == null) {
logError(
blameSource(elementSource), "Invalid value '$resValue' for <$tag>. Must be an integer.")
blameSource(elementSource), "Invalid value 'null' for <$tag>. Must be an integer.")
return null
}
val reference = Reference()
reference.name = ResourceName("", AaptResourceType.ID, nameAttribute.value)
reference.name = ResourceName("", ID, nameAttribute.value)
return AttributeResource.Symbol(reference, resValue.data, resValue.dataType.byteValue)
}
@@ -729,7 +737,8 @@ class TableExtractor(
* failed.
*/
private fun parseStyleItem(
element: StartElement, eventReader: XMLEventReader, style: Style): Boolean {
element: StartElement, eventReader: XMLEventReader, style: Style
): Boolean {
val itemSource = source.withLine(element.location.lineNumber)
val nameAttribute = element.getAttributeByName(QName("name"))
@@ -811,9 +820,10 @@ class TableExtractor(
}
else -> {
// besides XLIFF, any other namespaced tags are unsupported and ignored.
logger?.warning(
logger.warning(
"Ignoring element '$elementName' with unknown namespace '${elementName.namespaceURI}'.",
blameSource(source.withLine(element.location.lineNumber)))
blameSource(source.withLine(element.location.lineNumber))
)
}
}
@@ -873,9 +883,10 @@ class TableExtractor(
// Symbols should have the default config
if (parsedResource.config != ConfigDescription()) {
logger?.warning(
logger.warning(
"Ignoring configuration '${parsedResource.config}' for <${element.name}> tag.",
blameSource(source, element.location))
blameSource(source, element.location)
)
}
if (!parseSymbolImpl(element, eventReader, parsedResource)) {
@@ -1002,9 +1013,10 @@ class TableExtractor(
// Attributes only end up in default configuration
val defaultConfig = ConfigDescription()
if (parsedResource.config != defaultConfig) {
logger?.warning(
logger.warning(
"Ignoring configuration '${parsedResource.config}' for <${element.name}> tag.",
blameSource(source, element.location))
blameSource(source, element.location)
)
parsedResource.config = defaultConfig
}
@@ -1142,11 +1154,9 @@ class TableExtractor(
val symbolName = symbol.symbol.name.toString()
if (symbolMap.contains(symbolName)) {
val newSource =
logger?.getOriginalSource(blameSource(symbol.symbol.source))
?: blameSource(symbol.symbol.source)
logger.getOriginalSource(blameSource(symbol.symbol.source))
val previousSource =
logger?.getOriginalSource(blameSource(symbolMap[symbolName]!!.symbol.source))
?: blameSource(symbolMap[symbolName]!!.symbol.source)
logger.getOriginalSource(blameSource(symbolMap[symbolName]!!.symbol.source))
val errorMsg =
"Duplicate symbol '$symbolName' defined here: $newSource" +
" and here: $previousSource"
@@ -1174,7 +1184,7 @@ class TableExtractor(
}
val resource = AttributeResource(
if (typeMask == 0) Resources.Attribute.FormatFlags.ANY_VALUE else typeMask)
if (typeMask == 0) FormatFlags.ANY_VALUE else typeMask)
resource.weak = isWeak
resource.symbols.addAll(symbolMap.values)
resource.minInt = min ?: Int.MIN_VALUE
@@ -1365,7 +1375,8 @@ class TableExtractor(
element: StartElement,
eventReader: XMLEventReader,
parsedResource: ParsedResource,
type: AaptResourceType): Boolean {
type: AaptResourceType
): Boolean {
parsedResource.name = parsedResource.name.copy(type = type)
@@ -1394,7 +1405,7 @@ class TableExtractor(
if (marker != -1) {
style.parentInferred = true
style.parent =
Reference(ResourceName("", AaptResourceType.STYLE, styleName.substring(0, marker)))
Reference(ResourceName("", STYLE, styleName.substring(0, marker)))
}
}
@@ -1458,9 +1469,10 @@ class TableExtractor(
// Declare-stylable only ends up in the default config
val defaultConfig = ConfigDescription()
if (parsedResource.config != defaultConfig) {
logger?.warning(
logger.warning(
"Ignoring configuration '${parsedResource.config}' for <${element.name}> tag.",
blameSource(source, element.location))
blameSource(source, element.location)
)
parsedResource.config = defaultConfig
}
@@ -1554,9 +1566,10 @@ class TableExtractor(
val defaultConfig = ConfigDescription()
if (parsedResource.config != defaultConfig) {
logger?.warning(
logger.warning(
"Ignoring configuration '${parsedResource.config}' for <${element.name}> tag.",
blameSource(source, element.location))
blameSource(source, element.location)
)
}
val nameAttribute = element.getAttributeByName(QName(null, "name"))
@@ -1883,9 +1896,10 @@ class TableExtractor(
}
if (parsedResource.config != ConfigDescription()) {
logger?.warning(
logger.warning(
"Ignoring configuration '${parsedResource.config}' for <${element.name}> tag.",
blameSource(source, element.location))
blameSource(source, element.location)
)
}
val typeAttribute = element.getAttributeByName(QName("type"))
@@ -1919,7 +1933,7 @@ class TableExtractor(
parsedResource.resourceId = id
}
if (parsedType == AaptResourceType.ID) {
if (parsedType == ID) {
// An ID marked as public is also the definition of an ID.
parsedResource.value = Id()
}
@@ -1944,9 +1958,10 @@ class TableExtractor(
}
if (parsedResource.config != ConfigDescription()) {
logger?.warning(
logger.warning(
"Ignoring configuration '${parsedResource.config}' for <${element.name}> tag.",
blameSource(source, element.location))
blameSource(source, element.location)
)
}
val typeAttribute = element.getAttributeByName(QName("type"))
@@ -1,5 +1,6 @@
package com.android.aaptcompiler
import com.android.aaptcompiler.ResourceFile.Type.ProtoXml
import java.io.InputStream
import java.lang.RuntimeException
import jaxp.xml.namespace.QName
@@ -389,7 +390,7 @@ class XmlProcessor(val source: Source, val logger: BlameLogger?) {
// Now to add the new attribute to the parent xml element.
parentBuilder.addAttribute(
nameValue.entry,
nameValue.entry!!,
attrUri,
"@${resource.name}",
attrElement.location.lineNumber,
@@ -529,7 +530,7 @@ class XmlProcessor(val source: Source, val logger: BlameLogger?) {
primaryFile.name.copy(entry = newEntryName),
primaryFile.configuration,
primaryFile.source.withLine(element.location.lineNumber),
ResourceFile.Type.ProtoXml
ProtoXml
)
}
}
@@ -3,7 +3,6 @@ package com.android.aaptcompiler
import com.android.SdkConstants
import jaxp.sun.xml.internal.stream.XMLInputFactoryImpl
import jaxp.xml.stream.XMLEventReader
import jaxp.xml.stream.XMLInputFactory
import jaxp.xml.stream.events.StartElement
internal val xmlInputFactory = XMLInputFactoryImpl()
@@ -25,6 +25,8 @@ import com.android.aaptcompiler.ResourceTable
import com.android.aaptcompiler.Source
import com.android.aaptcompiler.Span
import com.android.aaptcompiler.StringPool
import com.android.aaptcompiler.StringPool.Context
import com.android.aaptcompiler.StringPool.Context.Priority.HIGH
import com.android.aaptcompiler.Style
import com.android.aaptcompiler.StyleString
import com.android.aaptcompiler.Styleable
@@ -48,10 +50,10 @@ fun deserializeConfigFromPb(
config: ConfigurationOuterClass.Configuration, logger: ILogger?
): ConfigDescription? {
val mcc = config.getMcc().toShort()
val mnc = config.getMnc().toShort()
val mcc = config.mcc.toShort()
val mnc = config.mnc.toShort()
val localeString = config.getLocale()
val localeString = config.locale
val localeValue = LocaleValue()
if (localeString.isNotEmpty()) {
if (!localeValue.initFromBcp47Tag(localeString)) {
@@ -70,9 +72,9 @@ fun deserializeConfigFromPb(
else -> ResTableConfig.SCREEN_LAYOUT.DIR_ANY
}
val smallestScreenWidthDp = config.getSmallestScreenWidthDp()
val screenWidthDp = config.getScreenWidthDp()
val screenHeightDp = config.getScreenHeightDp()
val smallestScreenWidthDp = config.smallestScreenWidthDp
val screenWidthDp = config.screenWidthDp
val screenHeightDp = config.screenHeightDp
val screenLayoutSize = when (config.getScreenLayoutSize()) {
ConfigurationOuterClass.Configuration.ScreenLayoutSize.SCREEN_LAYOUT_SIZE_SMALL ->
@@ -174,7 +176,7 @@ fun deserializeConfigFromPb(
else -> ResTableConfig.UI_MODE.NIGHT_ANY
}
val density = config.getDensity()
val density = config.density
val touchscreen = when (config.getTouchscreen()) {
ConfigurationOuterClass.Configuration.Touchscreen.TOUCHSCREEN_NOTOUCH ->
@@ -254,9 +256,9 @@ fun deserializeConfigFromPb(
else -> ResTableConfig.GRAMMATICAL_GENDER.ANY
}
val screenWidth = config.getScreenWidth()
val screenHeight = config.getScreenHeight()
val sdkVersion = config.getSdkVersion().toShort()
val screenWidth = config.screenWidth
val screenHeight = config.screenHeight
val sdkVersion = config.sdkVersion.toShort()
val configDescription = ConfigDescription(
ResTableConfig(
@@ -301,8 +303,8 @@ fun deserializeReferenceTypeFromPb(type: Resources.Reference.Type) = when (type)
}
fun deserializeSourceFromPb(source: Resources.Source, sourcePool: ResStringPool): Source {
val path = sourcePool.strings[source.getPathIdx()]
val line = source.getPosition().getLineNumber()
val path = sourcePool.strings[source.pathIdx]
val line = source.position.lineNumber
return Source(path, line)
}
@@ -320,7 +322,7 @@ fun deserializeOverlayableFromPb(
): OverlayableItem? {
var policies = 0
for (policy in item.getPolicyList()) {
for (policy in item.policyList) {
policies = policies or when (policy) {
Resources.OverlayableItem.Policy.PUBLIC -> OverlayableItem.Policy.PUBLIC
Resources.OverlayableItem.Policy.SYSTEM -> OverlayableItem.Policy.SYSTEM
@@ -337,11 +339,11 @@ fun deserializeOverlayableFromPb(
}
val source = if (item.hasSource()) {
deserializeSourceFromPb(item.getSource(), sourcePool)
deserializeSourceFromPb(item.source, sourcePool)
} else {
Source.EMPTY
}
val comment = item.getComment()
val comment = item.comment
return OverlayableItem(overlayable, policies, comment, source)
}
@@ -349,7 +351,7 @@ fun deserializeBinPrimitiveFromPb(
primitive: Resources.Primitive, logger: ILogger?
): BinaryPrimitive? {
val (type, deviceValue) = when (primitive.getOneofValueCase()) {
val (type, deviceValue) = when (primitive.oneofValueCase) {
Resources.Primitive.OneofValueCase.NULL_VALUE ->
Pair(ResValue.DataType.NULL, ResValue.NullFormat.UNDEFINED)
@@ -410,25 +412,25 @@ fun deserializeBinPrimitiveFromPb(
}
fun deserializeStringFromPb(string: Resources.String, valuePool: StringPool) =
BasicString(valuePool.makeRef(string.getValue()))
BasicString(valuePool.makeRef(string.value))
fun deserializeRawFromPb(string: Resources.RawString, valuePool: StringPool) =
RawString(valuePool.makeRef(string.getValue()))
RawString(valuePool.makeRef(string.value))
fun deserializeStyledStrFromPb(
string: Resources.StyledString, valuePool: StringPool
): StyledString {
val spans = mutableListOf<Span>()
for (span in string.getSpanList()) {
spans.add(Span(span.getTag(), span.getFirstChar(), span.getLastChar()))
for (span in string.spanList) {
spans.add(Span(span.tag, span.firstChar, span.lastChar))
}
return StyledString(valuePool.makeRef(StyleString(string.getValue(), spans)), listOf())
return StyledString(valuePool.makeRef(StyleString(string.value, spans)), listOf())
}
fun deserializeReferenceFromPb(ref: Resources.Reference, logger: ILogger?): Reference? {
val reference = if (ref.getName().isNotEmpty()) {
val refName = parseResourceName(ref.getName())
val reference = if (ref.name.isNotEmpty()) {
val refName = parseResourceName(ref.name)
if (refName == null) {
val errorMsg = "%s cannot be parsed as a Resource Name."
logger?.error(null, errorMsg, ref.name)
@@ -440,10 +442,10 @@ fun deserializeReferenceFromPb(ref: Resources.Reference, logger: ILogger?): Refe
}
reference.referenceType = deserializeReferenceTypeFromPb(ref.getType())
reference.isPrivate = ref.getPrivate()
reference.isPrivate = ref.private
if (ref.getId() != 0) {
reference.id = ref.getId()
if (ref.id != 0) {
reference.id = ref.id
}
return reference
}
@@ -454,8 +456,8 @@ fun deserializeFileRefFromPb(
val fileRef = FileReference(
valuePool.makeRef(
file.getPath(),
StringPool.Context(StringPool.Context.Priority.HIGH.priority, config)
file.path,
Context(HIGH.priority, config)
)
)
fileRef.type = deserializeFileTypeFromPb(file.getType())
@@ -467,21 +469,21 @@ fun deserializeAttrFromPb(
attr: Resources.Attribute, sourcePool: ResStringPool, logger: ILogger?
): AttributeResource? {
val attrResource = AttributeResource(attr.getFormatFlags())
attrResource.minInt = attr.getMinInt()
attrResource.maxInt = attr.getMaxInt()
val attrResource = AttributeResource(attr.formatFlags)
attrResource.minInt = attr.minInt
attrResource.maxInt = attr.maxInt
for (symbol in attr.getSymbolList()) {
val reference = deserializeReferenceFromPb(symbol.getName(), logger)
for (symbol in attr.symbolList) {
val reference = deserializeReferenceFromPb(symbol.name, logger)
reference ?: return null
if (symbol.hasSource()) {
reference.source = deserializeSourceFromPb(symbol.getSource(), sourcePool)
reference.source = deserializeSourceFromPb(symbol.source, sourcePool)
}
reference.comment = symbol.getComment()
reference.comment = symbol.comment
attrResource.symbols.add(
AttributeResource.Symbol(reference, symbol.getValue(), symbol.getType().toByte())
AttributeResource.Symbol(reference, symbol.value, symbol.type.toByte())
)
}
@@ -498,29 +500,29 @@ fun deserializeStyleFromPb(
val styleResource = Style()
if (style.hasParent()) {
val parentRef = deserializeReferenceFromPb(style.getParent(), logger)
val parentRef = deserializeReferenceFromPb(style.parent, logger)
parentRef ?: return null
parentRef.source = if (style.hasParentSource()) {
deserializeSourceFromPb(style.getParentSource(), sourcePool)
deserializeSourceFromPb(style.parentSource, sourcePool)
} else {
Source.EMPTY
}
styleResource.parent = parentRef
}
for (entry in style.getEntryList()) {
val key = deserializeReferenceFromPb(entry.getKey(), logger)
for (entry in style.entryList) {
val key = deserializeReferenceFromPb(entry.key, logger)
key ?: return null
val value = deserializeItemFromPb(entry.getItem(), valuePool, config, logger)
val value = deserializeItemFromPb(entry.item, valuePool, config, logger)
value ?: return null
if (entry.hasSource()) {
key.source = deserializeSourceFromPb(entry.getSource(), sourcePool)
value.source = deserializeSourceFromPb(entry.getSource(), sourcePool)
key.source = deserializeSourceFromPb(entry.source, sourcePool)
value.source = deserializeSourceFromPb(entry.source, sourcePool)
}
key.comment = entry.getComment()
value.comment = entry.getComment()
key.comment = entry.comment
value.comment = entry.comment
styleResource.entries.add(Style.Entry(key, value))
}
@@ -532,13 +534,13 @@ fun deserializeStyleableFromPb(
): Styleable? {
val styleableResource = Styleable()
for (entry in styleable.getEntryList()) {
val reference = deserializeReferenceFromPb(entry.getAttr(), logger)
for (entry in styleable.entryList) {
val reference = deserializeReferenceFromPb(entry.attr, logger)
reference ?: return null
if (entry.hasSource()) {
reference.source = deserializeSourceFromPb(entry.getSource(), sourcePool)
reference.source = deserializeSourceFromPb(entry.source, sourcePool)
}
reference.comment = entry.getComment()
reference.comment = entry.comment
styleableResource.entries.add(reference)
}
return styleableResource
@@ -553,14 +555,14 @@ fun deserializeArrayFromPb(
): ArrayResource? {
val arrayResource = ArrayResource()
for (entry in array.getElementList()) {
val item = deserializeItemFromPb(entry.getItem(), valuePool, config, logger)
for (entry in array.elementList) {
val item = deserializeItemFromPb(entry.item, valuePool, config, logger)
item ?: return null
if (entry.hasSource()) {
item.source = deserializeSourceFromPb(entry.getSource(), sourcePool)
item.source = deserializeSourceFromPb(entry.source, sourcePool)
}
item.comment = entry.getComment()
item.comment = entry.comment
arrayResource.elements.add(item)
}
return arrayResource
@@ -584,18 +586,18 @@ fun deserializePluralFromPb(
): Plural? {
val pluralResource = Plural()
for (entry in plural.getEntryList()) {
for (entry in plural.entryList) {
val type = deserializePluralTypeFromPb(entry.getArity())
val item = deserializeItemFromPb(entry.getItem(), valuePool, config, logger)
val item = deserializeItemFromPb(entry.item, valuePool, config, logger)
item ?: return null
item.source = if (entry.hasSource()) {
deserializeSourceFromPb(entry.getSource(), sourcePool)
deserializeSourceFromPb(entry.source, sourcePool)
} else {
Source.EMPTY
}
item.comment = entry.getComment()
item.comment = entry.comment
pluralResource.setValue(type, item)
}
@@ -648,7 +650,7 @@ fun deserializeItemFromPb(
valuePool: StringPool,
config: ConfigDescription,
logger: ILogger?
): Item? = when (item.getValueCase()) {
): Item? = when (item.valueCase) {
Resources.Item.ValueCase.REF -> deserializeReferenceFromPb(item.getRef(), logger)
Resources.Item.ValueCase.STR -> deserializeStringFromPb(item.getStr(), valuePool)
@@ -679,7 +681,7 @@ fun deserializeValueFromPb(
value.hasCompoundValue() -> {
val compoundValue = value.getCompoundValue()
when (compoundValue.getValueCase()) {
when (compoundValue.valueCase) {
Resources.CompoundValue.ValueCase.ATTR ->
deserializeAttrFromPb(compoundValue.getAttr(), sourcePool, logger)
@@ -714,11 +716,11 @@ fun deserializeValueFromPb(
}
valueResource ?: return null
valueResource.weak = value.getWeak()
valueResource.weak = value.weak
if (value.hasSource()) {
valueResource.source = deserializeSourceFromPb(value.getSource(), sourcePool)
valueResource.source = deserializeSourceFromPb(value.source, sourcePool)
}
valueResource.comment = value.getComment()
valueResource.comment = value.comment
return valueResource
}
@@ -732,14 +734,14 @@ fun deserializePackageFromPb(
): Boolean {
val packageId =
if (original.hasPackageId()) original.getPackageId().getId().toByte() else 0.toByte()
if (original.hasPackageId()) original.packageId.id.toByte() else 0.toByte()
val idIndex = mutableIntObjectMapOf<ResourceName>()
val resourcePackage =
table.createPackageAllowingDuplicateNames(original.getPackageName(), packageId)
for (group in original.getTypeList()) {
val resType = resourceTypeFromTag(group.getName())
table.createPackageAllowingDuplicateNames(original.packageName, packageId)
for (group in original.typeList) {
val resType = resourceTypeFromTag(group.name)
if (resType == null) {
val errorMsg = "Unrecognized Resource Type: %s."
@@ -749,18 +751,18 @@ fun deserializePackageFromPb(
val resourceGroup = resourcePackage.findOrCreateGroup(resType)
if (group.hasTypeId()) {
resourceGroup.id = group.getTypeId().getId().toByte()
resourceGroup.id = group.typeId.id.toByte()
}
for (entry in group.getEntryList()) {
val resourceEntry = resourceGroup.findOrCreateEntry(entry.getName())
for (entry in group.entryList) {
val resourceEntry = resourceGroup.findOrCreateEntry(entry.name)
if (entry.hasEntryId()) {
resourceEntry.id = entry.getEntryId().getId().toShort()
resourceEntry.id = entry.entryId.id.toShort()
}
// Deserialize the symbol status (public/private with metadata)
if (entry.hasVisibility()) {
val visibility = entry.getVisibility()
val visibility = entry.visibility
val visibilityLevel = deserializeVisibilityFromPb(visibility.getLevel())
when (visibilityLevel) {
@@ -777,30 +779,30 @@ fun deserializePackageFromPb(
}
val visibilitySource = if (visibility.hasSource()) {
deserializeSourceFromPb(visibility.getSource(), sourcePool)
deserializeSourceFromPb(visibility.source, sourcePool)
} else {
Source.EMPTY
}
val visibilityComment = visibility.getComment()
val visibilityComment = visibility.comment
resourceEntry.visibility = Visibility(visibilitySource, visibilityComment, visibilityLevel)
}
if (entry.hasAllowNew()) {
val allowNew = entry.getAllowNew()
val allowNew = entry.allowNew
val allowNewSource = if (allowNew.hasSource()) {
deserializeSourceFromPb(allowNew.getSource(), sourcePool)
deserializeSourceFromPb(allowNew.source, sourcePool)
} else {
Source.EMPTY
}
val allowNewComment = allowNew.getComment()
val allowNewComment = allowNew.comment
resourceEntry.allowNew = AllowNew(allowNewSource, allowNewComment)
}
if (entry.hasOverlayableItem()) {
// Find the overlayable to which this item belongs.
val overlayableItem = entry.getOverlayableItem()
if (overlayableItem.getOverlayableIdx() >= overlayables.size) {
val overlayableItem = entry.overlayableItem
if (overlayableItem.overlayableIdx >= overlayables.size) {
val errorMsg = "Illegal value for overlayable id '%s'. Because only '%s' overlayables" +
"exist in proto."
logger?.error(null, errorMsg, overlayableItem.overlayableIdx, overlayables.size)
@@ -809,7 +811,7 @@ fun deserializePackageFromPb(
val resourceOverlayable =
deserializeOverlayableFromPb(
overlayableItem,
overlayables[overlayableItem.getOverlayableIdx()],
overlayables[overlayableItem.overlayableIdx],
sourcePool,
logger
)
@@ -819,23 +821,23 @@ fun deserializePackageFromPb(
}
val resourceId = resourceIdFromParts(
original.getPackageId().getId().toByte(),
group.getTypeId().getId().toByte(),
entry.getEntryId().getId().toShort()
original.packageId.id.toByte(),
group.typeId.id.toByte(),
entry.entryId.id.toShort()
)
if (resourceId.isValidId()) {
idIndex[resourceId] =
ResourceName(resourcePackage.name, resourceGroup.type, resourceEntry.name)
}
for (configValue in entry.getConfigValueList()) {
val config = configValue.getConfig()
for (configValue in entry.configValueList) {
val config = configValue.config
val resourceConfig = deserializeConfigFromPb(config, logger)
resourceConfig ?: return false
val resourceConfigValue =
resourceEntry.findOrCreateValue(resourceConfig, config.getProduct())
resourceEntry.findOrCreateValue(resourceConfig, config.product)
if (resourceConfigValue.value.isTruthy()) {
val errorMsg = "Value already exists for name %s, config %s, and product %s."
logger?.error(
@@ -850,7 +852,7 @@ fun deserializePackageFromPb(
resourceConfigValue.value =
deserializeValueFromPb(
configValue.getValue(), table.stringPool, resourceConfig, sourcePool, logger
configValue.value, table.stringPool, resourceConfig, sourcePool, logger
)
resourceConfigValue.value ?: return false
@@ -885,22 +887,22 @@ fun deserializeTableFromPb(
): Boolean {
val sourcePool = ResStringPool.get(
table.getSourcePool().getData().asReadOnlyByteBuffer(),
table.getSourcePool().getSerializedSize()
table.sourcePool.data.asReadOnlyByteBuffer(),
table.sourcePool.serializedSize
)
// Deserialize the overlayable groups of a table
val overlayables = mutableListOf<Overlayable>()
for (overlayable in table.getOverlayableList()) {
for (overlayable in table.overlayableList) {
val overlayableSource = if (overlayable.hasSource()) {
deserializeSourceFromPb(overlayable.getSource(), sourcePool)
deserializeSourceFromPb(overlayable.source, sourcePool)
} else {
Source.EMPTY
}
overlayables.add(Overlayable(overlayable.getName(), overlayable.getActor(), overlayableSource))
overlayables.add(Overlayable(overlayable.name, overlayable.actor, overlayableSource))
}
for (resourcePackage in table.getPackageList()) {
for (resourcePackage in table.packageList) {
if (!deserializePackageFromPb(resourcePackage, sourcePool, overlayables, logger, outTable)) {
return false
}
@@ -1,7 +1,6 @@
package com.android.aaptcompiler.proto
import android.aapt.pb.internal.ResourcesInternal
import androidx.collection.intIntMapOf
import com.android.aapt.ConfigurationOuterClass
import com.android.aapt.Resources
import com.android.aaptcompiler.ArrayResource
@@ -59,7 +58,7 @@ internal fun serializeSourceToPb(source: Source, sourcePool: StringPool): Resour
sourceBuilder.setPathIdx(ref.index())
if (source.line != null) {
sourceBuilder.setPosition(
Resources.SourcePosition.newBuilder().setLineNumber(source.line).build()
Resources.SourcePosition.newBuilder().setLineNumber(source.line!!).build()
)
}
return sourceBuilder.build()
@@ -1,29 +0,0 @@
package com.itsaky.androidide.aapt
import com.android.aaptcompiler.ResourceEntry
import com.android.aaptcompiler.ResourceGroup
/** @author Akash Yadav */
/** Returns all [ResourceEntry]s which match the given predicate. */
fun ResourceGroup.findEntries(
entryId: Short? = null,
test: (String) -> Boolean
): List<ResourceEntry> {
val result = mutableListOf<ResourceEntry>()
entries.forEach {
if (test(it.key)) {
val element =
if (entryId != null) {
it.value[entryId] ?: it.value[null]
} else {
it.value[it.value.firstKey()]
}
if (element != null) {
result.add(element)
}
}
}
return result
}
@@ -16,6 +16,7 @@
package com.android.aaptcompiler
import com.android.aaptcompiler.BlameLogger.Source
import com.android.utils.ILogger
import com.google.common.truth.Truth.assertThat
import org.junit.Test
@@ -73,5 +74,5 @@ fun getMockBlameLogger(mockLogger: BlameLoggerTest.MockLogger) =
BlameLogger(
mockLogger
) {
BlameLogger.Source(it.sourcePath + ".rewritten", it.line + 1, it.column + 2)
Source(it.sourcePath + ".rewritten", it.line + 1, it.column + 2)
}
@@ -32,7 +32,8 @@ class PseudolocaleGeneratorTest {
val spans = listOf(
Span("i", 1, 10),
Span("b", 2, 3),
Span("b", 6, 7))
Span("b", 6, 7)
)
val original = StyledString(pool.makeRef(StyleString("Hello world!", spans)), listOf())
val newString1 = pseudolocalizeStyledString(original, Pseudolocalizer.Method.NONE, pool)
@@ -124,7 +125,8 @@ class PseudolocaleGeneratorTest {
Span("b", 16, 19),
Span("em", 29, 47),
Span("i", 38, 40),
Span("b", 44, 47))
Span("b", 44, 47)
)
val originalText = "This sentence is not what you think it is at all."
val original = StyledString(pool.makeRef(StyleString(originalText, originalSpans)), listOf())
@@ -3,7 +3,6 @@ package com.android.aaptcompiler
import com.android.aapt.Resources
import com.android.aaptcompiler.android.ResValue
import com.android.aaptcompiler.proto.deserializeConfigFromPb
import com.android.aaptcompiler.proto.deserializeFileTypeFromPb
import com.android.aaptcompiler.proto.deserializeTableFromPb
import com.android.aaptcompiler.testutils.ContainerReader
import com.android.aaptcompiler.testutils.FileEntry
@@ -1,8 +1,12 @@
package com.android.aaptcompiler
import com.android.aapt.Resources
import com.android.aapt.Resources.Attribute.FormatFlags
import com.android.aaptcompiler.AaptResourceType.ATTR
import com.android.aaptcompiler.AaptResourceType.ID
import com.android.aaptcompiler.AaptResourceType.STYLEABLE
import com.android.aaptcompiler.android.ResTableConfig
import com.android.aaptcompiler.android.ResValue
import com.android.aaptcompiler.android.ResValue.DataType.INT_BOOLEAN
import com.itsaky.androidide.layoutlib.resources.ResourceVisibility
import com.google.common.truth.Truth
import org.junit.Test
@@ -35,14 +39,14 @@ class ResourceTableTest {
Truth.assertThat(
table.addResource(
ResourceName("android", AaptResourceType.ID, "hey,there"),
ResourceName("android", ID, "hey,there"),
ConfigDescription(),
"",
id)).isFalse()
Truth.assertThat(
table.addResource(
ResourceName("android", AaptResourceType.ID, "hey:there"),
ResourceName("android", ID, "hey:there"),
ConfigDescription(),
"",
id)).isFalse()
@@ -86,23 +90,23 @@ class ResourceTableTest {
val table = ResourceTable()
val styleableOne = Styleable()
styleableOne.entries.add(0, Reference(ResourceName("", AaptResourceType.ATTR, "child_one")))
styleableOne.entries.add(1, Reference(ResourceName("android", AaptResourceType.ATTR, "child_two")))
styleableOne.entries.add(0, Reference(ResourceName("", ATTR, "child_one")))
styleableOne.entries.add(1, Reference(ResourceName("android", ATTR, "child_two")))
Truth.assertThat(
table.addResource(
ResourceName("", AaptResourceType.STYLEABLE, "styleable_parent_one"),
ResourceName("", STYLEABLE, "styleable_parent_one"),
ConfigDescription(),
"",
styleableOne)).isTrue()
val styleableTwo = Styleable()
styleableTwo.entries.add(0, Reference(ResourceName("", AaptResourceType.ATTR, "child_one")))
styleableTwo.entries.add(1, Reference(ResourceName("android", AaptResourceType.ATTR, "child_three")))
styleableTwo.entries.add(0, Reference(ResourceName("", ATTR, "child_one")))
styleableTwo.entries.add(1, Reference(ResourceName("android", ATTR, "child_three")))
Truth.assertThat(
table.addResource(
ResourceName("", AaptResourceType.STYLEABLE, "styleable_parent_two"),
ResourceName("", STYLEABLE, "styleable_parent_two"),
ConfigDescription(),
"",
styleableTwo)).isTrue()
@@ -139,7 +143,7 @@ class ResourceTableTest {
val table = ResourceTable()
val config = ConfigDescription()
val languageConfig =
ConfigDescription(ResTableConfig(language = byteArrayOf('p'.toByte(), 'l'.toByte())))
ConfigDescription(ResTableConfig(language = byteArrayOf('p'.code.toByte(), 'l'.code.toByte())))
val id1 = Id()
id1.source = Source("test/path/file.xml", 10)
@@ -223,11 +227,11 @@ class ResourceTableTest {
val config = ConfigDescription()
val name = parseResourceName("android:attr/foo")!!.resourceName
val weak1 = AttributeResource(Resources.Attribute.FormatFlags.STRING_VALUE)
val weak1 = AttributeResource(FormatFlags.STRING_VALUE)
weak1.weak = true
val weak2 = AttributeResource(
Resources.Attribute.FormatFlags.STRING_VALUE or
Resources.Attribute.FormatFlags.REFERENCE_VALUE)
FormatFlags.STRING_VALUE or
FormatFlags.REFERENCE_VALUE)
weak2.weak = true
Truth.assertThat(table.addResource(name, config, "", weak1)).isTrue()
@@ -340,7 +344,8 @@ class ResourceTableTest {
overlayable,
OverlayableItem.Policy.PRODUCT or OverlayableItem.Policy.VENDOR,
"comment",
Source("res/values/overlayable.xml", 42))
Source("res/values/overlayable.xml", 42)
)
val name = parseResourceName("android:string/foo")!!.resourceName
table.setOverlayable(name, overlayableItem)
@@ -436,14 +441,16 @@ class ResourceTableTest {
0x7f0100ff,
ConfigDescription(),
"",
BinaryPrimitive(ResValue(ResValue.DataType.INT_BOOLEAN, 0)))).isTrue()
BinaryPrimitive(ResValue(INT_BOOLEAN, 0))
)).isTrue()
Truth.assertThat(
table.addResourceWithId(
name,
0x7f010100,
ConfigDescription(),
"",
BinaryPrimitive(ResValue(ResValue.DataType.INT_BOOLEAN, 1)))).isTrue()
BinaryPrimitive(ResValue(INT_BOOLEAN, 1))
)).isTrue()
Truth.assertThat(
table.setVisibilityWithId(
@@ -1,7 +1,17 @@
package com.android.aaptcompiler
import com.android.aapt.Resources
import com.android.aapt.Resources.Attribute.FormatFlags
import com.android.aaptcompiler.AaptResourceType.COLOR
import com.android.aaptcompiler.AaptResourceType.ID
import com.android.aaptcompiler.AaptResourceType.INTEGER
import com.android.aaptcompiler.AaptResourceType.STRING
import com.android.aaptcompiler.android.ResValue
import com.android.aaptcompiler.android.ResValue.DataType.FLOAT
import com.android.aaptcompiler.android.ResValue.DataType.INT_BOOLEAN
import com.android.aaptcompiler.android.ResValue.DataType.INT_DEC
import com.android.aaptcompiler.android.ResValue.DataType.NULL
import com.android.aaptcompiler.android.ResValue.NullFormat
import com.google.common.truth.Truth.assertThat
import org.junit.Test
@@ -25,19 +35,19 @@ class ResourceUtilsTest {
var result = parseResourceName("android:color/foo")
assertThat(result).isNotNull()
assertThat(result?.resourceName)
.isEqualTo(ResourceName("android", AaptResourceType.COLOR, "foo"))
.isEqualTo(ResourceName("android", COLOR, "foo"))
assertThat(result?.isPrivate).isFalse()
result = parseResourceName("color/foo")
assertThat(result).isNotNull()
assertThat(result?.resourceName)
.isEqualTo(ResourceName("", AaptResourceType.COLOR, "foo"))
.isEqualTo(ResourceName("", COLOR, "foo"))
assertThat(result?.isPrivate).isFalse()
result = parseResourceName("*android:string/foo")
assertThat(result).isNotNull()
assertThat(result?.resourceName)
.isEqualTo(ResourceName("android", AaptResourceType.STRING, "foo"))
.isEqualTo(ResourceName("android", STRING, "foo"))
assertThat(result?.isPrivate).isTrue()
result = parseResourceName("")
@@ -49,7 +59,7 @@ class ResourceUtilsTest {
val result = parseReference("@color/bar")
assertThat(result).isNotNull()
assertThat(result?.reference?.name)
.isEqualTo(ResourceName("", AaptResourceType.COLOR, "bar"))
.isEqualTo(ResourceName("", COLOR, "bar"))
assertThat(result?.createNew).isFalse()
assertThat(result?.reference?.isPrivate).isFalse()
}
@@ -59,7 +69,7 @@ class ResourceUtilsTest {
val result = parseReference("@android:integer/foo")
assertThat(result).isNotNull()
assertThat(result?.reference?.name)
.isEqualTo(ResourceName("android", AaptResourceType.INTEGER, "foo"))
.isEqualTo(ResourceName("android", INTEGER, "foo"))
assertThat(result?.createNew).isFalse()
assertThat(result?.reference?.isPrivate).isFalse()
}
@@ -69,7 +79,7 @@ class ResourceUtilsTest {
val result = parseReference("\t @android:integer/foo\n \n\t")
assertThat(result).isNotNull()
assertThat(result?.reference?.name)
.isEqualTo(ResourceName("android", AaptResourceType.INTEGER, "foo"))
.isEqualTo(ResourceName("android", INTEGER, "foo"))
assertThat(result?.createNew).isFalse()
assertThat(result?.reference?.isPrivate).isFalse()
}
@@ -79,7 +89,7 @@ class ResourceUtilsTest {
val result = parseReference("@+android:id/foo")
assertThat(result).isNotNull()
assertThat(result?.reference?.name)
.isEqualTo(ResourceName("android", AaptResourceType.ID, "foo"))
.isEqualTo(ResourceName("android", ID, "foo"))
assertThat(result?.createNew).isTrue()
assertThat(result?.reference?.isPrivate).isFalse()
}
@@ -89,7 +99,7 @@ class ResourceUtilsTest {
val result = parseReference("@*android:id/foo")
assertThat(result).isNotNull()
assertThat(result?.reference?.name)
.isEqualTo(ResourceName("android", AaptResourceType.ID, "foo"))
.isEqualTo(ResourceName("android", ID, "foo"))
assertThat(result?.createNew).isFalse()
assertThat(result?.reference?.isPrivate).isTrue()
}
@@ -128,29 +138,29 @@ class ResourceUtilsTest {
@Test
fun testEmptyIsBinaryPrimitive() {
assertThat(makeEmpty())
.isEqualTo(BinaryPrimitive(ResValue(ResValue.DataType.NULL, ResValue.NullFormat.EMPTY)))
.isEqualTo(BinaryPrimitive(ResValue(NULL, NullFormat.EMPTY)))
assertThat(tryParseNullOrEmpty("@empty"))
.isEqualTo(BinaryPrimitive(ResValue(ResValue.DataType.NULL, ResValue.NullFormat.EMPTY)))
.isEqualTo(BinaryPrimitive(ResValue(NULL, NullFormat.EMPTY)))
}
@Test
fun testItemsWithWhitespaceAreParsedCorrectly() {
var result =
tryParseItemForAttribute(" 12\n ", Resources.Attribute.FormatFlags.INTEGER_VALUE)
tryParseItemForAttribute(" 12\n ", FormatFlags.INTEGER_VALUE)
assertThat(result).isNotNull()
assertThat(result)
.isEqualTo(BinaryPrimitive(ResValue(ResValue.DataType.INT_DEC, 12)))
.isEqualTo(BinaryPrimitive(ResValue(INT_DEC, 12)))
result =
tryParseItemForAttribute(" true\n ", Resources.Attribute.FormatFlags.BOOLEAN_VALUE)
tryParseItemForAttribute(" true\n ", FormatFlags.BOOLEAN_VALUE)
assertThat(result).isNotNull()
assertThat(result)
.isEqualTo(BinaryPrimitive(ResValue(ResValue.DataType.INT_BOOLEAN, -1)))
.isEqualTo(BinaryPrimitive(ResValue(INT_BOOLEAN, -1)))
val expectedFloat = 12.0f
result =
tryParseItemForAttribute(" 12.0\n ", Resources.Attribute.FormatFlags.FLOAT_VALUE)
tryParseItemForAttribute(" 12.0\n ", FormatFlags.FLOAT_VALUE)
assertThat(result).isNotNull()
assertThat(result)
.isEqualTo(BinaryPrimitive(ResValue(ResValue.DataType.FLOAT, expectedFloat.toRawBits())))
.isEqualTo(BinaryPrimitive(ResValue(FLOAT, expectedFloat.toRawBits())))
}
}
@@ -2,6 +2,7 @@ package com.android.aaptcompiler
import com.android.aaptcompiler.android.ResValue
import com.android.aapt.Resources
import com.android.aaptcompiler.AaptResourceType.RAW
import com.itsaky.androidide.layoutlib.resources.ResourceVisibility
import com.google.common.truth.Truth.assertThat
import org.junit.Assert.assertThrows
@@ -573,7 +574,7 @@ class TableExtractorTest {
val nullRef = getValue("integer/foo") as? Reference
assertThat(nullRef).isNotNull()
nullRef!!
assertThat(nullRef.name).isEqualTo(ResourceName("", AaptResourceType.RAW, ""))
assertThat(nullRef.name).isEqualTo(ResourceName("", RAW, ""))
assertThat(nullRef.id).isNull()
assertThat(nullRef.referenceType).isEqualTo(Reference.Type.RESOURCE)
}
@@ -1,5 +1,6 @@
package com.android.aaptcompiler
import com.android.aaptcompiler.ResourceFile.Type.ProtoXml
import com.android.aaptcompiler.testutils.parseNameOrFail
import com.google.common.truth.Truth.assertThat
import org.junit.Before
@@ -41,7 +42,8 @@ class XmlProcessorTest {
parseNameOrFail("layout/test"),
parse(configDescription),
Source(""),
ResourceFile.Type.ProtoXml)
ProtoXml
)
try {
processor.process(file, input.byteInputStream())
} catch (e: Exception) {
@@ -1,5 +1,6 @@
package com.android.aaptcompiler
import com.android.aaptcompiler.ResourceFile.Type.ProtoXml
import com.android.aaptcompiler.testutils.parseNameOrFail
import com.google.common.truth.Truth
import org.junit.Test
@@ -10,7 +11,8 @@ class XmlResourceBuilderTest {
parseNameOrFail("layout/foo"),
ConfigDescription(),
Source(""),
ResourceFile.Type.ProtoXml)
ProtoXml
)
@Test
fun testBasicXmlFlattening() {
@@ -2,9 +2,8 @@ package com.android.aaptcompiler.android
import com.google.common.truth.Truth
import org.junit.Test
import java.lang.AssertionError
class ResTable_configTest {
class ResTableConfigTest {
@Test
fun testBlockMethods() {
@@ -35,7 +34,8 @@ class ResTable_configTest {
// screenConfig2 block
0x2d2e2f30.hostToDevice(),
// localeNumberSystem block
byteArrayOf(0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38))
byteArrayOf(0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38)
)
Truth.assertThat(config.size).isEqualTo(52)
// Blocks are Little Endian and therefore the lower bits come first in the block. In other words
@@ -107,7 +107,8 @@ class ResTable_configTest {
}
private fun matchTestUnequal(
default: ResTableConfig, first: ResTableConfig, second: ResTableConfig) {
default: ResTableConfig, first: ResTableConfig, second: ResTableConfig
) {
// Default should match everything.
Truth.assertThat(default.match(first)).isTrue()
Truth.assertThat(default.match(second)).isTrue()
@@ -123,7 +124,8 @@ class ResTable_configTest {
}
private fun matchTestOneWay(
default: ResTableConfig, matches: ResTableConfig, noMatch: ResTableConfig) {
default: ResTableConfig, matches: ResTableConfig, noMatch: ResTableConfig
) {
// Default should match everything.
Truth.assertThat(default.match(matches)).isTrue()
Truth.assertThat(default.match(noMatch)).isTrue()
@@ -177,7 +179,8 @@ class ResTable_configTest {
// test that having multiple specified layout values doesn't interfere with matching.
val screenLayout = ResTableConfig(
screenLayout = (ResTableConfig.SCREEN_LAYOUT.SIZE_LARGE.toInt() or
ResTableConfig.SCREEN_LAYOUT.SCREENLONG_YES.toInt()).toByte())
ResTableConfig.SCREEN_LAYOUT.SCREENLONG_YES.toInt()).toByte()
)
Truth.assertThat(screenSize1.match(screenLayout)).isTrue()
Truth.assertThat(screenLayout.match(screenSize1)).isFalse()
@@ -196,7 +199,8 @@ class ResTable_configTest {
val uiMode = ResTableConfig(
uiMode = (ResTableConfig.UI_MODE.NIGHT_YES.toInt() or
ResTableConfig.UI_MODE.TYPE_APPLIANCE.toInt()).toByte())
ResTableConfig.UI_MODE.TYPE_APPLIANCE.toInt()).toByte()
)
// test that having multiple specified uiMode values doesn't interfere with matching.
Truth.assertThat(uiType2.match(uiMode)).isTrue()
@@ -223,7 +227,8 @@ class ResTable_configTest {
val colorMode = ResTableConfig(
colorMode = (ResTableConfig.COLOR_MODE.HDR_NO.toInt() or
ResTableConfig.COLOR_MODE.WIDE_GAMUT_YES.toInt()).toByte())
ResTableConfig.COLOR_MODE.WIDE_GAMUT_YES.toInt()).toByte()
)
// test that having multiple specified colorMode values doesn't interfere with matching.
Truth.assertThat(hdr2.match(colorMode)).isTrue()
@@ -319,19 +324,24 @@ class ResTable_configTest {
@Test
fun testLocaleIsMoreSpecificThan() {
val default = ResTableConfig()
val languageOnly = ResTableConfig(language = byteArrayOf('e'.toByte(), 'n'.toByte()))
val countryOnly = ResTableConfig(country = byteArrayOf('U'.toByte(), 'S'.toByte()))
val languageOnly = ResTableConfig(language = byteArrayOf('e'.code.toByte(), 'n'.code.toByte()))
val countryOnly = ResTableConfig(country = byteArrayOf('U'.code.toByte(), 'S'.code.toByte()))
val languageAndCountry = ResTableConfig(
language = byteArrayOf('e'.toByte(), 'n'.toByte()),
country = byteArrayOf('U'.toByte(), 'S'.toByte()))
language = byteArrayOf('e'.code.toByte(), 'n'.code.toByte()),
country = byteArrayOf('U'.code.toByte(), 'S'.code.toByte())
)
val localeVariant = ResTableConfig(
localeVariant = byteArrayOf(0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08))
localeVariant = byteArrayOf(0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08)
)
val scriptComputed = ResTableConfig(
localeScriptWasComputed = true, localeScript = byteArrayOf(0x01, 0x02, 0x03, 0x04))
localeScriptWasComputed = true, localeScript = byteArrayOf(0x01, 0x02, 0x03, 0x04)
)
val localeScript = ResTableConfig(
localeScript = byteArrayOf(0x01, 0x02, 0x03, 0x04))
localeScript = byteArrayOf(0x01, 0x02, 0x03, 0x04)
)
val numberingSystem = ResTableConfig(
localeNumberSystem = byteArrayOf(0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08))
localeNumberSystem = byteArrayOf(0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08)
)
// configs are consistent with themselves.
testLocaleSpecificityEqual(default, default)
@@ -396,7 +406,7 @@ class ResTable_configTest {
Truth.assertThat(config.isMoreSpecificThan(config)).isFalse()
}
for (i in 0.until(greatestToLeast.size)) {
for (i in greatestToLeast.indices) {
for (j in (i + 1).until(greatestToLeast.size)) {
val greater = greatestToLeast[i]
val lesser = greatestToLeast[j]
@@ -406,7 +416,8 @@ class ResTable_configTest {
} catch (e: AssertionError) {
throw AssertionError(
"Failed with greater = \"$greater\" at index $i and lesser = \"$lesser\" at index $j.",
e)
e
)
}
}
}
@@ -463,7 +474,8 @@ class ResTable_configTest {
screenHeight,
sdkVersion,
minorVersion,
default)
default
)
}
private fun testLocaleCompareTransitive(vararg firstToLast: ResTableConfig) {
@@ -472,7 +484,7 @@ class ResTable_configTest {
Truth.assertThat(config.compareLocales(config)).isEqualTo(0)
}
for (i in 0.until(firstToLast.size)) {
for (i in firstToLast.indices) {
for (j in (i + 1).until(firstToLast.size)) {
val less = firstToLast[i]
val more = firstToLast[j]
@@ -481,7 +493,8 @@ class ResTable_configTest {
Truth.assertThat(more.compareLocales(less)).isGreaterThan(0)
} catch (e: AssertionError) {
throw AssertionError(
"Failed with less = \"$less\" at index $i and more = \"$more\" at index $j.", e)
"Failed with less = \"$less\" at index $i and more = \"$more\" at index $j.", e
)
}
}
}
@@ -493,41 +506,56 @@ class ResTable_configTest {
val defaultWithVariant = ResTableConfig(localeVariant = byteArrayOf(1, 2, 3, 4, 5, 6, 7, 8))
val localeNoCountry1 = ResTableConfig(language = ResTableConfig.ENGLISH)
val localeNoCountryWithScript1 = ResTableConfig(
language = ResTableConfig.ENGLISH, localeScript = byteArrayOf(1, 2, 3, 4))
language = ResTableConfig.ENGLISH, localeScript = byteArrayOf(1, 2, 3, 4)
)
val localeNoCountryWithScript2 = ResTableConfig(
language = ResTableConfig.ENGLISH, localeScript = byteArrayOf(1, 2, 3, 5))
val localeNoCountry2 = ResTableConfig(language = byteArrayOf('f'.toByte(), 'r'.toByte()))
language = ResTableConfig.ENGLISH, localeScript = byteArrayOf(1, 2, 3, 5)
)
val localeNoCountry2 = ResTableConfig(
language = byteArrayOf(
'f'.code.toByte(),
'r'.code.toByte()
)
)
val localeNoCountryWithScript3 = ResTableConfig(
language = byteArrayOf('f'.toByte(), 'r'.toByte()), localeScript = byteArrayOf(1, 2, 3, 4))
language = byteArrayOf('f'.code.toByte(), 'r'.code.toByte()),
localeScript = byteArrayOf(1, 2, 3, 4)
)
val localeNoLang = ResTableConfig(country = ResTableConfig.UNITED_STATES)
val localeBasic = ResTableConfig(
language = ResTableConfig.ENGLISH, country = ResTableConfig.UNITED_STATES)
language = ResTableConfig.ENGLISH, country = ResTableConfig.UNITED_STATES
)
val localeBasicWithScript = ResTableConfig(
language = ResTableConfig.ENGLISH,
country = ResTableConfig.UNITED_STATES,
localeScript = byteArrayOf(1, 2, 3, 4))
localeScript = byteArrayOf(1, 2, 3, 4)
)
val localeBasicWithScriptAndVariant = ResTableConfig(
language = ResTableConfig.ENGLISH,
country = ResTableConfig.UNITED_STATES,
localeScript = byteArrayOf(1, 2, 3, 4),
localeVariant = byteArrayOf(1, 2, 3, 4, 5, 6, 7, 8))
localeVariant = byteArrayOf(1, 2, 3, 4, 5, 6, 7, 8)
)
val localeBasicWithScriptAndVariant2 = ResTableConfig(
language = ResTableConfig.ENGLISH,
country = ResTableConfig.UNITED_STATES,
localeScript = byteArrayOf(1, 2, 3, 4),
localeVariant = byteArrayOf(11, 12, 13, 14, 15, 16, 17, 18))
localeVariant = byteArrayOf(11, 12, 13, 14, 15, 16, 17, 18)
)
val localeBasicWithScriptVariantAndNumberSystem = ResTableConfig(
language = ResTableConfig.ENGLISH,
country = ResTableConfig.UNITED_STATES,
localeScript = byteArrayOf(1, 2, 3, 4),
localeVariant = byteArrayOf(11, 12, 13, 14, 15, 16, 17, 18),
localeNumberSystem = byteArrayOf(1, 2, 3, 4, 5, 6, 7, 8))
localeNumberSystem = byteArrayOf(1, 2, 3, 4, 5, 6, 7, 8)
)
val localeBasicWithScriptVariantAndNumberSystem2 = ResTableConfig(
language = ResTableConfig.ENGLISH,
country = ResTableConfig.UNITED_STATES,
localeScript = byteArrayOf(1, 2, 3, 4),
localeVariant = byteArrayOf(11, 12, 13, 14, 15, 16, 17, 18),
localeNumberSystem = byteArrayOf(11, 12, 13, 14, 15, 16, 17, 18))
localeNumberSystem = byteArrayOf(11, 12, 13, 14, 15, 16, 17, 18)
)
testLocaleCompareTransitive(
default,
@@ -543,14 +571,16 @@ class ResTable_configTest {
localeBasicWithScriptAndVariant,
localeBasicWithScriptAndVariant2,
localeBasicWithScriptVariantAndNumberSystem,
localeBasicWithScriptVariantAndNumberSystem2)
localeBasicWithScriptVariantAndNumberSystem2
)
// Ensure computed scripts do not affect results.
val localeBasicWithComputedScript = ResTableConfig(
language = ResTableConfig.ENGLISH,
country = ResTableConfig.UNITED_STATES,
localeScriptWasComputed = true,
localeScript = byteArrayOf(1, 2, 3, 4))
localeScript = byteArrayOf(1, 2, 3, 4)
)
Truth.assertThat(localeBasic.compareLocales(localeBasicWithComputedScript)).isEqualTo(0)
}
@@ -560,7 +590,7 @@ class ResTable_configTest {
Truth.assertThat(config.compareTo(config)).isEqualTo(0)
}
for (i in 0.until(firstToLast.size)) {
for (i in firstToLast.indices) {
for (j in (i + 1).until(firstToLast.size)) {
val less = firstToLast[i]
val more = firstToLast[j]
@@ -569,7 +599,8 @@ class ResTable_configTest {
Truth.assertThat(more.compareTo(less)).isGreaterThan(0)
} catch (e: AssertionError) {
throw AssertionError(
"Failed with less = \"$less\" at index $i and more = \"$more\" at index $j.", e)
"Failed with less = \"$less\" at index $i and more = \"$more\" at index $j.", e
)
}
}
}
@@ -585,7 +616,8 @@ class ResTable_configTest {
val uiMode = ResTableConfig(uiMode = ResTableConfig.UI_MODE.NIGHT_YES)
val colorMode = ResTableConfig(colorMode = ResTableConfig.COLOR_MODE.WIDE_GAMUT_NO)
val screenLayout2 = ResTableConfig(
screenLayout2 = ResTableConfig.SCREEN_LAYOUT2.SCREENROUND_YES)
screenLayout2 = ResTableConfig.SCREEN_LAYOUT2.SCREENROUND_YES
)
val screenLayout = ResTableConfig(screenLayout = ResTableConfig.SCREEN_LAYOUT.SIZE_LARGE)
val version = ResTableConfig(sdkVersion = 22)
val screenSize = ResTableConfig(screenWidth = 480, screenHeight = 320)
@@ -612,7 +644,8 @@ class ResTable_configTest {
screenType,
screenType2,
locale,
imsi)
imsi
)
}
@Test
@@ -624,7 +657,8 @@ class ResTable_configTest {
sdkVersion = 23,
screenWidth = 480,
screenHeight = 640,
screenLayout = ResTableConfig.SCREEN_LAYOUT.SIZE_LARGE)
screenLayout = ResTableConfig.SCREEN_LAYOUT.SIZE_LARGE
)
Truth.assertThat(default.diff(default)).isEqualTo(0)
Truth.assertThat(first.diff(first)).isEqualTo(0)
@@ -682,14 +716,15 @@ class ResTable_configTest {
}
private fun isBetterLocaleTransitive(
requested: ResTableConfig, vararg leastToGreatest: ResTableConfig) {
requested: ResTableConfig, vararg leastToGreatest: ResTableConfig
) {
for (config in leastToGreatest) {
// no config is better than itself.
Truth.assertThat(config.isLocaleBetterThan(config, requested)).isFalse()
}
for (i in 0.until(leastToGreatest.size)) {
for (i in leastToGreatest.indices) {
for (j in (i + 1).until(leastToGreatest.size)) {
val worse = leastToGreatest[i]
val better = leastToGreatest[j]
@@ -698,7 +733,8 @@ class ResTable_configTest {
Truth.assertThat(better.isLocaleBetterThan(worse, requested)).isTrue()
} catch (e: AssertionError) {
throw AssertionError(
"Failed with worse = \"$worse\" at index $i and better = \"$better\" at index $j.", e)
"Failed with worse = \"$worse\" at index $i and better = \"$better\" at index $j.", e
)
}
}
}
@@ -710,7 +746,8 @@ class ResTable_configTest {
language = ResTableConfig.FILIPINO,
country = byteArrayOf(1, 2),
localeVariant = byteArrayOf(3, 4, 5, 6, 7, 8, 9, 10),
localeNumberSystem = byteArrayOf(11, 12, 13, 14, 15, 16, 17, 18))
localeNumberSystem = byteArrayOf(11, 12, 13, 14, 15, 16, 17, 18)
)
val default = ResTableConfig()
val country = ResTableConfig(country = byteArrayOf(1, 2))
@@ -721,16 +758,19 @@ class ResTable_configTest {
val localeNumberSystem = ResTableConfig(
language = ResTableConfig.FILIPINO,
country = byteArrayOf(1, 2),
localeNumberSystem = byteArrayOf(11, 12, 13, 14, 15, 16, 17, 18))
localeNumberSystem = byteArrayOf(11, 12, 13, 14, 15, 16, 17, 18)
)
val localeVariant = ResTableConfig(
language = ResTableConfig.FILIPINO,
country = byteArrayOf(1, 2),
localeVariant = byteArrayOf(3, 4, 5, 6, 7, 8, 9, 10))
localeVariant = byteArrayOf(3, 4, 5, 6, 7, 8, 9, 10)
)
val almostTotalMatch = ResTableConfig(
language = ResTableConfig.TAGALOG,
country = byteArrayOf(1, 2),
localeVariant = byteArrayOf(3, 4, 5, 6, 7, 8, 9, 10),
localeNumberSystem = byteArrayOf(11, 12, 13, 14, 15, 16, 17, 18))
localeNumberSystem = byteArrayOf(11, 12, 13, 14, 15, 16, 17, 18)
)
isBetterLocaleTransitive(
requested,
@@ -742,13 +782,15 @@ class ResTable_configTest {
localeNumberSystem,
localeVariant,
almostTotalMatch,
requested)
requested
)
// Test that if the locale variant doesn't match it doesn't count.
val localeVariantFail = ResTableConfig(
language = ResTableConfig.FILIPINO,
country = byteArrayOf(1, 2),
localeVariant = byteArrayOf(3, 4, 5, 6, 7, 8, 9, 11))
localeVariant = byteArrayOf(3, 4, 5, 6, 7, 8, 9, 11)
)
Truth.assertThat(localeVariantFail.isLocaleBetterThan(localeNumberSystem, requested)).isFalse()
Truth.assertThat(localeNumberSystem.isLocaleBetterThan(localeVariantFail, requested)).isTrue()
@@ -756,14 +798,15 @@ class ResTable_configTest {
}
private fun isBetterTransitive(
requested: ResTableConfig, vararg leastToGreatest: ResTableConfig) {
requested: ResTableConfig, vararg leastToGreatest: ResTableConfig
) {
for (config in leastToGreatest) {
// no config is better than itself.
Truth.assertThat(config.isBetterThan(config, requested)).isFalse()
}
for (i in 0.until(leastToGreatest.size)) {
for (i in leastToGreatest.indices) {
for (j in (i + 1).until(leastToGreatest.size)) {
val worse = leastToGreatest[i]
val better = leastToGreatest[j]
@@ -772,7 +815,8 @@ class ResTable_configTest {
Truth.assertThat(better.isBetterThan(worse, requested)).isTrue()
} catch (e: AssertionError) {
throw AssertionError(
"Failed with worse = \"$worse\" at index $i and better = \"$better\" at index $j.", e)
"Failed with worse = \"$worse\" at index $i and better = \"$better\" at index $j.", e
)
}
}
}
@@ -807,7 +851,8 @@ class ResTable_configTest {
// screenConfig2 block
0xffffffff.toInt().hostToDevice(),
// localeNumberSystem block
byteArrayOf(0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38))
byteArrayOf(0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38)
)
val default = ResTableConfig()
val minorVersion = ResTableConfig(minorVersion = requested.minorVersion)
@@ -816,17 +861,21 @@ class ResTable_configTest {
val sdkVersion2 = ResTableConfig(sdkVersion = requested.sdkVersion)
val screenSize1 = ResTableConfig(screenWidth = requested.screenWidth)
val screenSize2 = ResTableConfig(
screenWidth = requested.screenWidth, screenHeight = requested.screenHeight)
screenWidth = requested.screenWidth, screenHeight = requested.screenHeight
)
val navigation = ResTableConfig(navigation = requested.navigation)
val keyboard = ResTableConfig(keyboard = requested.keyboard)
val navHidden = ResTableConfig(
inputFlags = (requested.inputFlags.toInt() and
ResTableConfig.INPUT_FLAGS.NAVHIDDEN_MASK).toByte())
ResTableConfig.INPUT_FLAGS.NAVHIDDEN_MASK).toByte()
)
val keysHiddenAlmostMatch = ResTableConfig(
inputFlags = ResTableConfig.INPUT_FLAGS.KEYSHIDDEN_NO)
inputFlags = ResTableConfig.INPUT_FLAGS.KEYSHIDDEN_NO
)
val keysHiddenMatch = ResTableConfig(
inputFlags = (requested.inputFlags.toInt() and
ResTableConfig.INPUT_FLAGS.KEYSHIDDEN_MASK).toByte())
ResTableConfig.INPUT_FLAGS.KEYSHIDDEN_MASK).toByte()
)
val touchscreen = ResTableConfig(touchscreen = requested.touchscreen)
// scaling up is worse than scaling down.
val densityLow = ResTableConfig(density = requested.density - 20)
@@ -834,37 +883,48 @@ class ResTable_configTest {
val densityEqual = ResTableConfig(density = requested.density)
val nightMode = ResTableConfig(
uiMode = (requested.uiMode.toInt() and
ResTableConfig.UI_MODE.NIGHT_MASK).toByte())
ResTableConfig.UI_MODE.NIGHT_MASK).toByte()
)
val uiType = ResTableConfig(
uiMode = (requested.uiMode.toInt() and
ResTableConfig.UI_MODE.TYPE_MASK).toByte())
ResTableConfig.UI_MODE.TYPE_MASK).toByte()
)
val orientation = ResTableConfig(orientation = requested.orientation)
val hdr = ResTableConfig(
colorMode = (requested.colorMode.toInt() and
ResTableConfig.COLOR_MODE.HDR_MASK).toByte())
ResTableConfig.COLOR_MODE.HDR_MASK).toByte()
)
val gamut = ResTableConfig(
colorMode = (requested.colorMode.toInt() and
ResTableConfig.COLOR_MODE.WIDE_GAMUT_MASK).toByte())
ResTableConfig.COLOR_MODE.WIDE_GAMUT_MASK).toByte()
)
val round = ResTableConfig(
screenLayout2 = (requested.screenLayout2.toInt() and
ResTableConfig.SCREEN_LAYOUT2.SCREENROUND_MASK).toByte())
ResTableConfig.SCREEN_LAYOUT2.SCREENROUND_MASK).toByte()
)
val long = ResTableConfig(
screenLayout = (requested.screenLayout.toInt() and
ResTableConfig.SCREEN_LAYOUT.SCREENLONG_MASK).toByte())
ResTableConfig.SCREEN_LAYOUT.SCREENLONG_MASK).toByte()
)
val sizeFlag = ResTableConfig(
screenLayout = (requested.screenLayout.toInt() and
ResTableConfig.SCREEN_LAYOUT.SIZE_MASK).toByte())
ResTableConfig.SCREEN_LAYOUT.SIZE_MASK).toByte()
)
val screenSizeDp1 = ResTableConfig(screenWidthDp = requested.screenWidthDp)
val screenSizeDp2 = ResTableConfig(
screenWidthDp = requested.screenWidthDp, screenHeightDp = requested.screenHeightDp)
screenWidthDp = requested.screenWidthDp, screenHeightDp = requested.screenHeightDp
)
val smallestScreenWidth1 = ResTableConfig(
smallestScreenWidthDp = requested.smallestScreenWidthDp - 20)
smallestScreenWidthDp = requested.smallestScreenWidthDp - 20
)
// Closer to requested will match better.
val smallestScreenWidth2 = ResTableConfig(
smallestScreenWidthDp = requested.smallestScreenWidthDp)
smallestScreenWidthDp = requested.smallestScreenWidthDp
)
val screenDir = ResTableConfig(
screenLayout = (requested.screenLayout.toInt() and
ResTableConfig.SCREEN_LAYOUT.DIR_MASK).toByte())
ResTableConfig.SCREEN_LAYOUT.DIR_MASK).toByte()
)
val mnc = ResTableConfig(mnc = requested.mnc)
val mcc = ResTableConfig(mcc = requested.mcc)
@@ -900,7 +960,8 @@ class ResTable_configTest {
screenDir,
mnc,
mcc,
requested)
requested
)
}
@Test
@@ -924,17 +985,37 @@ class ResTable_configTest {
val config5 =
ResTableConfig(
language = ResTableConfig.ENGLISH,
localeScript = byteArrayOf('l'.toByte(), 'a'.toByte(), 't'.toByte(), 'n'.toByte()))
localeScript = byteArrayOf(
'l'.code.toByte(),
'a'.code.toByte(),
't'.code.toByte(),
'n'.code.toByte()
)
)
Truth.assertThat(config5.toString()).isEqualTo("b+en+latn")
val config6 =
ResTableConfig(
language = ResTableConfig.ENGLISH,
country = ResTableConfig.UNITED_STATES,
localeScript = byteArrayOf('l'.toByte(), 'a'.toByte(), 't'.toByte(), 'n'.toByte()),
localeVariant = byteArrayOf('h'.toByte(), 'i'.toByte(), 0, 0, 0, 0, 0, 0),
localeScript = byteArrayOf(
'l'.code.toByte(),
'a'.code.toByte(),
't'.code.toByte(),
'n'.code.toByte()
),
localeVariant = byteArrayOf('h'.code.toByte(), 'i'.code.toByte(), 0, 0, 0, 0, 0, 0),
localeNumberSystem = byteArrayOf(
't'.toByte(), 'h'.toByte(), 'e'.toByte(), 'r'.toByte(), 'e'.toByte(), 0, 0, 0))
't'.code.toByte(),
'h'.code.toByte(),
'e'.code.toByte(),
'r'.code.toByte(),
'e'.code.toByte(),
0,
0,
0
)
)
Truth.assertThat(config6.toString()).isEqualTo("b+en+latn+US+hi+u+nu+there")
val config7 = ResTableConfig(screenWidth = 480, screenHeight = 640)
@@ -969,16 +1050,27 @@ class ResTable_configTest {
480,
480,
640,
byteArrayOf('l'.toByte(), 'a'.toByte(), 't'.toByte(), 'n'.toByte()),
byteArrayOf('h'.toByte(), 'i'.toByte(), 0, 0, 0, 0, 0, 0),
byteArrayOf('l'.code.toByte(), 'a'.code.toByte(), 't'.code.toByte(), 'n'.code.toByte()),
byteArrayOf('h'.code.toByte(), 'i'.code.toByte(), 0, 0, 0, 0, 0, 0),
ResTableConfig.SCREEN_LAYOUT2.SCREENROUND_YES,
(ResTableConfig.COLOR_MODE.HDR_YES.toInt() or
ResTableConfig.COLOR_MODE.WIDE_GAMUT_YES.toInt()).toByte(),
false,
byteArrayOf('t'.toByte(), 'h'.toByte(), 'e'.toByte(), 'r'.toByte(), 'e'.toByte(), 0, 0, 0))
byteArrayOf(
't'.code.toByte(),
'h'.code.toByte(),
'e'.code.toByte(),
'r'.code.toByte(),
'e'.code.toByte(),
0,
0,
0
)
)
Truth.assertThat(config9.toString()).isEqualTo(
"mcc310-mnc100-b+en+latn+US+hi+u+nu+there-feminine-ldltr-sw480dp-w480dp-h640dp-large-" +
"long-round-widecg-highdr-land-desk-notnight-xxhdpi-finger-keyssoft-qwerty-navhidden-dpad-" +
"v13.2")
"v13.2"
)
}
}
@@ -1,6 +1,7 @@
package com.android.aaptcompiler.proto
import com.android.aapt.Resources
import com.android.aapt.Resources.Attribute.FormatFlags
import com.android.aaptcompiler.ArrayResource
import com.android.aaptcompiler.AttributeResource
import com.android.aaptcompiler.BasicString
@@ -25,6 +26,10 @@ import com.android.aaptcompiler.StyledString
import com.android.aaptcompiler.Visibility
import com.android.aaptcompiler.android.ResStringPool
import com.android.aaptcompiler.android.ResValue
import com.android.aaptcompiler.android.ResValue.DataType.INT_DEC
import com.android.aaptcompiler.android.ResValue.DataType.INT_HEX
import com.android.aaptcompiler.android.ResValue.DataType.NULL
import com.android.aaptcompiler.android.ResValue.NullFormat
import com.android.aaptcompiler.buffer.BigBuffer
import com.android.aaptcompiler.getValue
import com.android.aaptcompiler.makeEmpty
@@ -237,7 +242,8 @@ class ProtoSerializeTest {
testSerializeDeserialize(tryParseBool("true")!!)
// Null type
testSerializeDeserialize(
BinaryPrimitive(ResValue(ResValue.DataType.NULL, ResValue.NullFormat.UNDEFINED)))
BinaryPrimitive(ResValue(NULL, NullFormat.UNDEFINED))
)
// Empty type
testSerializeDeserialize(tryParseNullOrEmpty("@empty") as BinaryPrimitive)
// Int types
@@ -372,7 +378,8 @@ class ProtoSerializeTest {
originalPool.makeRef(
StyleString(
"Hello World!",
listOf(Span("b", 0, 4), Span("i", 6, 10)))),
listOf(Span("b", 0, 4), Span("i", 6, 10)))
),
listOf()),
newPool)
@@ -396,7 +403,8 @@ class ProtoSerializeTest {
overlayable,
OverlayableItem.Policy.SYSTEM or OverlayableItem.Policy.PRODUCT,
"comment",
Source("res/values/overlayable.xml", 19))
Source("res/values/overlayable.xml", 19)
)
val overlayableItem2 = OverlayableItem(overlayable)
@@ -419,10 +427,10 @@ class ProtoSerializeTest {
@Test
fun testAttrSerialization() {
val emptyAttribute = AttributeResource(Resources.Attribute.FormatFlags.ENUM_VALUE)
val emptyAttribute = AttributeResource(FormatFlags.ENUM_VALUE)
testSerializeDeserialize(emptyAttribute)
val attributeExample = AttributeResource(Resources.Attribute.FormatFlags.FLAGS_VALUE)
val attributeExample = AttributeResource(FormatFlags.FLAGS_VALUE)
attributeExample.minInt = 0
attributeExample.maxInt = 1 shl 16
@@ -457,14 +465,16 @@ class ProtoSerializeTest {
styleExampleNoParent.entries.add(
Style.Entry(
Reference(parseResourceName("android:string/foo")!!.resourceName),
BasicString(oldPool.makeRef("Hello there!"))))
BasicString(oldPool.makeRef("Hello there!"))
))
val keyReference = Reference(parseResourceName("android:bool/bar")!!.resourceName)
keyReference.source = Source("res/values/style.xml", 30)
keyReference.comment = "Whether or not the bar state can be active."
styleExampleNoParent.entries.add(
Style.Entry(
keyReference,
tryParseBool("true")))
tryParseBool("true")
))
testSerializeDeserialize(styleExampleNoParent, newPool)
Truth.assertThat(newPool.size()).isEqualTo(1)
@@ -567,7 +577,8 @@ class ProtoSerializeTest {
parseNameOrFail("com.app.a:string/styled"),
ConfigDescription(),
"",
StyledString(table.stringPool.makeRef(styleString), listOf()))).isTrue()
StyledString(table.stringPool.makeRef(styleString), listOf())
)).isTrue()
// Make a resource with different products.
Truth.assertThat(
@@ -575,13 +586,15 @@ class ProtoSerializeTest {
parseNameOrFail("com.app.a:integer/one"),
parse("land"),
"",
BinaryPrimitive(ResValue(ResValue.DataType.INT_DEC, 123)))).isTrue()
BinaryPrimitive(ResValue(INT_DEC, 123))
)).isTrue()
Truth.assertThat(
table.addResource(
parseNameOrFail("com.app.a:integer/one"),
parse("land"),
"tablet",
BinaryPrimitive(ResValue(ResValue.DataType.INT_HEX, 321)))).isTrue()
BinaryPrimitive(ResValue(INT_HEX, 321))
)).isTrue()
// Make a reference with both resource name and resource ID.
// The reference should point to a resource outside of this table to test that both name and id
@@ -595,7 +608,8 @@ class ProtoSerializeTest {
// Make an overlayable resource.
val overlayableItem = OverlayableItem(
Overlayable("OverlayableName", "overlay://theme", Source("res/values/overlayable.xml", 40)),
source = Source("res/values/overlayable.xml", 42))
source = Source("res/values/overlayable.xml", 42)
)
Truth.assertThat(
table.setOverlayable(
parseNameOrFail("com.app.a:integer/overlayable"), overlayableItem)).isTrue()
@@ -20,6 +20,7 @@ package com.itsaky.androidide.aaptcompiler
import com.android.aaptcompiler.BlameLogger
import com.android.aaptcompiler.ResourceCompilerOptions
import com.android.aaptcompiler.ResourceFile
import com.android.aaptcompiler.ResourceFile.Type.ProtoXml
import com.android.aaptcompiler.ResourceName
import com.android.aaptcompiler.XmlProcessor
import com.android.aaptcompiler.compileResource
@@ -27,11 +28,11 @@ import com.android.aaptcompiler.extractPathData
import com.android.utils.StdLogger
import com.android.utils.StdLogger.Level.VERBOSE
import com.itsaky.androidide.utils.FileProvider
import java.nio.file.Paths
import kotlin.io.path.absolute
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import java.nio.file.Paths
import kotlin.io.path.absolute
/** @author Akash Yadav */
@RunWith(RobolectricTestRunner::class)
@@ -40,7 +41,8 @@ class CompilerTest {
@Test
fun `test simple compilatation`() {
val input =
FileProvider.projectRoot().resolve("core/app/src/main/res/layout/activity_editor.xml").toFile()
FileProvider.projectRoot().resolve("core/app/src/main/res/layout/activity_editor.xml")
.toFile()
val output = Paths.get("./build").absolute().normalize().toFile()
println(input)
println(output)
@@ -53,13 +55,14 @@ class CompilerTest {
@Test
fun `test xml processor`() {
val input =
Paths.get(".", "src/test/resources/layout/activity_editor.xml").absolute().normalize().toFile()
Paths.get(".", "src/test/resources/layout/activity_editor.xml").absolute().normalize()
.toFile()
val pathData = extractPathData(input, input.absolutePath)
val resFile = ResourceFile(
ResourceName("", pathData.type!!, pathData.name),
pathData.config,
pathData.source,
ResourceFile.Type.ProtoXml
ProtoXml
)
val processor = XmlProcessor(pathData.source, BlameLogger(StdLogger(VERBOSE)))
processor.process(resFile, input.inputStream().buffered())
-2
View File
@@ -22,8 +22,6 @@ plugins {
id("org.jetbrains.kotlin.jvm")
}
dependencies {
implementation(projects.utilities.shared)
implementation(libs.composite.jaxp)
+1 -1
View File
@@ -27,7 +27,7 @@ plugins {
android {
namespace = "${BuildConfig.packageName}.lsp.xml"
namespace = "${BuildConfig.packageName}.xml.lsp"
}
kapt {
@@ -4,6 +4,7 @@ import com.itsaky.androidide.lsp.models.CodeFormatResult;
import com.itsaky.androidide.lsp.models.FormatCodeParams;
import com.itsaky.androidide.lsp.xml.providers.format.XMLFormatter;
import com.itsaky.androidide.utils.StopWatch;
import org.eclipse.lemminx.dom.DOMParser;
import org.eclipse.lemminx.uriresolver.URIResolverExtensionManager;
import org.slf4j.Logger;
@@ -19,10 +19,7 @@ package com.itsaky.androidide.lsp.xml.providers.completion
import com.android.aaptcompiler.AaptResourceType.STYLEABLE
import com.android.aaptcompiler.ConfigDescription
import com.android.aaptcompiler.Reference
import com.android.aaptcompiler.ResourceGroup
import com.android.aaptcompiler.ResourcePathData
import com.android.aaptcompiler.ResourceTablePackage
import com.android.aaptcompiler.Styleable
import com.itsaky.androidide.lookup.Lookup
import com.itsaky.androidide.lsp.api.ICompletionProvider
@@ -32,6 +29,8 @@ import com.itsaky.androidide.lsp.models.CompletionResult
import com.itsaky.androidide.lsp.models.MatchLevel.NO_MATCH
import com.itsaky.androidide.lsp.xml.utils.XmlUtils.NodeType
import com.itsaky.androidide.lsp.xml.utils.XmlUtils.NodeType.ATTRIBUTE
import com.itsaky.androidide.xml.res.IResourceGroup
import com.itsaky.androidide.xml.res.IResourceTablePackage
import com.itsaky.androidide.xml.widgets.Widget
import com.itsaky.androidide.xml.widgets.WidgetTable
import org.eclipse.lemminx.dom.DOMDocument
@@ -108,7 +107,7 @@ open class AttrCompletionProvider(provider: ICompletionProvider) :
}
val pck = namespace.substringAfter(NAMESPACE_PREFIX)
val packages = mutableSetOf<ResourceTablePackage>()
val packages = mutableSetOf<IResourceTablePackage>()
for (table in tables) {
if (namespace == NAMESPACE_AUTO) {
packages.addAll(table.packages.filter { it.name.isNotBlank() })
@@ -124,7 +123,7 @@ open class AttrCompletionProvider(provider: ICompletionProvider) :
}
protected open fun addFromPackage(
tablePackage: ResourceTablePackage?,
tablePackage: IResourceTablePackage?,
node: DOMNode,
pck: String,
nsPrefix: String,
@@ -173,11 +172,11 @@ open class AttrCompletionProvider(provider: ICompletionProvider) :
}
}
protected open fun hasAttr(prefix: String, ref: Reference): Boolean {
protected open fun hasAttr(prefix: String, ref: com.android.aaptcompiler.Reference): Boolean {
return this.nodeAtCursor.hasAttribute("${prefix}:${ref.name.entry}")
}
protected open fun findNodeStyleables(node: DOMNode, styleables: ResourceGroup): Set<Styleable> {
protected open fun findNodeStyleables(node: DOMNode, styleables: IResourceGroup): Set<Styleable> {
val nodeName = node.nodeName
val widgets = Lookup.getDefault().lookup(WidgetTable.COMPLETION_LOOKUP_KEY) ?: return emptySet()
@@ -205,7 +204,7 @@ open class AttrCompletionProvider(provider: ICompletionProvider) :
}
protected open fun findStyleablesForName(
styleables: ResourceGroup,
styleables: IResourceGroup,
node: DOMNode,
addFromParent: Boolean = false,
suffix: String = ""
@@ -237,7 +236,7 @@ open class AttrCompletionProvider(provider: ICompletionProvider) :
}
protected open fun findLayoutParams(
styleables: ResourceGroup,
styleables: IResourceGroup,
parentNode: DOMNode
): Set<Styleable> {
val result = mutableSetOf<Styleable>()
@@ -257,7 +256,7 @@ open class AttrCompletionProvider(provider: ICompletionProvider) :
}
protected open fun findStyleablesForWidget(
styleables: ResourceGroup,
styleables: IResourceGroup,
widgets: WidgetTable,
widget: Widget,
node: DOMNode,
@@ -302,7 +301,7 @@ open class AttrCompletionProvider(provider: ICompletionProvider) :
}
protected open fun addWidgetStyleable(
styleables: ResourceGroup,
styleables: IResourceGroup,
widget: Widget,
result: MutableSet<Styleable>,
suffix: String = ""
@@ -311,7 +310,7 @@ open class AttrCompletionProvider(provider: ICompletionProvider) :
}
protected open fun addWidgetStyleable(
styleables: ResourceGroup,
styleables: IResourceGroup,
widget: String,
result: MutableSet<Styleable>,
suffix: String = ""
@@ -323,7 +322,7 @@ open class AttrCompletionProvider(provider: ICompletionProvider) :
}
protected open fun addSuperclassStyleables(
styleables: ResourceGroup,
styleables: IResourceGroup,
widgets: WidgetTable,
widget: Widget,
result: MutableSet<Styleable>,
@@ -340,7 +339,7 @@ open class AttrCompletionProvider(provider: ICompletionProvider) :
}
}
protected open fun findStyleableEntry(styleables: ResourceGroup, name: String): Styleable? {
protected open fun findStyleableEntry(styleables: IResourceGroup, name: String): Styleable? {
val value = styleables.findEntry(name)?.findValue(ConfigDescription())?.value
if (value !is Styleable) {
log.warn("Cannot find styleable for {}", name)
@@ -27,7 +27,6 @@ import com.android.aapt.Resources.Attribute.FormatFlags.FLAGS
import com.android.aapt.Resources.Attribute.FormatFlags.INTEGER
import com.android.aapt.Resources.Attribute.FormatFlags.REFERENCE
import com.android.aapt.Resources.Attribute.FormatFlags.STRING
import com.android.aaptcompiler.AaptResourceType
import com.android.aaptcompiler.AaptResourceType.ATTR
import com.android.aaptcompiler.AaptResourceType.BOOL
import com.android.aaptcompiler.AaptResourceType.DIMEN
@@ -35,9 +34,6 @@ import com.android.aaptcompiler.AaptResourceType.UNKNOWN
import com.android.aaptcompiler.AttributeResource
import com.android.aaptcompiler.ConfigDescription
import com.android.aaptcompiler.ResourcePathData
import com.android.aaptcompiler.ResourceTable
import com.android.aaptcompiler.ResourceTablePackage
import com.itsaky.androidide.aapt.findEntries
import com.itsaky.androidide.lsp.api.ICompletionProvider
import com.itsaky.androidide.lsp.models.CompletionItem
import com.itsaky.androidide.lsp.models.CompletionParams
@@ -49,6 +45,8 @@ import com.itsaky.androidide.lsp.xml.edits.QualifiedValueEditHandler
import com.itsaky.androidide.lsp.xml.utils.XmlUtils.NodeType
import com.itsaky.androidide.lsp.xml.utils.XmlUtils.NodeType.ATTRIBUTE_VALUE
import com.itsaky.androidide.lsp.xml.utils.dimensionUnits
import com.itsaky.androidide.xml.res.IResourceTable
import com.itsaky.androidide.xml.res.IResourceTablePackage
import com.itsaky.androidide.xml.resources.ResourceTableRegistry
import com.itsaky.androidide.xml.utils.attrValue_qualifiedRef
import com.itsaky.androidide.xml.utils.attrValue_qualifiedRefWithIncompletePckOrType
@@ -143,7 +141,9 @@ open class AttrValueCompletionProvider(provider: ICompletionProvider) :
if (matcher.matches()) {
val valPck = matcher.group(1)
val typeStr = matcher.group(3)
val valType = AaptResourceType.values().firstOrNull { it.tagName == typeStr } ?: return EMPTY
val valType =
com.android.aaptcompiler.AaptResourceType.values().firstOrNull { it.tagName == typeStr }
?: return EMPTY
val newPrefix = matcher.group(4) ?: ""
addValues(valType, newPrefix, list) { it == valPck }
return CompletionResult(list)
@@ -179,7 +179,9 @@ open class AttrValueCompletionProvider(provider: ICompletionProvider) :
if (matcher.matches()) {
val typeStr = matcher.group(1)
val newPrefix = matcher.group(2) ?: ""
val valType = AaptResourceType.values().firstOrNull { it.tagName == typeStr } ?: return EMPTY
val valType =
com.android.aaptcompiler.AaptResourceType.values().firstOrNull { it.tagName == typeStr }
?: return EMPTY
addValues(valType, newPrefix, list)
return CompletionResult(list)
}
@@ -217,12 +219,13 @@ open class AttrValueCompletionProvider(provider: ICompletionProvider) :
}
}
private fun listResTypes(): List<String> = AaptResourceType.values().map { it.tagName }
private fun listResTypes(): List<String> =
com.android.aaptcompiler.AaptResourceType.values().map { it.tagName }
protected open fun resTableForFindAttr() = platformResourceTable()
private fun findAttr(
tables: Set<ResourceTable>,
tables: Set<IResourceTable>,
namespace: String,
pck: String,
attr: String
@@ -248,7 +251,7 @@ open class AttrValueCompletionProvider(provider: ICompletionProvider) :
}
private fun findAttr(
packages: Collection<ResourceTablePackage>,
packages: Collection<IResourceTablePackage>,
attr: String
): AttributeResource? {
for (pck in packages) {
@@ -272,15 +275,27 @@ open class AttrValueCompletionProvider(provider: ICompletionProvider) :
} else {
// Check for specific attribute formats
if (attr.hasType(STRING)) {
addValues(type = AaptResourceType.STRING, prefix = prefix, result = list)
addValues(
type = com.android.aaptcompiler.AaptResourceType.STRING,
prefix = prefix,
result = list
)
}
if (attr.hasType(INTEGER)) {
addValues(type = AaptResourceType.INTEGER, prefix = prefix, result = list)
addValues(
type = com.android.aaptcompiler.AaptResourceType.INTEGER,
prefix = prefix,
result = list
)
}
if (attr.hasType(COLOR)) {
addValues(type = AaptResourceType.COLOR, prefix = prefix, result = list)
addValues(
type = com.android.aaptcompiler.AaptResourceType.COLOR,
prefix = prefix,
result = list
)
}
if (attr.hasType(BOOLEAN)) {
@@ -294,7 +309,11 @@ open class AttrValueCompletionProvider(provider: ICompletionProvider) :
}
if (attr.hasType(INTEGER)) {
addValues(type = AaptResourceType.INTEGER, prefix = prefix, result = list)
addValues(
type = com.android.aaptcompiler.AaptResourceType.INTEGER,
prefix = prefix,
result = list
)
}
if (attr.hasType(ENUM) || attr.hasType(FLAGS)) {
@@ -333,7 +352,7 @@ open class AttrValueCompletionProvider(provider: ICompletionProvider) :
}
private fun completeReferences(prefix: String, list: MutableList<CompletionItem>) {
for (value in AaptResourceType.values()) {
for (value in com.android.aaptcompiler.AaptResourceType.values()) {
if (value == UNKNOWN) {
continue
}
@@ -343,7 +362,7 @@ open class AttrValueCompletionProvider(provider: ICompletionProvider) :
}
private fun addValues(
type: AaptResourceType,
type: com.android.aaptcompiler.AaptResourceType,
prefix: String,
result: MutableList<CompletionItem>,
checkPck: (String) -> Boolean = { true }
@@ -369,20 +388,20 @@ open class AttrValueCompletionProvider(provider: ICompletionProvider) :
.toHashSet()
entries.forEach { pair ->
pair.second?.forEach {
pair.second?.forEach { entry ->
result.add(
createAttrValueCompletionItem(
pair.first,
type.tagName,
it.name,
matchLevel(it.name, prefix)
entry.name,
matchLevel(entry.name, prefix)
)
)
}
}
}
override fun findResourceTables(nsUri: String?): Set<ResourceTable> {
override fun findResourceTables(nsUri: String?): Set<IResourceTable> {
// When completing values, all namespaces must be included
val tables = HashSet(findAllModuleResourceTables())
@@ -17,9 +17,7 @@
package com.itsaky.androidide.lsp.xml.providers.completion
import com.android.aaptcompiler.Reference
import com.android.aaptcompiler.ResourcePathData
import com.android.aaptcompiler.ResourceTable
import com.itsaky.androidide.lookup.Lookup
import com.itsaky.androidide.lsp.api.ICompletionProvider
import com.itsaky.androidide.lsp.api.describeSnippet
@@ -41,6 +39,7 @@ import com.itsaky.androidide.lsp.xml.utils.XmlUtils.NodeType
import com.itsaky.androidide.lsp.xml.utils.XmlUtils.NodeType.ATTRIBUTE
import com.itsaky.androidide.lsp.xml.utils.XmlUtils.NodeType.ATTRIBUTE_VALUE
import com.itsaky.androidide.utils.DocumentUtils
import com.itsaky.androidide.xml.res.IResourceTable
import com.itsaky.androidide.xml.resources.ResourceTableRegistry
import org.eclipse.lemminx.dom.DOMAttr
import org.eclipse.lemminx.dom.DOMDocument
@@ -163,7 +162,7 @@ abstract class IXmlCompletionProvider(private val provider: ICompletionProvider)
* @param matchLevel The match level.
*/
protected open fun createAttrCompletionItem(
attr: Reference,
attr: com.android.aaptcompiler.Reference,
partial: String,
resPkg: String,
nsPrefix: String,
@@ -252,7 +251,7 @@ abstract class IXmlCompletionProvider(private val provider: ICompletionProvider)
}
}
protected open fun findResourceTables(nsUri: String?): Set<ResourceTable> {
protected open fun findResourceTables(nsUri: String?): Set<IResourceTable> {
if (nsUri.isNullOrBlank()) {
return emptySet()
}
@@ -287,11 +286,11 @@ abstract class IXmlCompletionProvider(private val provider: ICompletionProvider)
return setOf(table)
}
protected open fun findAllModuleResourceTables(): Set<ResourceTable> {
protected open fun findAllModuleResourceTables(): Set<IResourceTable> {
val lookup = Lookup.getDefault()
val sourceResTables = lookup.lookup(ResourceTableRegistry.COMPLETION_MODULE_RES) ?: emptySet()
val depResTables = lookup.lookup(ResourceTableRegistry.COMPLETION_DEP_RES) ?: emptySet()
return mutableSetOf<ResourceTable>().also {
return mutableSetOf<IResourceTable>().also {
it.addAll(sourceResTables)
it.addAll(depResTables)
}
@@ -18,11 +18,11 @@
package com.itsaky.androidide.lsp.xml.providers.completion.common
import com.android.aaptcompiler.ConfigDescription
import com.android.aaptcompiler.ResourceGroup
import com.android.aaptcompiler.Styleable
import com.itsaky.androidide.lsp.api.ICompletionProvider
import com.itsaky.androidide.lsp.xml.providers.completion.AttrCompletionProvider
import com.itsaky.androidide.lsp.xml.utils.ITagTransformer
import com.itsaky.androidide.xml.res.IResourceGroup
import org.eclipse.lemminx.dom.DOMNode
/**
@@ -37,7 +37,7 @@ open class CommonAttrCompletionProvider(
provider: ICompletionProvider
) : AttrCompletionProvider(provider) {
override fun findNodeStyleables(node: DOMNode, styleables: ResourceGroup): Set<Styleable> {
override fun findNodeStyleables(node: DOMNode, styleables: IResourceGroup): Set<Styleable> {
val name = node.nodeName
val styleable =
styleables
@@ -17,11 +17,11 @@
package com.itsaky.androidide.lsp.xml.providers.completion.etc
import com.android.aaptcompiler.ResourceGroup
import com.android.aaptcompiler.Styleable
import com.itsaky.androidide.lsp.api.ICompletionProvider
import com.itsaky.androidide.lsp.xml.providers.completion.AttrCompletionProvider
import com.itsaky.androidide.lsp.xml.utils.ITagTransformer
import com.itsaky.androidide.xml.res.IResourceGroup
import org.eclipse.lemminx.dom.DOMNode
/**
@@ -35,7 +35,7 @@ class InheritingAttrCompletionProvider(
provider: ICompletionProvider
) : AttrCompletionProvider(provider) {
override fun findNodeStyleables(node: DOMNode, styleables: ResourceGroup): Set<Styleable> {
override fun findNodeStyleables(node: DOMNode, styleables: IResourceGroup): Set<Styleable> {
val nodeStyleables = mutableSetOf<Styleable>()
val name = node.nodeName
val entryName = tagTransform.transform(name, nodeAtCursor.parentNode?.nodeName ?: "")
@@ -18,7 +18,6 @@
package com.itsaky.androidide.lsp.xml.providers.completion.manifest
import com.android.aaptcompiler.ConfigDescription
import com.android.aaptcompiler.ResourceGroup
import com.android.aaptcompiler.ResourcePathData
import com.android.aaptcompiler.Styleable
import com.itsaky.androidide.lsp.api.ICompletionProvider
@@ -28,6 +27,7 @@ import com.itsaky.androidide.lsp.xml.providers.completion.canCompleteManifest
import com.itsaky.androidide.lsp.xml.providers.completion.manifestResourceTable
import com.itsaky.androidide.lsp.xml.providers.completion.transformToEntryName
import com.itsaky.androidide.lsp.xml.utils.XmlUtils.NodeType
import com.itsaky.androidide.xml.res.IResourceGroup
import org.eclipse.lemminx.dom.DOMNode
/**
@@ -44,10 +44,11 @@ class ManifestAttrCompletionProvider(provider: ICompletionProvider) :
override fun findResourceTables(nsUri: String?) = manifestResourceTable()
override fun findNodeStyleables(node: DOMNode, styleables: ResourceGroup): Set<Styleable> {
override fun findNodeStyleables(node: DOMNode, styleables: IResourceGroup): Set<Styleable> {
val name = node.nodeName
val styleable =
styleables.findEntry(transformToEntryName(name, MANIFEST_TAG_PREFIX))?.findValue(ConfigDescription())?.value
styleables.findEntry(transformToEntryName(name, MANIFEST_TAG_PREFIX))
?.findValue(ConfigDescription())?.value
if (styleable != null && styleable is Styleable) {
return setOf(styleable)
}
@@ -25,7 +25,6 @@ import com.android.SdkConstants.TAG_INTENT_FILTER
import com.android.SdkConstants.TAG_RECEIVER
import com.android.SdkConstants.TAG_SERVICE
import com.android.aaptcompiler.ResourcePathData
import com.android.aaptcompiler.ResourceTable
import com.itsaky.androidide.lookup.Lookup
import com.itsaky.androidide.lsp.api.ICompletionProvider
import com.itsaky.androidide.lsp.models.ClassCompletionData
@@ -41,9 +40,10 @@ import com.itsaky.androidide.lsp.xml.providers.completion.AttrValueCompletionPro
import com.itsaky.androidide.lsp.xml.providers.completion.manifestResourceTable
import com.itsaky.androidide.lsp.xml.providers.completion.match
import com.itsaky.androidide.lsp.xml.utils.XmlUtils.NodeType
import com.itsaky.androidide.projects.android.AndroidModule
import com.itsaky.androidide.projects.ModuleProject
import com.itsaky.androidide.projects.android.AndroidModule
import com.itsaky.androidide.xml.permissions.Permission
import com.itsaky.androidide.xml.res.IResourceTable
import com.itsaky.androidide.xml.resources.ResourceTableRegistry
import org.eclipse.lemminx.dom.DOMDocument
@@ -74,7 +74,7 @@ class ManifestAttrValueCompletionProvider(provider: ICompletionProvider) :
return super.doComplete(params, pathData, document, type, prefix)
}
override fun resTableForFindAttr(): ResourceTable? {
override fun resTableForFindAttr(): IResourceTable? {
return manifestResourceTable().firstOrNull()
}
@@ -190,7 +190,7 @@ class ManifestAttrValueCompletionProvider(provider: ICompletionProvider) :
?: throw IllegalStateException("No module project provided")
}
override fun findResourceTables(nsUri: String?): Set<ResourceTable> {
override fun findResourceTables(nsUri: String?): Set<IResourceTable> {
val tables = manifestResourceTable().toMutableSet()
if (nsUri.isNullOrBlank()) {
return tables
@@ -19,7 +19,6 @@ package com.itsaky.androidide.lsp.xml.providers.completion.manifest
import com.android.aaptcompiler.AaptResourceType.STYLEABLE
import com.android.aaptcompiler.ResourcePathData
import com.itsaky.androidide.aapt.findEntries
import com.itsaky.androidide.lookup.Lookup
import com.itsaky.androidide.lsp.api.ICompletionProvider
import com.itsaky.androidide.lsp.models.CompletionItem
@@ -19,10 +19,10 @@ package com.itsaky.androidide.lsp.xml.providers.completion
import com.android.SdkConstants.ANDROID_MANIFEST_XML
import com.android.aaptcompiler.ResourcePathData
import com.android.aaptcompiler.ResourceTable
import com.itsaky.androidide.lookup.Lookup
import com.itsaky.androidide.lsp.xml.utils.XmlUtils.NodeType
import com.itsaky.androidide.utils.VMUtils
import com.itsaky.androidide.xml.res.IResourceTable
import com.itsaky.androidide.xml.resources.ResourceTableRegistry
const val MANIFEST_TAG_PREFIX = "AndroidManifest"
@@ -34,8 +34,9 @@ fun canCompleteManifest(pathData: ResourcePathData, type: NodeType): Boolean {
pathData.file.name.endsWith("_template.xml"))
}
fun manifestResourceTable(): Set<ResourceTable> {
fun manifestResourceTable(): Set<IResourceTable> {
return setOf(
Lookup.getDefault().lookup(ResourceTableRegistry.COMPLETION_MANIFEST_ATTR_RES) ?: return emptySet()
Lookup.getDefault().lookup(ResourceTableRegistry.COMPLETION_MANIFEST_ATTR_RES)
?: return emptySet()
)
}
@@ -17,11 +17,11 @@
package com.itsaky.androidide.lsp.xml.providers.completion
import com.android.aaptcompiler.ResourceTable
import com.itsaky.androidide.lookup.Lookup
import com.itsaky.androidide.lsp.models.CompletionItem.Companion.matchLevel
import com.itsaky.androidide.lsp.models.MatchLevel
import com.itsaky.androidide.lsp.models.MatchLevel.NO_MATCH
import com.itsaky.androidide.xml.res.IResourceTable
import com.itsaky.androidide.xml.resources.ResourceTableRegistry
import org.eclipse.lemminx.dom.DOMNode
import kotlin.math.min
@@ -36,7 +36,7 @@ fun match(simpleName: String, qualifiedName: String, prefix: String): MatchLevel
return MatchLevel.values()[min(simpleNameMatchLevel.ordinal, nameMatchLevel.ordinal)]
}
fun platformResourceTable(): ResourceTable? {
fun platformResourceTable(): IResourceTable? {
return Lookup.getDefault().lookup(ResourceTableRegistry.COMPLETION_FRAMEWORK_RES)
}
@@ -46,7 +46,6 @@ fun findAllNamespaces(node: DOMNode): MutableSet<Pair<String, String>> {
while (curr != null && !curr.isOwnerDocument) {
@Suppress("SENSELESS_COMPARISON") // attributes might be null. Ignore warning
if (curr.attributes == null) {
curr = curr.parentNode
continue
@@ -29,10 +29,12 @@
package com.itsaky.androidide.lsp.xml.providers.format;
import com.itsaky.androidide.lsp.models.TextEdit;
import java.util.List;
import org.eclipse.lemminx.dom.DOMAttr;
import org.eclipse.lemminx.utils.StringUtils;
import java.util.List;
/**
* DOM attribute formatter.
*
@@ -30,9 +30,11 @@ package com.itsaky.androidide.lsp.xml.providers.format;
import com.itsaky.androidide.lsp.models.TextEdit;
import com.itsaky.androidide.preferences.internal.EditorPreferences;
import java.util.List;
import org.eclipse.lemminx.dom.DOMCDATASection;
import java.util.List;
/** DOM CDATA section formatter. */
public class DOMCDATAFormatter {
private final XMLFormatterDocumentNew formatterDocument;
@@ -30,9 +30,11 @@ package com.itsaky.androidide.lsp.xml.providers.format;
import com.itsaky.androidide.lsp.models.TextEdit;
import com.itsaky.androidide.preferences.internal.EditorPreferences;
import java.util.List;
import org.eclipse.lemminx.dom.DOMComment;
import java.util.List;
/** DOM comment formatter. */
public class DOMCommentFormatter {
@@ -29,7 +29,7 @@
package com.itsaky.androidide.lsp.xml.providers.format;
import com.itsaky.androidide.lsp.models.TextEdit;
import java.util.List;
import org.eclipse.lemminx.dom.DOMDocumentType;
import org.eclipse.lemminx.dom.DOMNode;
import org.eclipse.lemminx.dom.DTDAttlistDecl;
@@ -38,6 +38,8 @@ import org.eclipse.lemminx.dom.DTDDeclParameter;
import org.eclipse.lemminx.utils.StringUtils;
import org.w3c.dom.Node;
import java.util.List;
/**
* DOM docType formatter.
*
@@ -29,13 +29,15 @@
package com.itsaky.androidide.lsp.xml.providers.format;
import com.itsaky.androidide.lsp.models.TextEdit;
import java.util.List;
import org.eclipse.lemminx.commons.BadLocationException;
import org.eclipse.lemminx.dom.DOMAttr;
import org.eclipse.lemminx.dom.DOMElement;
import org.eclipse.lemminx.dom.builder.EmptyElements;
import org.eclipse.lemminx.utils.StringUtils;
import java.util.List;
/**
* DOM element formatter.
*
@@ -29,10 +29,12 @@
package com.itsaky.androidide.lsp.xml.providers.format;
import com.itsaky.androidide.lsp.models.TextEdit;
import java.util.List;
import org.eclipse.lemminx.dom.DOMAttr;
import org.eclipse.lemminx.dom.DOMProcessingInstruction;
import java.util.List;
/**
* DOM processing instruction formatter.
*
@@ -29,9 +29,11 @@
package com.itsaky.androidide.lsp.xml.providers.format;
import com.itsaky.androidide.lsp.models.TextEdit;
import java.util.List;
import org.eclipse.lemminx.dom.DOMText;
import java.util.List;
/**
* DOM text formatter.
*
@@ -31,14 +31,16 @@ package com.itsaky.androidide.lsp.xml.providers.format;
import com.itsaky.androidide.lsp.models.TextEdit;
import com.itsaky.androidide.models.Position;
import com.itsaky.androidide.models.Range;
import org.eclipse.lemminx.commons.BadLocationException;
import org.eclipse.lemminx.commons.TextDocument;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import org.eclipse.lemminx.commons.BadLocationException;
import org.eclipse.lemminx.commons.TextDocument;
/**
* Utilities for {@link TextEdit}.
@@ -19,12 +19,14 @@ package com.itsaky.androidide.lsp.xml.providers.format;
import com.itsaky.androidide.lsp.models.TextEdit;
import com.itsaky.androidide.models.Range;
import java.util.List;
import org.eclipse.lemminx.commons.BadLocationException;
import org.eclipse.lemminx.dom.DOMDocument;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
/**
* XML formatter support.
*/
@@ -33,8 +33,7 @@ import com.itsaky.androidide.lsp.xml.utils.XMLBuilder;
import com.itsaky.androidide.models.Position;
import com.itsaky.androidide.models.Range;
import com.itsaky.androidide.preferences.internal.EditorPreferences;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.lemminx.commons.BadLocationException;
import org.eclipse.lemminx.commons.TextDocument;
import org.eclipse.lemminx.dom.DOMAttr;
@@ -52,6 +51,9 @@ import org.eclipse.lemminx.dom.DTDDeclNode;
import org.eclipse.lemminx.dom.DTDDeclParameter;
import org.eclipse.lemminx.dom.builder.EmptyElements;
import java.util.ArrayList;
import java.util.List;
/**
* Default XML formatter which generates one text edit by rewriting the DOM node which must be
* formatted.
@@ -34,10 +34,7 @@ import com.itsaky.androidide.lsp.xml.models.XMLServerSettings;
import com.itsaky.androidide.models.Position;
import com.itsaky.androidide.models.Range;
import com.itsaky.androidide.preferences.internal.EditorPreferences;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.eclipse.lemminx.commons.BadLocationException;
import org.eclipse.lemminx.commons.TextDocument;
import org.eclipse.lemminx.dom.DOMAttr;
@@ -54,6 +51,11 @@ import org.eclipse.lsp4j.jsonrpc.CancelChecker;
import org.w3c.dom.Node;
import org.w3c.dom.Text;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Experimental XML formatter which generates several text edit to remove, add, update spaces /
* indent.
@@ -28,6 +28,7 @@
package com.itsaky.androidide.lsp.xml.utils;
import com.itsaky.androidide.lsp.xml.models.XMLServerSettings;
import org.eclipse.lemminx.dom.builder.BaseXmlFormattingOptions;
import org.eclipse.lemminx.dom.builder.XmlBuilder;
+1
View File
@@ -0,0 +1 @@
/build
+56
View File
@@ -0,0 +1,56 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
import com.google.protobuf.gradle.id
import com.itsaky.androidide.build.config.BuildConfig
plugins {
id("com.android.library")
id("kotlin-android")
id("com.google.protobuf")
}
android {
namespace = "${BuildConfig.packageName}.xml.resapi"
}
protobuf {
protoc {
artifact = "com.google.protobuf:protoc:4.27.0"
}
generateProtoTasks {
all().forEach {
it.builtins {
id("java") {
option("lite")
}
}
}
}
}
dependencies {
api(libs.aapt2.annotations)
api(libs.aapt2.common)
api(libs.google.protobuf)
api(libs.composite.layoutlibApi)
api(libs.composite.jaxp)
implementation(projects.utilities.shared)
}
@@ -0,0 +1,137 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.android.aaptcompiler
import com.android.aaptcompiler.AaptResourceType.ANIM
import com.android.aaptcompiler.AaptResourceType.ANIMATOR
import com.android.aaptcompiler.AaptResourceType.ARRAY
import com.android.aaptcompiler.AaptResourceType.ATTR
import com.android.aaptcompiler.AaptResourceType.ATTR_PRIVATE
import com.android.aaptcompiler.AaptResourceType.BOOL
import com.android.aaptcompiler.AaptResourceType.COLOR
import com.android.aaptcompiler.AaptResourceType.CONFIG_VARYING
import com.android.aaptcompiler.AaptResourceType.DIMEN
import com.android.aaptcompiler.AaptResourceType.DRAWABLE
import com.android.aaptcompiler.AaptResourceType.FONT
import com.android.aaptcompiler.AaptResourceType.FRACTION
import com.android.aaptcompiler.AaptResourceType.ID
import com.android.aaptcompiler.AaptResourceType.INTEGER
import com.android.aaptcompiler.AaptResourceType.INTERPOLATOR
import com.android.aaptcompiler.AaptResourceType.LAYOUT
import com.android.aaptcompiler.AaptResourceType.MACRO
import com.android.aaptcompiler.AaptResourceType.MENU
import com.android.aaptcompiler.AaptResourceType.MIPMAP
import com.android.aaptcompiler.AaptResourceType.NAVIGATION
import com.android.aaptcompiler.AaptResourceType.PLURALS
import com.android.aaptcompiler.AaptResourceType.RAW
import com.android.aaptcompiler.AaptResourceType.STRING
import com.android.aaptcompiler.AaptResourceType.STYLE
import com.android.aaptcompiler.AaptResourceType.STYLEABLE
import com.android.aaptcompiler.AaptResourceType.TRANSITION
import com.android.aaptcompiler.AaptResourceType.XML
private const val ANIM_TAG = "anim"
private const val ANIMATOR_TAG = "animator"
private const val ARRAY_TAG = "array"
private const val ATTR_TAG = "attr"
private const val ATTR_PRIVATE_TAG = "^attr-private"
private const val BOOL_TAG = "bool"
private const val COLOR_TAG = "color"
private const val CONFIG_VARYING_TAG = "configVarying"
private const val DIMEN_TAG = "dimen"
private const val DRAWABLE_TAG = "drawable"
private const val FONT_TAG = "font"
private const val FRACTION_TAG = "fraction"
private const val ID_TAG = "id"
private const val INTEGER_TAG = "integer"
private const val INTERPOLATOR_TAG = "interpolator"
private const val LAYOUT_TAG = "layout"
private const val MACRO_TAG = "macro"
private const val MENU_TAG = "menu"
private const val MIPMAP_TAG = "mipmap"
private const val NAVIGATION_TAG = "navigation"
private const val PLURALS_TAG = "plurals"
private const val RAW_TAG = "raw"
private const val STRING_TAG = "string"
private const val STYLE_TAG = "style"
private const val STYLEABLE_TAG = "styleable"
private const val TRANSITION_TAG = "transition"
private const val XML_TAG = "xml"
fun resourceTypeFromTag(tag: String) =
when(tag) {
ANIM_TAG -> ANIM
ANIMATOR_TAG -> ANIMATOR
ARRAY_TAG -> ARRAY
ATTR_TAG -> ATTR
ATTR_PRIVATE_TAG -> ATTR_PRIVATE
BOOL_TAG -> BOOL
COLOR_TAG -> COLOR
CONFIG_VARYING_TAG -> CONFIG_VARYING
DIMEN_TAG -> DIMEN
DRAWABLE_TAG -> DRAWABLE
FONT_TAG -> FONT
FRACTION_TAG -> FRACTION
ID_TAG -> ID
INTEGER_TAG -> INTEGER
INTERPOLATOR_TAG -> INTERPOLATOR
LAYOUT_TAG -> LAYOUT
MACRO_TAG -> MACRO
MENU_TAG -> MENU
MIPMAP_TAG -> MIPMAP
NAVIGATION_TAG -> NAVIGATION
PLURALS_TAG -> PLURALS
RAW_TAG -> RAW
STRING_TAG -> STRING
STYLE_TAG -> STYLE
STYLEABLE_TAG -> STYLEABLE
TRANSITION_TAG -> TRANSITION
XML_TAG -> XML
else -> null
}
enum class AaptResourceType(val tagName: String) {
ANIM(ANIM_TAG),
ANIMATOR(ANIMATOR_TAG),
ARRAY(ARRAY_TAG),
ATTR(ATTR_TAG),
ATTR_PRIVATE(ATTR_PRIVATE_TAG),
BOOL(BOOL_TAG),
COLOR(COLOR_TAG),
CONFIG_VARYING(CONFIG_VARYING_TAG),
DIMEN(DIMEN_TAG),
DRAWABLE(DRAWABLE_TAG),
FONT(FONT_TAG),
FRACTION(FRACTION_TAG),
ID(ID_TAG),
INTEGER(INTEGER_TAG),
INTERPOLATOR(INTERPOLATOR_TAG),
LAYOUT(LAYOUT_TAG),
MACRO(MACRO_TAG),
MENU(MENU_TAG),
MIPMAP(MIPMAP_TAG),
NAVIGATION(NAVIGATION_TAG),
PLURALS(PLURALS_TAG),
RAW(RAW_TAG),
STRING(STRING_TAG),
STYLE(STYLE_TAG),
STYLEABLE(STYLEABLE_TAG),
TRANSITION(TRANSITION_TAG),
UNKNOWN(""),
XML(XML_TAG)
}
@@ -1,3 +1,20 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
/*
* Copyright (C) 2020 The Android Open Source Project
*
@@ -22,14 +39,14 @@ import com.android.utils.ILogger
import jaxp.xml.stream.Location
import java.io.File
internal fun blameSource(
fun blameSource(
source: Source,
line: Int? = source.line,
column: Int? = null
): BlameLogger.Source =
BlameLogger.Source(source.path, line ?: -1, column ?: -1)
internal fun blameSource(
fun blameSource(
source: Source,
location: Location
): BlameLogger.Source =
@@ -126,7 +143,7 @@ class BlameLogger(
logger.verbose(message)
}
internal fun getOutputSource(source: Source): Source {
fun getOutputSource(source: Source): Source {
return getOriginalSource(
source.copy(sourcePath = userVisibleSourceTransform(source.sourcePath))
)
@@ -0,0 +1,248 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.android.aaptcompiler
import com.android.aaptcompiler.android.ResTableConfig
/**
* @author Akash Yadav
*/
class ConfigDescription(base: ResTableConfig = ResTableConfig()): ResTableConfig(base) {
override fun equals(other: Any?): Boolean {
if (other is ConfigDescription) {
return compareTo(other) == 0
}
return false
}
private fun compatible(first: Int, second: Int) = first == 0 || second == 0 || first == second
}
const val WILDCARD_NAME: String = "any"
fun parse(config: String): ConfigDescription {
val parts = config.split('-').map { it.lowercase() }
var index = 0
val configDescription = ConfigDescription()
val locale = LocaleValue()
if (parts.isEmpty() || config.isEmpty()) {
return applyVersionForCompatibility(configDescription)
}
if (parseMcc(parts[index], configDescription)) {
index++
if (index == parts.size) {
return applyVersionForCompatibility(configDescription)
}
}
if (parseMnc(parts[index], configDescription)) {
index++
if (index == parts.size) {
return applyVersionForCompatibility(configDescription)
}
}
// Locale spans a few '-' separators, so we let it control the index
index += locale.initFromParts(parts, index)
locale.writeTo(configDescription)
if (index == parts.size) {
return configDescription
}
if (parseGrammaticalInflection(parts[index], configDescription)) {
index++
if (index == parts.size) {
return applyVersionForCompatibility(configDescription)
}
}
if (parseLayoutDirection(parts[index], configDescription)) {
index++
if (index == parts.size) {
return applyVersionForCompatibility(configDescription)
}
}
if (parseSmallestScreenWidthDp(parts[index], configDescription)) {
index++
if (index == parts.size) {
return applyVersionForCompatibility(configDescription)
}
}
if (parseScreenWidthDp(parts[index], configDescription)) {
index++
if (index == parts.size) {
return applyVersionForCompatibility(configDescription)
}
}
if (parseScreenHeightDp(parts[index], configDescription)) {
index++
if (index == parts.size) {
return applyVersionForCompatibility(configDescription)
}
}
if (parseScreenLayoutSize(parts[index], configDescription)) {
index++
if (index == parts.size) {
return applyVersionForCompatibility(configDescription)
}
}
if (parseScreenLayoutLong(parts[index], configDescription)) {
index++
if (index == parts.size) {
return applyVersionForCompatibility(configDescription)
}
}
if (parseScreenRound(parts[index], configDescription)) {
index++
if (index == parts.size) {
return applyVersionForCompatibility(configDescription)
}
}
if (parseWideColorGamut(parts[index], configDescription)) {
index++
if (index == parts.size) {
return applyVersionForCompatibility(configDescription)
}
}
if (parseHdr(parts[index], configDescription)) {
index++
if (index == parts.size) {
return applyVersionForCompatibility(configDescription)
}
}
if (parseOrientation(parts[index], configDescription)) {
index++
if (index == parts.size) {
return applyVersionForCompatibility(configDescription)
}
}
if (parseUiModeType(parts[index], configDescription)) {
index++
if (index == parts.size) {
return applyVersionForCompatibility(configDescription)
}
}
if (parseUiModeNight(parts[index], configDescription)) {
index++
if (index == parts.size) {
return applyVersionForCompatibility(configDescription)
}
}
if (parseDensity(parts[index], configDescription)) {
index++
if (index == parts.size) {
return applyVersionForCompatibility(configDescription)
}
}
if (parseTouchscreen(parts[index], configDescription)) {
index++
if (index == parts.size) {
return applyVersionForCompatibility(configDescription)
}
}
if (parseKeysHidden(parts[index], configDescription)) {
index++
if (index == parts.size) {
return applyVersionForCompatibility(configDescription)
}
}
if (parseKeyboard(parts[index], configDescription)) {
index++
if (index == parts.size) {
return applyVersionForCompatibility(configDescription)
}
}
if (parseNavHidden(parts[index], configDescription)) {
index++
if (index == parts.size) {
return applyVersionForCompatibility(configDescription)
}
}
if (parseNavigation(parts[index], configDescription)) {
index++
if (index == parts.size) {
return applyVersionForCompatibility(configDescription)
}
}
if (parseScreenSize(parts[index], configDescription)) {
index++
if (index == parts.size) {
return applyVersionForCompatibility(configDescription)
}
}
if (parseVersion(parts[index], configDescription)) {
index++
if (index == parts.size) {
return applyVersionForCompatibility(configDescription)
}
}
throw error("Unrecognized part '${parts[index]}' in configuration $config")
}
private fun applyVersionForCompatibility(config: ConfigDescription): ConfigDescription {
val minSdk = when {
config.uiModeType() == ResTableConfig.UI_MODE.TYPE_VR_HEADSET ||
config.wideColorGamut() != ResTableConfig.COLOR_MODE.WIDE_GAMUT_ANY ||
config.hdr() != ResTableConfig.COLOR_MODE.HDR_ANY -> SDKConstants.SDK_O
config.layoutRound() != 0.toByte() -> SDKConstants.SDK_MARSHMALLOW
config.density == ResTableConfig.DENSITY.ANY -> SDKConstants.SDK_LOLLIPOP
config.smallestScreenWidthDp != 0 ||
config.screenWidthDp != 0 ||
config.screenHeightDp != 0 -> SDKConstants.SDK_HONEYCOMB_MR2
config.uiModeType() != ResTableConfig.UI_MODE.TYPE_ANY ||
config.uiModeNight() != ResTableConfig.UI_MODE.NIGHT_ANY -> SDKConstants.SDK_FROYO
config.layoutSize() != ResTableConfig.SCREEN_LAYOUT.SIZE_ANY ||
config.layoutLong() != ResTableConfig.SCREEN_LAYOUT.SCREENLONG_ANY ||
config.density != ResTableConfig.DENSITY.DEFAULT -> SDKConstants.SDK_DONUT
config.grammaticalInflection != ResTableConfig.GRAMMATICAL_GENDER.ANY -> SDKConstants.SDK_U
else -> 0
}
if (minSdk > config.sdkVersion) {
config.sdkVersion = minSdk
}
return config
}
@@ -1,3 +1,20 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
/*
* Copyright (C) 2019 The Android Open Source Project
*
@@ -18,6 +35,11 @@ package com.android.aaptcompiler
import com.android.aaptcompiler.android.AConfiguration
import com.android.aaptcompiler.android.ResTableConfig
import com.android.aaptcompiler.android.ResTableConfig.COLOR_MODE
import com.android.aaptcompiler.android.ResTableConfig.INPUT_FLAGS
import com.android.aaptcompiler.android.ResTableConfig.SCREEN_LAYOUT
import com.android.aaptcompiler.android.ResTableConfig.SCREEN_LAYOUT2
import com.android.aaptcompiler.android.ResTableConfig.UI_MODE
import com.android.aaptcompiler.android.isTruthy
/** attempt to parse the Mobile Country Code */
@@ -65,7 +87,7 @@ fun parseLayoutDirection(part: String, config: ConfigDescription) : Boolean {
}
// AND existing config with the mask and then OR with the found layout direction.
config.screenLayout =
maskAndApply(config.screenLayout, ResTableConfig.SCREEN_LAYOUT.DIR_MASK, value)
maskAndApply(config.screenLayout, SCREEN_LAYOUT.DIR_MASK, value)
return true
}
@@ -119,7 +141,7 @@ fun parseScreenLayoutSize(part: String, config: ConfigDescription): Boolean {
}
// AND existing config with the mask and then OR with the found layout size.
config.screenLayout =
maskAndApply(config.screenLayout, ResTableConfig.SCREEN_LAYOUT.SIZE_MASK, value)
maskAndApply(config.screenLayout, SCREEN_LAYOUT.SIZE_MASK, value)
return true
}
@@ -132,7 +154,7 @@ fun parseScreenLayoutLong(part: String, config: ConfigDescription): Boolean {
}
// AND existing config with the mask and then OR with the found layout size.
config.screenLayout =
maskAndApply(config.screenLayout, ResTableConfig.SCREEN_LAYOUT.SCREENLONG_MASK, value)
maskAndApply(config.screenLayout, SCREEN_LAYOUT.SCREENLONG_MASK, value)
return true
}
@@ -144,7 +166,7 @@ fun parseScreenRound(part: String, config: ConfigDescription): Boolean {
else -> return false
}
config.screenLayout2 =
maskAndApply(config.screenLayout2, ResTableConfig.SCREEN_LAYOUT2.SCREENROUND_MASK, value)
maskAndApply(config.screenLayout2, SCREEN_LAYOUT2.SCREENROUND_MASK, value)
return true
}
@@ -156,7 +178,7 @@ fun parseWideColorGamut(part: String, config: ConfigDescription): Boolean {
else -> return false
}
config.colorMode =
maskAndApply(config.colorMode, ResTableConfig.COLOR_MODE.WIDE_GAMUT_MASK, value)
maskAndApply(config.colorMode, COLOR_MODE.WIDE_GAMUT_MASK, value)
return true
}
@@ -167,7 +189,7 @@ fun parseHdr(part: String, config: ConfigDescription): Boolean {
"lowdr" -> ResTableConfig.COLOR_MODE.HDR_NO
else -> return false
}
config.colorMode = maskAndApply(config.colorMode, ResTableConfig.COLOR_MODE.HDR_MASK, value)
config.colorMode = maskAndApply(config.colorMode, COLOR_MODE.HDR_MASK, value)
return true
}
@@ -193,7 +215,7 @@ fun parseUiModeType(part: String, config: ConfigDescription): Boolean {
"vrheadset" -> ResTableConfig.UI_MODE.TYPE_VR_HEADSET
else -> return false
}
config.uiMode = maskAndApply(config.uiMode, ResTableConfig.UI_MODE.TYPE_MASK, value)
config.uiMode = maskAndApply(config.uiMode, UI_MODE.TYPE_MASK, value)
return true
}
@@ -204,7 +226,7 @@ fun parseUiModeNight(part: String, config: ConfigDescription): Boolean {
"notnight" -> ResTableConfig.UI_MODE.NIGHT_NO
else -> return false
}
config.uiMode = maskAndApply(config.uiMode, ResTableConfig.UI_MODE.NIGHT_MASK, value)
config.uiMode = maskAndApply(config.uiMode, UI_MODE.NIGHT_MASK, value)
return true
}
@@ -255,7 +277,7 @@ fun parseKeysHidden(part: String, config: ConfigDescription): Boolean {
else -> return false
}
config.inputFlags =
maskAndApply(config.inputFlags, ResTableConfig.INPUT_FLAGS.KEYSHIDDEN_MASK, value)
maskAndApply(config.inputFlags, INPUT_FLAGS.KEYSHIDDEN_MASK, value)
return true
}
@@ -278,7 +300,7 @@ fun parseNavHidden(part: String, config: ConfigDescription): Boolean {
else -> return false
}
config.inputFlags =
maskAndApply(config.inputFlags, ResTableConfig.INPUT_FLAGS.NAVHIDDEN_MASK, value)
maskAndApply(config.inputFlags, INPUT_FLAGS.NAVHIDDEN_MASK, value)
return true
}
@@ -1,3 +1,20 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
/*
* Copyright (C) 2020 The Android Open Source Project
*
@@ -1,3 +1,20 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.android.aaptcompiler
/**
@@ -0,0 +1,31 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.android.aaptcompiler
/**
* Represents a single value for an entry for a given Configuration.
*
* @property config The configuration for which this value is defined.
* @property product The product name for which this value is defined.
* @property value The actual Value.
*/
data class ResourceConfigValue(
val config: ConfigDescription,
val product: String,
var value: Value? = null
)
@@ -1,3 +1,20 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
/*
* Copyright (C) 2019 The Android Open Source Project
*
@@ -16,6 +33,8 @@
package com.android.aaptcompiler
import com.android.aaptcompiler.AaptResourceType.RAW
data class ResourceFile(
val name: ResourceName,
val configuration: ConfigDescription,
@@ -42,7 +61,7 @@ data class ResourceFile(
data class ResourceName(
val pck: String?,
val type: AaptResourceType = AaptResourceType.RAW,
val type: AaptResourceType = RAW,
val entry: String? = null): Comparable<ResourceName> {
override fun compareTo(other: ResourceName): Int {
@@ -75,7 +94,7 @@ data class ResourceName(
}
companion object {
val EMPTY = ResourceName("", AaptResourceType.RAW, "")
val EMPTY = ResourceName("", RAW, "")
}
}
@@ -1,3 +1,20 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
/*
* Copyright (C) 2019 The Android Open Source Project
*
@@ -16,8 +33,8 @@
package com.android.aaptcompiler
import com.itsaky.androidide.layoutlib.resources.ResourceType
import java.io.File
import java.util.Locale
/**
* Class for keeping track of Resource File of any kind.
@@ -60,17 +77,19 @@ fun extractPathData(file: File, sourcePath: String = file.absolutePath) : Resour
else file.name
val source = Source(sourcePath)
// TODO(b/142481190): think about obfuscation
val parentName = file.parentFile.name
val parentName = file.parentFile!!.name
val type = parentName.substringBefore("-")
val config = if (type != parentName) {
file.parentFile.name.substringAfter(type).substring(1)
file.parentFile!!.name.substringAfter(type).substring(1)
} else {
""
}
val configDescription = parse(config)
return ResourcePathData(
source, extension.toLowerCase(), resName, type, config, file, configDescription)
source, extension.lowercase(Locale.getDefault()), resName, type, config, file, configDescription
)
}
/**
@@ -1,3 +1,20 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
/*
* Copyright (C) 2019 The Android Open Source Project
*
@@ -17,7 +34,14 @@
package com.android.aaptcompiler
import com.android.aapt.Resources
import com.android.aaptcompiler.AaptResourceType.ATTR
import com.android.aaptcompiler.AaptResourceType.ID
import com.android.aaptcompiler.AaptResourceType.STYLE
import com.android.aaptcompiler.Reference.Type.ATTRIBUTE
import com.android.aaptcompiler.android.ResValue
import com.android.aaptcompiler.android.ResValue.DataType.INT_BOOLEAN
import com.android.aaptcompiler.android.ResValue.DataType.NULL
import com.android.aaptcompiler.android.ResValue.NullFormat
import com.android.aaptcompiler.android.parseHex
import com.android.aaptcompiler.android.stringToFloat
import com.android.aaptcompiler.android.stringToInt
@@ -27,7 +51,7 @@ fun tryParseBool(string: String) : BinaryPrimitive? {
boolean ?: return null
val data = if (boolean) -1 else 0
return BinaryPrimitive(ResValue(ResValue.DataType.INT_BOOLEAN, data))
return BinaryPrimitive(ResValue(INT_BOOLEAN, data))
}
fun parseAsBool(string: String): Boolean? =
@@ -51,7 +75,7 @@ fun makeNull() : Reference {
}
fun makeEmpty(): BinaryPrimitive {
return BinaryPrimitive(ResValue(ResValue.DataType.NULL, ResValue.NullFormat.EMPTY))
return BinaryPrimitive(ResValue(NULL, NullFormat.EMPTY))
}
fun tryParseInt(value: String): BinaryPrimitive? {
@@ -64,7 +88,8 @@ fun parseResourceId(value: String): Int? {
val resValue = stringToInt(value)
if (resValue != null &&
resValue.dataType == ResValue.DataType.INT_HEX &&
resValue.data.isValidDynamicId()) {
resValue.data.isValidDynamicId()
) {
return resValue.data
}
return null
@@ -100,6 +125,7 @@ fun tryParseColor(value: String): BinaryPrimitive? {
}
data = data or 0xff000000.toInt()
}
5 -> {
dataType = ResValue.DataType.INT_COLOR_ARGB4
for (i in 1..4) {
@@ -111,6 +137,7 @@ fun tryParseColor(value: String): BinaryPrimitive? {
data = (data shl 8) or (hexValue + (hexValue shl 4))
}
}
7 -> {
dataType = ResValue.DataType.INT_COLOR_RGB8
for (i in 1..6) {
@@ -123,6 +150,7 @@ fun tryParseColor(value: String): BinaryPrimitive? {
}
data = data or 0xff000000.toInt()
}
9 -> {
dataType = ResValue.DataType.INT_COLOR_ARGB8
for (i in 1..8) {
@@ -134,6 +162,7 @@ fun tryParseColor(value: String): BinaryPrimitive? {
data = (data shl 4) or hexValue
}
}
else -> return null
}
return if (error)
@@ -197,8 +226,9 @@ fun parseAttributeReference(value: String) : ReferenceInfo? {
val reference = Reference()
reference.name =
ResourceName(
possibleResourceName.packageName, AaptResourceType.ATTR, possibleResourceName.entry)
reference.referenceType = Reference.Type.ATTRIBUTE
possibleResourceName.packageName, ATTR, possibleResourceName.entry
)
reference.referenceType = ATTRIBUTE
return ReferenceInfo(reference, false)
}
@@ -248,7 +278,7 @@ fun parseReference(value: String) : ReferenceInfo? {
return null
}
if (create && referenceNameInfo.resourceName.type != AaptResourceType.ID) {
if (create && referenceNameInfo.resourceName.type != ID) {
return null
}
@@ -308,11 +338,13 @@ fun parseResourceName(value: String) : ResourceNameInfo? {
return ResourceNameInfo(
ResourceName(possibleResourceName.packageName, resourceType, possibleResourceName.entry),
isPrivate)
isPrivate
)
}
data class PossibleResourceName(
val packageName: String, val typeName: String, val entry: String, val success: Boolean = true)
val packageName: String, val typeName: String, val entry: String, val success: Boolean = true
)
fun extractResourceName(value: String): PossibleResourceName {
@@ -334,6 +366,7 @@ fun extractResourceName(value: String): PossibleResourceName {
typeName = value.substring(offsetCurrent, currentChar)
offsetCurrent = currentChar + 1
}
packageName.isEmpty() && value[currentChar] == ':' -> {
hasPackageSeparator = true
packageName = value.substring(offsetCurrent, currentChar)
@@ -353,7 +386,8 @@ fun extractResourceName(value: String): PossibleResourceName {
fun tryParseItemForAttribute(
value: String,
resourceTypeMask: Int,
onCreateReference: ((name: ResourceName) -> Boolean)? = null): Item? {
onCreateReference: ((name: ResourceName) -> Boolean)? = null
): Item? {
val nullOrEmpty = tryParseNullOrEmpty(value)
if (nullOrEmpty != null) {
@@ -399,7 +433,8 @@ fun tryParseItemForAttribute(
if ((resourceTypeMask and floatMask) != 0) {
val floatingPoint = tryParseFloat(value)
if (floatingPoint != null &&
(androidTypeToAttributeTypeMask(floatingPoint.resValue.dataType) and resourceTypeMask) != 0) {
(androidTypeToAttributeTypeMask(floatingPoint.resValue.dataType) and resourceTypeMask) != 0
) {
return floatingPoint
}
}
@@ -413,6 +448,7 @@ fun androidTypeToAttributeTypeMask(type: ResValue.DataType) =
ResValue.DataType.REFERENCE,
ResValue.DataType.ATTRIBUTE,
ResValue.DataType.DYNAMIC_REFERENCE -> Resources.Attribute.FormatFlags.REFERENCE_VALUE
ResValue.DataType.STRING -> Resources.Attribute.FormatFlags.STRING_VALUE
ResValue.DataType.FLOAT -> Resources.Attribute.FormatFlags.FLOAT_VALUE
ResValue.DataType.DIMENSION -> Resources.Attribute.FormatFlags.DIMENSION_VALUE
@@ -423,11 +459,13 @@ fun androidTypeToAttributeTypeMask(type: ResValue.DataType) =
Resources.Attribute.FormatFlags.ENUM_VALUE or
Resources.Attribute.FormatFlags.FLAGS_VALUE
}
ResValue.DataType.INT_BOOLEAN -> Resources.Attribute.FormatFlags.BOOLEAN_VALUE
ResValue.DataType.INT_COLOR_ARGB8,
ResValue.DataType.INT_COLOR_RGB8,
ResValue.DataType.INT_COLOR_ARGB4,
ResValue.DataType.INT_COLOR_RGB4 -> Resources.Attribute.FormatFlags.COLOR_VALUE
else -> 0
}
@@ -456,6 +494,7 @@ fun verifyJavaStringFormat(string: String): Boolean {
nonpositionalArgs = true
}
}
currentIndex < string.length && string.codePointAt(currentIndex) == '<'.code -> {
// Reusing last argument, bad idea since positions can be moved around during translation
nonpositionalArgs = true
@@ -466,6 +505,7 @@ fun verifyJavaStringFormat(string: String): Boolean {
++currentIndex
}
}
else -> nonpositionalArgs = true
}
// Ignore size, width, flags, etc.
@@ -478,6 +518,7 @@ fun verifyJavaStringFormat(string: String): Boolean {
','.code,
'('.code,
in '0'.code..'9'.code -> ++currentIndex
else -> break@search
}
}
@@ -571,11 +612,13 @@ fun parseStyleParentReference(str: String): ParsedParentInfo {
val resourceName =
ResourceName(
possibleResourceName.packageName, AaptResourceType.STYLE, possibleResourceName.entry)
possibleResourceName.packageName, STYLE, possibleResourceName.entry
)
if (!hasLeadingIdentifiers &&
resourceName.pck!!.isEmpty() &&
possibleResourceName.typeName.isNotEmpty()) {
possibleResourceName.typeName.isNotEmpty()
) {
val errorString = "Invalid parent reference '$str'"
return ParsedParentInfo(null, errorString)
}
@@ -610,7 +653,7 @@ fun parseXmlAttributeName(str: String): Reference {
}
result.name =
ResourceName(packageName, AaptResourceType.ATTR, if (entryName.isEmpty()) name else entryName)
ResourceName(packageName, ATTR, if (entryName.isEmpty()) name else entryName)
return result
}
@@ -1,3 +1,20 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
/*
* Copyright (C) 2019 The Android Open Source Project
*
@@ -17,6 +34,8 @@
package com.android.aaptcompiler
import com.android.aapt.Resources
import com.android.aaptcompiler.AaptResourceType.MACRO
import com.android.aaptcompiler.Reference.Type.RESOURCE
import com.android.aaptcompiler.android.ResValue
import com.android.aaptcompiler.android.deviceToHost
import com.android.aaptcompiler.android.hostToDevice
@@ -74,7 +93,7 @@ class Reference(var name: ResourceName = ResourceName.EMPTY): Item() {
}
var id : Int? = null
var referenceType : Reference.Type = Reference.Type.RESOURCE
var referenceType : Type = RESOURCE
var isPrivate = false
var isDynamic = false
// Only used for macros, which can contain any format type
@@ -83,14 +102,14 @@ class Reference(var name: ResourceName = ResourceName.EMPTY): Item() {
var allowRaw = true
override fun flatten(): ResValue? {
if (name?.type == AaptResourceType.MACRO) {
if (name?.type == MACRO) {
return null
}
val resId = id ?: 0
val dynamic = resId.isValidDynamicId() && isDynamic
val dataType = when {
referenceType == Reference.Type.RESOURCE ->
referenceType == RESOURCE ->
if (dynamic) ResValue.DataType.DYNAMIC_REFERENCE else ResValue.DataType.REFERENCE
else ->
if (dynamic) ResValue.DataType.DYNAMIC_ATTRIBUTE else ResValue.DataType.ATTRIBUTE
@@ -253,18 +272,23 @@ private fun maskToString(typeMask: Int): String {
result.append(maskToString(typeMask, Resources.Attribute.FormatFlags.BOOLEAN_VALUE, "| boolean"))
result.append(maskToString(typeMask, Resources.Attribute.FormatFlags.COLOR_VALUE, "| color"))
result.append(
maskToString(typeMask, Resources.Attribute.FormatFlags.DIMENSION_VALUE, "| dimension"))
maskToString(typeMask, Resources.Attribute.FormatFlags.DIMENSION_VALUE, "| dimension")
)
result.append(maskToString(typeMask, Resources.Attribute.FormatFlags.ENUM_VALUE, "| enum"))
result.append(maskToString(typeMask, Resources.Attribute.FormatFlags.FLAGS_VALUE, "| flags"))
result.append(maskToString(typeMask, Resources.Attribute.FormatFlags.FLOAT_VALUE, "| float"))
result.append(
maskToString(typeMask, Resources.Attribute.FormatFlags.FRACTION_VALUE, "| fraction"))
maskToString(typeMask, Resources.Attribute.FormatFlags.FRACTION_VALUE, "| fraction")
)
result.append(
maskToString(typeMask, Resources.Attribute.FormatFlags.INTEGER_VALUE, "| integer"))
maskToString(typeMask, Resources.Attribute.FormatFlags.INTEGER_VALUE, "| integer")
)
result.append(
maskToString(typeMask, Resources.Attribute.FormatFlags.REFERENCE_VALUE, "| reference"))
maskToString(typeMask, Resources.Attribute.FormatFlags.REFERENCE_VALUE, "| reference")
)
result.append(
maskToString(typeMask, Resources.Attribute.FormatFlags.STRING_VALUE, "| string"))
maskToString(typeMask, Resources.Attribute.FormatFlags.STRING_VALUE, "| string")
)
return if (result.isNotEmpty()) result.substring(2) else ""
}
@@ -414,7 +438,8 @@ class Style: Value() {
newStyle.source = source
for (entry in entries){
newStyle.entries.add(
Entry(entry.key, entry.value?.clone(pool)))
Entry(entry.key, entry.value?.clone(pool))
)
}
return newStyle
}
@@ -476,14 +501,14 @@ class Style: Value() {
entries.clear()
for (entry in mergedEntries) {
entries.add(Style.Entry(entry.key, entry.value?.clone(pool)))
entries.add(Entry(entry.key, entry.value?.clone(pool)))
}
}
override fun toString(): String {
val sb = StringBuilder(parent?.name.toString() +"\n")
for (entry in entries) {
sb.appendln(entry.key.name.toString() + " " + entry.value?.toString())
sb.appendLine(entry.key.name.toString() + " " + entry.value?.toString())
}
return sb.toString()
}
@@ -506,7 +531,7 @@ class Plural: Value() {
val values = arrayOfNulls<Item?>(Type.NUM_TYPES)
fun setValue(type: Plural.Type, item: Item) {
fun setValue(type: Type, item: Item) {
values[type.ordinal] = item
}
@@ -1,3 +1,20 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
/*
* Copyright (C) 2019 The Android Open Source Project
*
@@ -1,3 +1,20 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
/*
* Copyright (C) 2019 The Android Open Source Project
*
@@ -16,6 +33,7 @@
package com.android.aaptcompiler
import com.android.aaptcompiler.StringPool.Context.Priority.NORMAL
import com.android.aaptcompiler.android.ChunkType
import com.android.aaptcompiler.android.ResChunkHeader
import com.android.aaptcompiler.android.ResStringPoolHeader
@@ -36,8 +54,9 @@ class StringPool {
private val indexedStrings = mutableMapOf<String, MutableList<Entry>>()
class Context(
var priority: Long = Priority.NORMAL.priority,
var config: ConfigDescription = ConfigDescription()) {
var priority: Long = NORMAL.priority,
var config: ConfigDescription = ConfigDescription()
) {
enum class Priority(val priority: Long) {
HIGH(1),
NORMAL(0x7fffffffL),
@@ -58,7 +77,7 @@ class StringPool {
fun context(): Context = entry.context
}
class Span(internal val name: Ref, var firstChar: Int, var lastChar: Int = firstChar) {
class Span(val name: Ref, var firstChar: Int, var lastChar: Int = firstChar) {
override fun equals(other: Any?): Boolean {
if (other is Span) {
return name.value() == other.name.value() &&
@@ -97,7 +116,7 @@ class StringPool {
fun size(): Int = styles.size + strings.size
fun entryAt(index: Int): StringPool.Entry {
fun entryAt(index: Int): Entry {
return strings[index]
}
@@ -1487,12 +1487,12 @@ open class ResTableConfig(
}
companion object {
internal val ENGLISH = byteArrayOf('e'.code.toByte(), 'n'.code.toByte()) // packed version of "en"
internal val UNITED_STATES = byteArrayOf('U'.code.toByte(), 'S'.code.toByte()) // packed version of "US"
internal val FILIPINO = byteArrayOf(0xAD.toByte(), 0x05.toByte()) // packed version of "fil"
internal val TAGALOG = byteArrayOf('t'.code.toByte(), 'l'.code.toByte()) // packed version of "tl"
val ENGLISH = byteArrayOf('e'.code.toByte(), 'n'.code.toByte()) // packed version of "en"
val UNITED_STATES = byteArrayOf('U'.code.toByte(), 'S'.code.toByte()) // packed version of "US"
val FILIPINO = byteArrayOf(0xAD.toByte(), 0x05.toByte()) // packed version of "fil"
val TAGALOG = byteArrayOf('t'.code.toByte(), 'l'.code.toByte()) // packed version of "tl"
internal const val NUMBERING_SYSTEM_PREFIX = "u-nu-"
const val NUMBERING_SYSTEM_PREFIX = "u-nu-"
// Flags indicating a set of config values. These flag constants must
// match the corresponding ones in android.content.pm.ActivityInfo and
@@ -20,7 +20,6 @@ import com.android.aaptcompiler.StringPool
import com.android.aaptcompiler.parseFloat
import java.nio.ByteBuffer
import java.nio.ByteOrder
import kotlin.text.Charsets
/**
* Definitions of resource data structures.
@@ -36,7 +35,8 @@ class ResChunkHeader(
val typeId: Short,
// Size of the chunk header (in bytes). Adding this value to the address of the chunk will be
// the address of the associated data if any.
val headerSize: Short) {
val headerSize: Short
) {
constructor() : this(0, 0)
@@ -54,6 +54,7 @@ enum class ChunkType(val id: Short) {
// Marker for the beginning of XML types.
XML_FIRST_CHUNK_TYPE(0x0100),
// Child types of XML_TYPE
XML_START_NAMESPACE_TYPE(0x0100),
XML_END_NAMESPACE_TYPE(0x0101),
@@ -61,6 +62,7 @@ enum class ChunkType(val id: Short) {
XML_END_ELEMENT_TYPE(0x0103),
XML_CDATA_TYPE(0x0104),
XML_LAST_CHUNK_TYPE(0x017f),
// This contains a uint32_t array mapping strings in the string
// pool back to resource identifiers. It is optional.
XML_RESOURCE_MAP_TYPE(0x0180),
@@ -85,36 +87,51 @@ data class ResValue(val dataType: DataType, val data: Int, val size: Short = 0)
enum class DataType(val byteValue: Byte) {
// The 'data' is either 0 or 1, specifying this resource is undefined or empty, respectively.
NULL(0X00),
// The 'data' holds a ResTable reference, i.e. a reference to another resource.
REFERENCE(0x01),
// The 'data' holds an attribute resource identifier.
ATTRIBUTE(0x02),
// The 'data' holds an index into the containing resource table's string pool.
STRING(0x03),
// The 'data' holds a single-precision floating point number.
FLOAT(0x04),
// The 'data' holds a fixed point number encoding a dimension value, e.g. 100in.
DIMENSION(0x05),
// The 'data' holds a fixed point number encoding a fraction of a container.
FRACTION(0x06),
// The 'data' holds a dynamic ResTable reference, which needs to be resolved before it can be
// used as a REFERENCE.
DYNAMIC_REFERENCE(0x07),
// The 'data' holds an attribute resource identifier, which needs to be resolved before it can
// be used like a ATTRIBUTE.
DYNAMIC_ATTRIBUTE(0x08),
// The 'data' is a raw integer of the form n..n.
INT_DEC(0x10),
// The 'data' is a raw integer value of the form 0xn..n.
INT_HEX(0x11),
// The 'data' is either 0 or 1, for input 'false' or 'true' respectively.
INT_BOOLEAN(0x12),
// The 'data' is a raw integer value of the form #aarrggbb.
INT_COLOR_ARGB8(0x1c),
// The 'data' is a raw integer value of the form #rrggbb.
INT_COLOR_RGB8(0x1d),
// The 'data' is a raw integer value of the form #argb
INT_COLOR_ARGB4(0x1e),
// The 'data is a raw integer value of the form #rgb
INT_COLOR_RGB4(0x1f);
@@ -135,19 +152,25 @@ data class ResValue(val dataType: DataType, val data: Int, val size: Short = 0)
// TYPE_DIMENSION: Value is raw pixels.
const val UNIT_PX = 0
// TYPE_DIMENSION: Value is Device Independent Pixels.
const val UNIT_DIP = 1
// TYPE_DIMENSION: Value is a Scaled device independent Pixels.
const val UNIT_SP = 2
// TYPE_DIMENSION: Value is in points.
const val UNIT_PT = 3
// TYPE_DIMENSION: Value is in inches.
const val UNIT_IN = 4
// TYPE_DIMENSION: Value is in millimeters.
const val UNIT_MM = 5
// TYPE_FRACTION: A basic fraction of the overall size.
const val UNIT_FRACTION = 0
// TYPE_FRACTION: A fraction of the parent size.
const val UNIT_FRACTION_PARENT = 1
@@ -159,10 +182,13 @@ data class ResValue(val dataType: DataType, val data: Int, val size: Short = 0)
// The mantissa is an integral number -- i.e., 0xnnnnnn.0
const val RADIX_23p0 = 0
// The mantissa magnitude is 16 bits -- i.e, 0xnnnn.nn
const val RADIX_16p7 = 1
// The mantissa magnitude is 8 bits -- i.e, 0xnn.nnnn
const val RADIX_8p15 = 2
// The mantissa magnitude is 0 bits -- i.e, 0x0.nnnnnn
const val RADIX_0p23 = 3
@@ -186,7 +212,8 @@ data class ResValue(val dataType: DataType, val data: Int, val size: Short = 0)
data class ResStringPoolRef(
// Index into the string pool table (unsigned 32 bit offset from the indices immediately after
// ResStringPoolHeader) at which to find the location of the string data in the pool
val index: Int)
val index: Int
)
/**
* Definition of a pool of strings.
@@ -208,13 +235,16 @@ data class ResStringPoolHeader(
val stringCount: Int,
// Number of style span arrays in the pool (number of 32 bit indices that follow the string
// indices).
val styleCount: Int) {
val styleCount: Int
) {
constructor() : this(ResChunkHeader(), 0, 0)
var flags: Int = 0
// Index from header of the string data.
var stringsStart: Int = 0
// Index from header of the style data.
var stylesStart: Int = 0
@@ -250,7 +280,8 @@ class ResStringPool private constructor(
val stringPoolSize: Int,
val strings: List<String>,
val stylesPoolSize: Int,
val styles: List<List<ResStringPoolSpan>>) {
val styles: List<List<ResStringPoolSpan>>
) {
companion object {
fun get(buffer: ByteBuffer, length: Int): ResStringPool {
@@ -278,7 +309,8 @@ class ResStringPool private constructor(
val header =
ResStringPoolHeader(
chunkHeader, buffer.getInt(8).deviceToHost(), buffer.getInt(12).deviceToHost())
chunkHeader, buffer.getInt(8).deviceToHost(), buffer.getInt(12).deviceToHost()
)
header.flags = buffer.getInt(16).deviceToHost()
header.stringsStart = buffer.getInt(20).deviceToHost()
header.stylesStart = buffer.getInt(24).deviceToHost()
@@ -288,7 +320,8 @@ class ResStringPool private constructor(
if (header.stringCount != 0) {
// we need to check overflow and ensure the string indexes can fit.
if (header.stringCount * 4 < header.stringCount ||
(header.header.headerSize + (header.stringCount*4)) > resourceSize) {
(header.header.headerSize + (header.stringCount * 4)) > resourceSize
) {
error("Invalid StringPool: Buffer not large enough for string indices.")
}
@@ -367,6 +400,7 @@ class ResStringPool private constructor(
((firstByteUTF16.toInt() shl 8) + secondByte) and StringPool.UTF8_ENCODE_LENGTH_MAX
}
else -> firstByteUTF16.toInt()
}
@@ -381,6 +415,7 @@ class ResStringPool private constructor(
((firstByteUTF8.toInt() shl 8) + secondByte) and StringPool.UTF8_ENCODE_LENGTH_MAX
}
else -> firstByteUTF8.toInt()
}
@@ -414,6 +449,7 @@ class ResStringPool private constructor(
stringPosition += 2
((firstShort.toInt() shl 16) + secondShort) and StringPool.UTF16_ENCODE_LENGTH_MAX
}
else -> firstShort.toInt()
}
@@ -487,7 +523,8 @@ fun stringToInt(string: String): ResValue? {
val isHex: Boolean
if (codePointCount >= index + 2
&& trimmedString.codePointAt(index) == '0'.code
&& trimmedString.codePointAt(index+1) == 'x'.code) {
&& trimmedString.codePointAt(index + 1) == 'x'.code
) {
isHex = true
index += 2
@@ -541,11 +578,13 @@ fun stringToInt(string: String): ResValue? {
return ResValue(
if (isHex) ResValue.DataType.INT_HEX else ResValue.DataType.INT_DEC,
value.toInt())
value.toInt()
)
}
private data class UnitEntry(
val name: String, val dataType: ResValue.DataType, val unitValue: Int, val scale: Float = 1.0f)
val name: String, val dataType: ResValue.DataType, val unitValue: Int, val scale: Float = 1.0f
)
private val unitSuffixes = listOf(
UnitEntry("px", ResValue.DataType.DIMENSION, ResValue.ComplexFormat.UNIT_PX),
@@ -557,7 +596,9 @@ private val unitSuffixes = listOf(
UnitEntry("mm", ResValue.DataType.DIMENSION, ResValue.ComplexFormat.UNIT_MM),
UnitEntry("%", ResValue.DataType.FRACTION, ResValue.ComplexFormat.UNIT_FRACTION, 1.0f / 100),
UnitEntry(
"%p", ResValue.DataType.FRACTION, ResValue.ComplexFormat.UNIT_FRACTION_PARENT, 1.0f/100))
"%p", ResValue.DataType.FRACTION, ResValue.ComplexFormat.UNIT_FRACTION_PARENT, 1.0f / 100
)
)
private fun parseUnitType(string: String): UnitEntry? {
for (entry in unitSuffixes) {
@@ -592,7 +633,8 @@ fun stringToFloat(string: String): ResValue? {
if (parseHex(entry?.name?.codePointAt(0) ?: 0) != -1) {
// try to parse the float value with the beginning of the suffix considered.
if (trimmedString.contains("0x", true) &&
!stringToParse.contains('p')) {
!stringToParse.contains('p')
) {
// ambiguous value string, resulting in a parse failure.
return null
}
@@ -630,15 +672,19 @@ fun stringToFloat(string: String): ResValue? {
bits and ((1L shl 23) - 1) == 0L ->
// Always use 23p0 if there is no fraction, as it is easier to read.
Pair(ResValue.ComplexFormat.RADIX_23p0, 23)
bits and ((1L shl 23) - 1).inv() == 0L ->
// Whole number part is zero -- can fit into 0 leading bits of precision.
Pair(ResValue.ComplexFormat.RADIX_0p23, 0)
bits and ((1L shl 31) - 1).inv() == 0L ->
// Magnitude can fit in 8 leading bits of precision.
Pair(ResValue.ComplexFormat.RADIX_8p15, 8)
bits and ((1L shl 39) - 1).inv() == 0L ->
// Magnitude can fit in 16 leading bits of precision.
Pair(ResValue.ComplexFormat.RADIX_16p7, 16)
else ->
// Need whole range, so no fractional part.
Pair(ResValue.ComplexFormat.RADIX_23p0, 23)
@@ -16,7 +16,6 @@
package com.android.aaptcompiler.android
import androidx.collection.mutableIntIntMapOf
import java.nio.ByteOrder
private val littleEndian = ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN
@@ -37,13 +37,13 @@ class BigBuffer(val blockSize: Int = 1024) {
var size: Int = 0
private set
internal val blocks = mutableListOf<Block>()
val blocks = mutableListOf<Block>()
internal class Block(
class Block(
internal var size: Int, internal val blockSize: Int, internal val data: ByteBuffer)
data class BlockRef internal constructor(
internal val start: Int, val size: Int, internal val block: Block) {
val start: Int, val size: Int, val block: Block) {
fun writeByte(value: Byte, location: Int) {
if (location + 1 > size) {
@@ -0,0 +1,29 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.xml.res
import com.android.aaptcompiler.ConfigDescription
import com.android.aaptcompiler.ResourceConfigValue
/**
* @author Akash Yadav
*/
interface IResourceEntry {
val name: String
fun findValue(config: ConfigDescription, product: String = ""): ResourceConfigValue?
}
@@ -0,0 +1,31 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.xml.res
/**
* @author Akash Yadav
*/
interface IResourceGroup {
fun findEntry(name: String, entryId: Short? = null): IResourceEntry?
fun findEntries(
entryId: Short? = null,
predicate: (String) -> Boolean
): List<IResourceEntry>
}
@@ -0,0 +1,41 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.xml.res
import com.android.aaptcompiler.ResourceName
/**
* @author Akash Yadav
*/
interface IResourceTable {
val packages: Collection<IResourceTablePackage>
fun findResource(name: ResourceName): ISearchResult?
/**
* Returns the package struct with the given name, or null if such a package does not
* exist. The empty string is a valid package and typically is used to represent the
* 'current' package before it is known to the ResourceTable.
*
* @param name the name of the package.
* @return the [IResourceTablePackage] with the requested name or {@code null} if that package
* does not exist in the table.
*/
fun findPackage(name: String): IResourceTablePackage?
}
@@ -0,0 +1,28 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.xml.res
import com.android.aaptcompiler.AaptResourceType
/**
* @author Akash Yadav
*/
interface IResourceTablePackage {
val name: String
fun findGroup(type: AaptResourceType, groupId: Byte? = null): IResourceGroup?
}
@@ -0,0 +1,30 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.xml.res
import com.android.aaptcompiler.Source
/**
* @author Akash Yadav
*/
interface IResourceValue {
var source: Source
var comment: String
var weak: Boolean
var translatable: Boolean
}
@@ -0,0 +1,30 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.xml.res
/**
* @author Akash Yadav
*/
interface ISearchResult {
val tablePackage: IResourceTablePackage
val group: IResourceGroup
val entry: IResourceEntry
}
+3 -4
View File
@@ -24,17 +24,15 @@ plugins {
id("kotlin-kapt")
}
android {
namespace = "${BuildConfig.packageName}.xml"
namespace = "${BuildConfig.packageName}.xml.utils"
}
dependencies {
kapt(libs.google.auto.service)
api(projects.utilities.lookup)
api(projects.xml.aaptcompiler)
api(projects.xml.resourcesApi)
implementation(libs.common.kotlin)
implementation(libs.composite.jdt)
@@ -43,6 +41,7 @@ dependencies {
implementation(projects.core.common)
implementation(projects.logging.logger)
implementation(projects.xml.aaptcompiler)
testImplementation(libs.tests.junit)
testImplementation(libs.tests.robolectric)
@@ -40,6 +40,7 @@ import com.itsaky.androidide.xml.internal.resources.DefaultResourceTableRegistry
import com.itsaky.androidide.xml.internal.resources.DefaultResourceTableRegistry.SingleLineValueEntryType.CATEGORIES
import com.itsaky.androidide.xml.internal.resources.DefaultResourceTableRegistry.SingleLineValueEntryType.FEATURES
import com.itsaky.androidide.xml.internal.resources.DefaultResourceTableRegistry.SingleLineValueEntryType.SERVICE_ACTIONS
import com.itsaky.androidide.xml.res.IResourceTable
import com.itsaky.androidide.xml.resources.ResourceTableRegistry
import com.itsaky.androidide.xml.resources.ResourceTableRegistry.Companion.PCK_ANDROID
import org.slf4j.LoggerFactory
@@ -98,7 +99,7 @@ class DefaultResourceTableRegistry : ResourceTableRegistry {
}
}
override fun forPlatformDir(platform: File): ResourceTable? {
override fun forPlatformDir(platform: File): IResourceTable? {
getManifestAttrTable(platform)
getActivityActions(platform)
getBroadcastActions(platform)
@@ -249,14 +250,19 @@ class DefaultResourceTableRegistry : ResourceTableRegistry {
AaptResourceType.UNKNOWN
}
val resName = ResourceName(pck, type, file.nameWithoutExtension)
table.addFileReference(resName, ConfigDescription(), Source(file.path), file.path)
table.addFileReference(
resName, ConfigDescription(),
Source(file.path), file.path
)
}
}
}
private fun getDefaultOptions(): TableExtractorOptions {
return TableExtractorOptions(translatable = true, errorOnPositionalArgs = false,
visibility = PUBLIC)
return TableExtractorOptions(
translatable = true, errorOnPositionalArgs = false,
visibility = PUBLIC
)
}
private fun updateFromDirectory(
@@ -18,10 +18,10 @@
package com.itsaky.androidide.xml.resources
import com.android.aaptcompiler.ResourceGroup
import com.android.aaptcompiler.ResourceTable
import com.itsaky.androidide.lookup.Lookup
import com.itsaky.androidide.utils.ServiceLoader
import com.itsaky.androidide.xml.registry.XmlRegistry
import com.itsaky.androidide.xml.res.IResourceTable
import java.io.File
/**
@@ -29,26 +29,31 @@ import java.io.File
*
* @author Akash Yadav
*/
interface ResourceTableRegistry : XmlRegistry<ResourceTable> {
interface ResourceTableRegistry : XmlRegistry<IResourceTable> {
companion object {
const val PCK_ANDROID = "android"
@JvmStatic
val COMPLETION_MODULE_RES = Lookup.Key<Set<ResourceTable>>()
val COMPLETION_MODULE_RES = Lookup.Key<Set<IResourceTable>>()
@JvmStatic
val COMPLETION_DEP_RES = Lookup.Key<Set<ResourceTable>>()
val COMPLETION_DEP_RES = Lookup.Key<Set<IResourceTable>>()
@JvmStatic
val COMPLETION_FRAMEWORK_RES = Lookup.Key<ResourceTable>()
val COMPLETION_FRAMEWORK_RES = Lookup.Key<IResourceTable>()
@JvmStatic
val COMPLETION_MANIFEST_ATTR_RES = Lookup.Key<ResourceTable>()
val COMPLETION_MANIFEST_ATTR_RES = Lookup.Key<IResourceTable>()
private var sInstance: ResourceTableRegistry? = null
@JvmStatic
fun getInstance(): ResourceTableRegistry {
val klass = ResourceTableRegistry::class.java
return sInstance ?: ServiceLoader.load(klass, klass.classLoader).findFirstOrThrow().also { sInstance = it }
return sInstance ?: ServiceLoader.load(klass, klass.classLoader).findFirstOrThrow()
.also { sInstance = it }
}
}
@@ -57,7 +62,7 @@ interface ResourceTableRegistry : XmlRegistry<ResourceTable> {
*
* @param name The package name for the resource table.
*/
fun forPackage(name: String, vararg resDirs: File): ResourceTable?
fun forPackage(name: String, vararg resDirs: File): IResourceTable?
/**
* Remove the resource table entry for the given package name.
@@ -71,7 +76,7 @@ interface ResourceTableRegistry : XmlRegistry<ResourceTable> {
*
* @return The [ResourceGroup] or null if not found or not available.
*/
fun getManifestAttrTable(platform: File): ResourceTable?
fun getManifestAttrTable(platform: File): IResourceTable?
/** Get the list of all activity actions for the given [platform] directory. */
fun getActivityActions(platform: File): List<String>
@@ -88,7 +93,7 @@ interface ResourceTableRegistry : XmlRegistry<ResourceTable> {
/** Get the list of all features for the given [platform] directory. */
fun getFeatures(platform: File): List<String>
override fun forPlatformDir(platform: File): ResourceTable? {
override fun forPlatformDir(platform: File): IResourceTable? {
return forPackage(PCK_ANDROID, File(platform, "data/res"))
}
}
@@ -155,7 +155,13 @@ class ResourceTableRegistryTest {
this.packages.first().apply { assertThat(this.name).isEqualTo("android") }
}
resourceTable!!.findResource(ResourceName(pck = "android", type = STRING, entry = "ok")).apply {
resourceTable!!.findResource(
com.android.aaptcompiler.ResourceName(
pck = "android",
type = STRING,
entry = "ok"
)
).apply {
assertThat(this).isNotNull()
assertThat(this!!.entry).isNotNull()
assertThat(this.entry.values).isNotEmpty()
@@ -163,14 +169,20 @@ class ResourceTableRegistryTest {
assertThat(this).isNotNull()
assertThat(this!!.value).isNotNull()
this.value.apply {
assertThat(this).isInstanceOf(BasicString::class.java)
assertThat(this).isInstanceOf(com.android.aaptcompiler.BasicString::class.java)
assertThat(this.toString()).isEqualTo("OK")
}
}
}
resourceTable
.findResource(ResourceName(pck = "android", type = STRING, entry = "cancel"))
.findResource(
com.android.aaptcompiler.ResourceName(
pck = "android",
type = STRING,
entry = "cancel"
)
)
.apply {
assertThat(this).isNotNull()
assertThat(this!!.entry).isNotNull()
@@ -179,14 +191,20 @@ class ResourceTableRegistryTest {
assertThat(this).isNotNull()
assertThat(this!!.value).isNotNull()
this.value.apply {
assertThat(this).isInstanceOf(BasicString::class.java)
assertThat(this).isInstanceOf(com.android.aaptcompiler.BasicString::class.java)
assertThat(this.toString()).isEqualTo("Cancel")
}
}
}
resourceTable
.findResource(ResourceName(pck = "android", type = COLOR, entry = "holo_red_dark"))
.findResource(
com.android.aaptcompiler.ResourceName(
pck = "android",
type = COLOR,
entry = "holo_red_dark"
)
)
.apply {
assertThat(this).isNotNull()
assertThat(this!!.entry).isNotNull()
@@ -196,8 +214,8 @@ class ResourceTableRegistryTest {
assertThat(this!!.value).isNotNull()
this.value.apply {
assertThat(this).isNotNull()
assertThat(this!!).isInstanceOf(BinaryPrimitive::class.java)
(this as BinaryPrimitive).resValue.apply {
assertThat(this!!).isInstanceOf(com.android.aaptcompiler.BinaryPrimitive::class.java)
(this as com.android.aaptcompiler.BinaryPrimitive).resValue.apply {
assertThat(this).isNotNull()
assertThat(this.dataType).isEqualTo(DataType.INT_COLOR_ARGB8)
assertThat(this.data)