diff --git a/tests/test-home/.androidide/init/model.jar b/tests/test-home/.androidide/init/model.jar index 2fbb08002..16c463d6d 100644 Binary files a/tests/test-home/.androidide/init/model.jar and b/tests/test-home/.androidide/init/model.jar differ diff --git a/xml-inflater/src/main/java/com/itsaky/androidide/inflater/drawable/BitmapDrawableParser.kt b/xml-inflater/src/main/java/com/itsaky/androidide/inflater/drawable/BitmapDrawableParser.kt index 2abf36dbd..66bcfee12 100644 --- a/xml-inflater/src/main/java/com/itsaky/androidide/inflater/drawable/BitmapDrawableParser.kt +++ b/xml-inflater/src/main/java/com/itsaky/androidide/inflater/drawable/BitmapDrawableParser.kt @@ -45,8 +45,7 @@ class BitmapDrawableParser protected constructor(parser: XmlPullParser, minDepth } val `val` = value(index) val dr = parseDrawable(context, `val`) - ?: throw InflateException("Cannot parse drawable for android:src = $`val`") - val bitmap = BitmapDrawable(BaseApplication.getBaseInstance().resources, toBitmap(dr)) + val bitmap = BitmapDrawable(context.resources, toBitmap(dr)) index = attrIndex("antialias") if (index != -1) { bitmap.setAntiAlias(parseBoolean(value(index))) diff --git a/xml-inflater/src/main/java/com/itsaky/androidide/inflater/drawable/ClipDrawableParser.kt b/xml-inflater/src/main/java/com/itsaky/androidide/inflater/drawable/ClipDrawableParser.kt index 949e88fe3..0865bf668 100644 --- a/xml-inflater/src/main/java/com/itsaky/androidide/inflater/drawable/ClipDrawableParser.kt +++ b/xml-inflater/src/main/java/com/itsaky/androidide/inflater/drawable/ClipDrawableParser.kt @@ -20,7 +20,6 @@ import android.content.Context import android.graphics.drawable.ClipDrawable import android.graphics.drawable.Drawable import android.view.Gravity -import com.itsaky.androidide.inflater.InflateException import org.xmlpull.v1.XmlPullParser /** @@ -31,17 +30,14 @@ import org.xmlpull.v1.XmlPullParser open class ClipDrawableParser protected constructor(parser: XmlPullParser, minDepth: Int) : IDrawableParser(parser, minDepth) { @Throws(Exception::class) - public override fun parseDrawable(context: Context): Drawable { + public override fun parseDrawable(context: Context): Drawable? { var index = attrIndex("drawable") if (index == -1) { - throw InflateException(" drawable must specify android:drawable attribute") + // TODO Parse inner drawables + return ClipDrawable(null, Gravity.LEFT, ClipDrawable.HORIZONTAL) } - val value = - value(index) - ?: throw InflateException("Invalid value specified to android:drawable attribute") - val drawable = - parseDrawable(context, value) - ?: throw InflateException("Unable to parse drawable for value: $value") + val value = value(index) + val drawable = parseDrawable(context, value) var orientation = ClipDrawable.HORIZONTAL index = attrIndex("clipOrientation") if (index != -1) { diff --git a/xml-inflater/src/main/java/com/itsaky/androidide/inflater/drawable/IDrawableParser.kt b/xml-inflater/src/main/java/com/itsaky/androidide/inflater/drawable/IDrawableParser.kt index 83d3c4b73..70b50415b 100644 --- a/xml-inflater/src/main/java/com/itsaky/androidide/inflater/drawable/IDrawableParser.kt +++ b/xml-inflater/src/main/java/com/itsaky/androidide/inflater/drawable/IDrawableParser.kt @@ -21,7 +21,6 @@ import android.graphics.drawable.Drawable import android.os.Build.VERSION import android.os.Build.VERSION_CODES import androidx.annotation.ChecksSdkIntAtLeast -import com.itsaky.androidide.inflater.internal.utils.parseDimension import org.xmlpull.v1.XmlPullParser /** @@ -29,7 +28,8 @@ import org.xmlpull.v1.XmlPullParser * * @author Akash Yadav */ -abstract class IDrawableParser protected constructor(protected open val parser: XmlPullParser?, open var minDepth: Int) { +abstract class IDrawableParser +protected constructor(protected open val parser: XmlPullParser?, open var minDepth: Int) { /** * Parse the drawable using the already provided parser and data. * @@ -61,40 +61,41 @@ abstract class IDrawableParser protected constructor(protected open val parser: } return drawable } - - protected fun parseInteger(value: String?, def: Int): Int { - return parseInteger(value, def) + + protected fun parseInteger(value: String, def: Int): Int { + return com.itsaky.androidide.inflater.internal.utils.parseInteger(value, def) } - - protected fun parseBoolean(value: String?): Boolean { - return parseBoolean(value) + + protected fun parseBoolean(value: String): Boolean { + return com.itsaky.androidide.inflater.internal.utils.parseBoolean(value) } - - protected fun parseDrawable(context: Context?, value: String?): Drawable { - return parseDrawable(context, value) + + protected fun parseDrawable(context: Context, value: String): Drawable { + return com.itsaky.androidide.inflater.internal.utils.parseDrawable(context, value) } - - protected fun parseGravity(value: String?): Int { - return parseGravity(value) + + protected fun parseGravity(value: String): Int { + return com.itsaky.androidide.inflater.internal.utils.parseGravity(value) } - + protected fun parseDimension(context: Context?, value: String?): Int { - return parseDimension(context!!, value, 0f).toInt() + return com.itsaky.androidide.inflater.internal.utils + .parseDimension(context!!, value, 0f) + .toInt() } - - protected fun parseColor(context: Context?, value: String?): Int { - return parseColor(context, value) + + protected fun parseColor(context: Context, value: String): Int { + return com.itsaky.androidide.inflater.internal.utils.parseColor(context, value) } - + /** * Actual implementation of the parse logic. * * @return The parsed drawable. Maybe `null`. * @throws Exception If any fatal error occurs while parsing the drawable. */ - @Throws(Exception::class) - protected abstract fun parseDrawable(context: Context): Drawable? - + @Throws(Exception::class) protected abstract fun parseDrawable(context: Context): Drawable? + /** * Find the index of the attribute with the given name. * @@ -109,7 +110,7 @@ abstract class IDrawableParser protected constructor(protected open val parser: } return -1 } - + /** * Get the value of the attribute at the given index. * @@ -119,28 +120,27 @@ abstract class IDrawableParser protected constructor(protected open val parser: protected fun value(index: Int): String { return parser!!.getAttributeValue(index) } - + /** * Checks if the drawable parser is allowed to parse at the current depth. This must be checked if * the drawable parser keeps looking for [XmlPullParser.START_TAG] or [ ][XmlPullParser.END_TAG]. * - * - * The [LayerListParser] can contain tags which are expected to be parsed by other - * parsers. So, if the nesting parser does not check if this method if `true`, it might - * consume all the events and the [LayerListParser] will have invalid data to parse. In some - * cases, the nested parser might consume all the events until [XmlPullParser.END_DOCUMENT]. + * The [LayerListParser] can contain tags which are expected to be parsed by other parsers. So, if + * the nesting parser does not check if this method if `true`, it might consume all the events and + * the [LayerListParser] will have invalid data to parse. In some cases, the nested parser might + * consume all the events until [XmlPullParser.END_DOCUMENT]. * * @return `true` if the parser can keep parsing, `false` otherwise. */ protected fun canParse(): Boolean { return parser!!.depth >= minDepth } - + @get:ChecksSdkIntAtLeast(api = VERSION_CODES.Q) protected val isApi29: Boolean get() = VERSION.SDK_INT >= 29 - + companion object { const val ANY_DEPTH = -1 } -} \ No newline at end of file +} diff --git a/xml-inflater/src/main/java/com/itsaky/androidide/inflater/internal/utils/parseUtils.kt b/xml-inflater/src/main/java/com/itsaky/androidide/inflater/internal/utils/parseUtils.kt index a72fa4aad..f83ebf95a 100644 --- a/xml-inflater/src/main/java/com/itsaky/androidide/inflater/internal/utils/parseUtils.kt +++ b/xml-inflater/src/main/java/com/itsaky/androidide/inflater/internal/utils/parseUtils.kt @@ -25,12 +25,14 @@ import android.util.TypedValue.complexToDimension import android.view.Gravity import android.view.ViewGroup.LayoutParams import androidx.core.text.isDigitsOnly +import com.android.SdkConstants.EXT_XML import com.android.aaptcompiler.AaptResourceType import com.android.aaptcompiler.AaptResourceType.ARRAY import com.android.aaptcompiler.AaptResourceType.ATTR import com.android.aaptcompiler.AaptResourceType.BOOL import com.android.aaptcompiler.AaptResourceType.COLOR import com.android.aaptcompiler.AaptResourceType.DIMEN +import com.android.aaptcompiler.AaptResourceType.DRAWABLE import com.android.aaptcompiler.AaptResourceType.INTEGER import com.android.aaptcompiler.AaptResourceType.STRING import com.android.aaptcompiler.ArrayResource @@ -38,6 +40,7 @@ import com.android.aaptcompiler.AttributeResource import com.android.aaptcompiler.BasicString import com.android.aaptcompiler.BinaryPrimitive import com.android.aaptcompiler.ConfigDescription +import com.android.aaptcompiler.FileReference import com.android.aaptcompiler.RawString import com.android.aaptcompiler.Reference import com.android.aaptcompiler.ResourceEntry @@ -52,8 +55,10 @@ import com.android.aaptcompiler.tryParseBool import com.android.aaptcompiler.tryParseFlagSymbol import com.android.aaptcompiler.tryParseInt import com.android.aaptcompiler.tryParseReference +import com.itsaky.androidide.inflater.drawable.DrawableParserFactory import com.itsaky.androidide.projects.api.AndroidModule import com.itsaky.androidide.utils.ILogger +import java.io.File import java.util.regex.Pattern private var currentModule: AndroidModule? = null @@ -171,15 +176,27 @@ fun parseDrawable(context: Context, value: String, def: Drawable = unknownDrawab if (HEX_COLOR.matcher(value).matches()) { return parseColorDrawable(context, value) } - - // if (value[0] == '@') { - // val (pck, type, name) = parseResourceReference(value) - // return if(pck == null) { - // parseDrawableResRef(type, name, value) - // } else { - // parseQualifiedDrawableResRef() - // } - // } + val drawableResolver: (Value?) -> Drawable? = + fun(it): Drawable? { + if (it is FileReference) { + val file = File(it.path.value()) + if (!file.exists() || file.extension != EXT_XML) { + return null + } + val parser = DrawableParserFactory.newParser(context, file) ?: return null + return parser.parse(context) + } + // TODO(itsaky) : Drawable of any type other than a file? + return null + } + if (value[0] == '@') { + return parseReference( + value = value, + expectedType = DRAWABLE, + def = def, + resolver = drawableResolver + ) + } return def } @@ -278,7 +295,7 @@ fun parseFloat(value: String, defValue: Float): Float { @JvmOverloads fun parseGravity(value: String, def: Int = defaultGravity()): Int { val attr = findAttributeResource("android", ATTR, "gravity") ?: return defaultGravity() - return parseFlag(attr = attr, value = value, def = defaultGravity()) + return parseFlag(attr = attr, value = value, def = def) } fun parseFlag(attr: AttributeResource, value: String, def: Int = -1): Int { @@ -389,19 +406,19 @@ fun resolveResourceReference( def: T, resolver: (Value?) -> T? ): T { - val dimenValue = entry.findValue(ConfigDescription())!!.value - if (dimenValue is Reference) { + val value = entry.findValue(ConfigDescription())!!.value + if (value is Reference) { return resolveResourceReference( table = table, type = type, pck = pck.name, - name = dimenValue.name.entry!!, + name = value.name.entry!!, def = def, resolver = resolver ) } - return resolver(dimenValue) + return resolver(value) ?: run { log.warn("Unable to resolve dimension reference '$name'") def diff --git a/xml-inflater/src/test/java/com/itsaky/androidide/inflater/ValueParsersTest.kt b/xml-inflater/src/test/java/com/itsaky/androidide/inflater/ValueParsersTest.kt index e0abad595..063707b0e 100644 --- a/xml-inflater/src/test/java/com/itsaky/androidide/inflater/ValueParsersTest.kt +++ b/xml-inflater/src/test/java/com/itsaky/androidide/inflater/ValueParsersTest.kt @@ -18,7 +18,12 @@ package com.itsaky.androidide.inflater import android.graphics.Color +import android.graphics.drawable.BitmapDrawable +import android.graphics.drawable.ClipDrawable import android.graphics.drawable.ColorDrawable +import android.graphics.drawable.GradientDrawable +import android.graphics.drawable.LayerDrawable +import android.graphics.drawable.ShapeDrawable import com.google.common.truth.Truth.assertThat import com.itsaky.androidide.inflater.internal.utils.endParse import com.itsaky.androidide.inflater.internal.utils.parseBoolean @@ -30,10 +35,10 @@ import com.itsaky.androidide.inflater.internal.utils.parseIntegerArray import com.itsaky.androidide.inflater.internal.utils.parseString import com.itsaky.androidide.inflater.internal.utils.parseStringArray import com.itsaky.androidide.inflater.internal.utils.startParse +import kotlin.math.roundToInt import org.junit.Test import org.junit.runner.RunWith import org.robolectric.RobolectricTestRunner -import kotlin.math.roundToInt @RunWith(RobolectricTestRunner::class) class ValueParsersTest { @@ -85,7 +90,7 @@ class ValueParsersTest { } @Test - fun `simple color drawable parse test`() { + fun `drawable parse test`() { inflaterTest { module -> requiresActivity { activity -> startParse(module) @@ -94,6 +99,14 @@ class ValueParsersTest { assertThat(this).isInstanceOf(ColorDrawable::class.java) assertThat((this as ColorDrawable).color).isEqualTo(Color.RED) } + parseDrawable(activity, "@android:drawable/ab_share_pack_material").apply { + assertThat(this).isNotNull() + assertThat(this).isInstanceOf(BitmapDrawable::class.java) + } + parseDrawable(activity, "@android:drawable/action_bar_background").apply { + assertThat(this).isNotNull() + assertThat(this).isInstanceOf(GradientDrawable::class.java) + } endParse() } } @@ -191,8 +204,9 @@ class ValueParsersTest { assertThat(parseColor(activity, "@android:color/white")).isEqualTo(Color.WHITE) assertThat(parseColor(activity, "@android:color/transparent")).isEqualTo(Color.TRANSPARENT) assertThat(parseColor(activity, "@color/test_color")).isEqualTo(Color.parseColor("#f44336")) - assertThat(parseColor(activity, "@color/test_color_ref")).isEqualTo(Color.parseColor("#f44336")) - + assertThat(parseColor(activity, "@color/test_color_ref")) + .isEqualTo(Color.parseColor("#f44336")) + // TODO Implement color state list parser assertThat(parseColor(activity, "@color/test_selector")).isEqualTo(Color.TRANSPARENT) endParse()