diff --git a/projects/sdk/core/gradle-plugin/src/main/kotlin/com/tencent/shadow/core/gradle/AGPCompat.kt b/projects/sdk/core/gradle-plugin/src/main/kotlin/com/tencent/shadow/core/gradle/AGPCompat.kt index e527b5e58..f13ce7bb8 100644 --- a/projects/sdk/core/gradle-plugin/src/main/kotlin/com/tencent/shadow/core/gradle/AGPCompat.kt +++ b/projects/sdk/core/gradle-plugin/src/main/kotlin/com/tencent/shadow/core/gradle/AGPCompat.kt @@ -22,6 +22,7 @@ import com.android.build.gradle.BaseExtension import com.android.build.gradle.api.ApplicationVariant import com.android.build.gradle.api.BaseVariantOutput import com.android.build.gradle.internal.dsl.ProductFlavor +import org.gradle.api.Project import org.gradle.api.Task import java.io.File @@ -32,8 +33,11 @@ internal interface AGPCompat { fun addFlavorDimension(baseExtension: BaseExtension, dimensionName: String) fun setProductFlavorDefault(productFlavor: ProductFlavor, isDefault: Boolean) fun getProcessResourcesTask(output: BaseVariantOutput): Task - fun getProcessResourcesFile(processResourcesTask: Task, variantName: String): File + fun getAaptAdditionalParameters(processResourcesTask: Task): List fun getMinSdkVersion(pluginVariant: ApplicationVariant): Int fun hasDeprecatedTransformApi(): Boolean + fun getProcessManifestTask(output: BaseVariantOutput): Task + fun getProcessManifestFile(project: Project, pluginVariant: ApplicationVariant, output: BaseVariantOutput): File + fun getRTxtFile(project: Project, processResourcesTask: Task?, variantName: String): File } \ No newline at end of file diff --git a/projects/sdk/core/gradle-plugin/src/main/kotlin/com/tencent/shadow/core/gradle/AGPCompatImpl.kt b/projects/sdk/core/gradle-plugin/src/main/kotlin/com/tencent/shadow/core/gradle/AGPCompatImpl.kt index a12f8846c..7a20ae106 100644 --- a/projects/sdk/core/gradle-plugin/src/main/kotlin/com/tencent/shadow/core/gradle/AGPCompatImpl.kt +++ b/projects/sdk/core/gradle-plugin/src/main/kotlin/com/tencent/shadow/core/gradle/AGPCompatImpl.kt @@ -1,20 +1,15 @@ package com.tencent.shadow.core.gradle -import com.android.SdkConstants import com.android.build.gradle.BaseExtension import com.android.build.gradle.api.ApplicationVariant import com.android.build.gradle.api.BaseVariantOutput import com.android.build.gradle.internal.dsl.ProductFlavor import com.android.build.gradle.internal.res.LinkApplicationAndroidResourcesTask -import com.android.build.gradle.internal.scope.InternalArtifactType import com.android.sdklib.AndroidVersion.VersionCodes +import org.gradle.api.Project import org.gradle.api.Task -import org.gradle.api.file.Directory -import org.gradle.api.file.DirectoryProperty import org.gradle.api.provider.Property import java.io.File -import kotlin.reflect.full.declaredMemberProperties -import kotlin.reflect.jvm.isAccessible internal class AGPCompatImpl : AGPCompat { @@ -25,58 +20,6 @@ internal class AGPCompatImpl : AGPCompat { output.processResources } - override fun getProcessResourcesFile(processResourcesTask: Task, variantName: String): File { - val capitalizeVariantName = variantName.capitalize() - - return try { - File( - processResourcesTask.outputs.files.files.first { it.name.equals("out") }, - "resources-$variantName.ap_" - ) - - // 使用 resPackageOutputFolder - // 获取的路径和上方路径一致 - // 备选(不推荐) - /*File( - (processResourcesTask as LinkApplicationAndroidResourcesTask).resPackageOutputFolder.asFile.get(), - "resources-$variantName.ap_" - )*/ - } catch (ignored: Exception) { - // 高版本 AGP - try { - // 通过反射获取 KProperty: linkedResourcesOutputDir、linkedResourcesArtifactType - val linkedResourcesOutputDir = - LinkApplicationAndroidResourcesTask::class.declaredMemberProperties.first { - it.name == "linkedResourcesOutputDir" - }.let { - it.isAccessible = true - it.getter.call(processResourcesTask) as DirectoryProperty - } - - @Suppress("UNCHECKED_CAST") - val linkedResourcesArtifactType = - LinkApplicationAndroidResourcesTask::class.declaredMemberProperties.first { - it.name == "linkedResourcesArtifactType" - }.let { - it.isAccessible = true - it.getter.call(processResourcesTask) as Property> - } - - File( - linkedResourcesOutputDir.asFile.get(), - linkedResourcesArtifactType.get().name().lowercase() - .replace("_", "-") + "-" + variantName + SdkConstants.DOT_RES - ) - } catch (ignored: Exception) { - // 反射获取出错,备用 - File( - processResourcesTask.outputs.files.files.first { it.name.equals("process${capitalizeVariantName}Resources") }, - "linked-resources-binary-format-$variantName.ap_" - ) - } - } - } - @Suppress("PrivateApi") override fun getAaptAdditionalParameters(processResourcesTask: Task): List = try { @@ -144,6 +87,105 @@ internal class AGPCompatImpl : AGPCompat { return true } + override fun getProcessManifestTask(output: BaseVariantOutput): Task { + return try { + output.processManifestProvider.get() + } catch (_: Error) { + output.processManifest + } + } + + /** + * 获取合并后的 AndroidManifest.xml 文件。 + * + * 优先从 processManifest 任务输出获取,否则搜索 intermediates 目录。 + */ + override fun getProcessManifestFile( + project: Project, + pluginVariant: ApplicationVariant, + output: BaseVariantOutput + ): File { + // 1. 优先从任务输出获取 + try { + output.processManifestProvider.get().outputs.files.files.forEach { + findFileByName(it, "AndroidManifest.xml")?.let { file -> return file } + } + } catch (_: Exception) { + // 忽略 + } + + val variantName = pluginVariant.name + + // 2. 搜索中间产物目录 + return listOf( + "intermediates/merged_manifests/$variantName", // AGP 4.x/7.x/8.x + "intermediates/manifests/full/$variantName", // AGP 3.x + ) + .map { File(project.buildDir, it) } + .first { + findFileByName(it, "AndroidManifest.xml") != null + } + } + + /** + * 获取 R.txt 文件。 + * + * 优先从 processResources 任务的输出获取(最准确), 否则搜索 intermediates 目录。 + */ + override fun getRTxtFile( + project: Project, + processResourcesTask: Task?, + variantName: String + ): File { + // 1. 优先尝试从任务输出中查找 + if (processResourcesTask != null) { + try { + processResourcesTask.outputs.files.files.forEach { + findFileByName(it, "R.txt")?.let { file -> return file } + } + } catch (_: Exception) { + // 忽略解析错误,继续走备选路径 + } + } + + // 2. 根据 AGP 版本已知的中间产物路径搜索 + return listOf( + "intermediates/runtime_symbol_list/$variantName", // AGP 4.x/7.x/8.x + "intermediates/symbols/$variantName", + "intermediates/bundles/$variantName" + ) + .map { File(project.buildDir, it) } + .first { + findFileByName(it, "R.txt") != null + } + } + + /** + * 搜索指定目录下指定文件名的文件。 + * + * @return 文件对象,若找不到则返回 null 。 + */ + private fun findFileByName(file: File, fileName:String): File? { + if (!file.exists()) { + return null + } + if (file.isFile && file.name == fileName) { + return file + } + if (file.isDirectory) { + val subFiles = file.listFiles() + if (subFiles != null) { + for (subFile in subFiles) { + val resultFile = findFileByName(subFile, fileName) + if (resultFile != null) { + return resultFile + } + } + } + } + return null + } + companion object { fun getStringFromProperty(x: Any?): String { return when (x) { diff --git a/projects/sdk/core/gradle-plugin/src/main/kotlin/com/tencent/shadow/core/gradle/ShadowPlugin.kt b/projects/sdk/core/gradle-plugin/src/main/kotlin/com/tencent/shadow/core/gradle/ShadowPlugin.kt index 1f54a2685..91edc9463 100644 --- a/projects/sdk/core/gradle-plugin/src/main/kotlin/com/tencent/shadow/core/gradle/ShadowPlugin.kt +++ b/projects/sdk/core/gradle-plugin/src/main/kotlin/com/tencent/shadow/core/gradle/ShadowPlugin.kt @@ -26,6 +26,7 @@ import com.android.build.gradle.BaseExtension import com.android.build.gradle.api.ApplicationVariant import com.android.sdklib.AndroidVersion.VersionCodes import com.tencent.shadow.core.gradle.extensions.PackagePluginExtension +import com.tencent.shadow.core.manifest_parser.createResourceMapper import com.tencent.shadow.core.manifest_parser.generatePluginManifest import com.tencent.shadow.core.transform.DeprecatedTransformWrapper import com.tencent.shadow.core.transform.GradleTransformWrapper @@ -35,8 +36,6 @@ import com.tencent.shadow.core.transform_kit.ClassPoolBuilder import org.gradle.api.* import org.gradle.api.tasks.compile.JavaCompile import java.io.File -import java.net.URLClassLoader -import java.util.zip.ZipFile class ShadowPlugin : Plugin { @@ -104,8 +103,6 @@ class ShadowPlugin : Plugin { createPackagePluginTasks(project) - addLocateApkanalyzerTask(project) - onEachPluginVariant(project) { pluginVariant -> checkAaptPackageIdConfig(pluginVariant) @@ -118,49 +115,6 @@ class ShadowPlugin : Plugin { checkKotlinAndroidPluginForPluginManifestTask(project) } - private fun addLocateApkanalyzerTask(project: Project) { - val appExtension: AppExtension = - project.extensions.getByType(AppExtension::class.java) - val sdkDirectory = appExtension.sdkDirectory - val outputFile = project.locateApkanalyzerResultPath() - - project.tasks.register(locateApkanalyzerTaskName) { - it.inputs.property("sdkPath", sdkDirectory.path) - it.outputs.file(outputFile).withPropertyName("locateApkanalyzerResultPath") - - it.doLast { - // 如果其他project的此任务执行过了,就不用再查找了 - if (outputFile.exists() && File(outputFile.readText()).exists()) { - return@doLast - } - - // 找出apkanalyzer.jar.它是build tool的一部分,但位置随着版本有变化,所以这里用搜索文件确定位置 - // 如果有多个版本,随机取第一个,因为只用decodeXml方法,预期不同版本没什么区别。 - val apkanalyzerJarFile = - try { - sdkDirectory.walk().filter { file -> - listOf( - "apkanalyzer.jar",// 低版本build tools - "apkanalyzer-classpath.jar",// 2020-06-05 cmdline-tools version 2.0 - ).any { it == file.name } - }.first() - } catch (e: NoSuchElementException) { - // https://developer.android.com/studio/command-line/apkanalyzer - // https://developer.android.com/studio/releases/sdk-tools - // https://cs.android.com/android/platform/superproject/+/master:prebuilts/cmdline-tools/tools/bin/apkanalyzer;l=67;bpv=1;bpt=0 - throw Error( - "找不到apkanalyzer.它来自:" + - "cmdline-tools." + - "如果高版本SDK也找不到这个文件,Shadow就需要更新了。" - ) - } - - outputFile.parentFile.mkdirs() - outputFile.writeText(apkanalyzerJarFile.absolutePath) - } - } - } - /** * GeneratePluginManifestTask会向android DSL添加新的java源码目录, * 而kotlin-android会在syncKotlinAndAndroidSourceSets中接管java的源码目录, @@ -221,49 +175,30 @@ class ShadowPlugin : Plugin { // 找出ap_文件 val processResourcesTask = agpCompat.getProcessResourcesTask(output) - val processedResFile = agpCompat.getProcessResourcesFile(processResourcesTask, variantName) - - // decodeBinaryManifestTask输出的apkanalyzer manifest print结果文件 - val decodeXml = File( - project.buildDir, - "intermediates/decodeBinaryManifest/$variantName/AndroidManifest.xml" - ) - - // 添加decodeXml任务 - val decodeBinaryManifestTask = - project.tasks.register("decode${capitalizeVariantName}BinaryManifest") { - it.dependsOn(locateApkanalyzerTaskName) - it.dependsOn(processResourcesTask) - it.inputs.file(processedResFile) - it.outputs.file(decodeXml).withPropertyName("decodeXml") - - it.doLast { - val zipFile = ZipFile(processedResFile) - val binaryXml = zipFile.getInputStream( - zipFile.getEntry("AndroidManifest.xml") - ).readBytes() - - val outputXmlBytes = decodeXml(project, binaryXml) - decodeXml.parentFile.mkdirs() - decodeXml.writeBytes(outputXmlBytes) - } - } - // 添加生成PluginManifest.java任务 val pluginManifestSourceDir = File(project.buildDir, "generated/source/pluginManifest/$variantName") val generatePluginManifestTask = project.tasks.register("generate${capitalizeVariantName}PluginManifest") { - it.dependsOn(decodeBinaryManifestTask) - it.inputs.file(decodeXml) + // 必须依赖 processManifest 任务以获取合并后的 AndroidManifest.xml + val mergedTask = agpCompat.getProcessManifestTask(output) + it.dependsOn(mergedTask) + it.dependsOn(processResourcesTask) + it.outputs.dir(pluginManifestSourceDir).withPropertyName("pluginManifestSourceDir") it.doLast { + // 解析合并后的 AndroidManifest.xml + R.txt + // 这种方案直接解析 XML 格式的 AndroidManifest.xml ,不再依赖 aapt2 产生的二进制产物 + val mergedManifest = agpCompat.getProcessManifestFile(project, pluginVariant, output) + val rTxt = agpCompat.getRTxtFile(project, processResourcesTask, variantName) + val resourceMapper = createResourceMapper(rTxt) generatePluginManifest( - decodeXml, + mergedManifest, pluginManifestSourceDir, - "com.tencent.shadow.core.manifest_parser" + "com.tencent.shadow.core.manifest_parser", + resourceMapper ) } } @@ -276,50 +211,6 @@ class ShadowPlugin : Plugin { (javacTask as JavaCompile).source(project.fileTree(relativePath)) } - /** - * 反射apkanalyzer中的BinaryXmlParser类的decodeXml方法 - */ - @Suppress("PrivateApi") - private fun decodeXml(project: Project, binaryXml: ByteArray): ByteArray { - val jarPath = File(project.locateApkanalyzerResultPath().readText()) - val tempCL = URLClassLoader(arrayOf(jarPath.toURL()), contextClassLoader) - val binaryXmlParserClass = - tempCL.loadClass("com.android.tools.apk.analyzer.BinaryXmlParser") - return try { - decodeXmlMethodV1(binaryXmlParserClass, binaryXml) - } catch (ignored: Exception) { - decodeXmlMethodV2(binaryXmlParserClass, binaryXml) - } - } - - private fun decodeXmlMethodV1(binaryXmlParserClass: Class<*>, binaryXml: ByteArray): ByteArray { - val decodeXmlMethod = binaryXmlParserClass.getDeclaredMethod( - "decodeXml", - String::class.java, - ByteArray::class.java - ) - return decodeXmlMethod.invoke( - null, - "AndroidManifest.xml", - binaryXml - ) as ByteArray - } - - /** - * 新版本代码中删掉了一个String参数,这个参数原来只用于log输出了 - * https://cs.android.com/android-studio/platform/tools/base/+/6a81855c2fa102ae4532ad9a645e40177770a26a:apkparser/analyzer/src/main/java/com/android/tools/apk/analyzer/BinaryXmlParser.java;dlc=598c38100e4fb2b001385faea994fcb54cc515b1 - */ - private fun decodeXmlMethodV2(binaryXmlParserClass: Class<*>, binaryXml: ByteArray): ByteArray { - val decodeXmlMethod = binaryXmlParserClass.getDeclaredMethod( - "decodeXml", - ByteArray::class.java - ) - return decodeXmlMethod.invoke( - null, - binaryXml - ) as ByteArray - } - /** * 检查插件是否修改了资源ID分区 * @@ -446,10 +337,6 @@ class ShadowPlugin : Plugin { } companion object { - const val locateApkanalyzerTaskName = "locateApkanalyzer" - private fun Project.locateApkanalyzerResultPath() = - File(rootProject.buildDir, "shadow/ApkanalyzerPath.txt") - private fun buildAgpCompat(project: Project): AGPCompat { return AGPCompatImpl() } diff --git a/projects/sdk/core/manifest-parser/src/main/kotlin/com/tencent/shadow/core/manifest_parser/ManifestParser.kt b/projects/sdk/core/manifest-parser/src/main/kotlin/com/tencent/shadow/core/manifest_parser/ManifestParser.kt index 0b389ce2e..8d9a6d39e 100644 --- a/projects/sdk/core/manifest-parser/src/main/kotlin/com/tencent/shadow/core/manifest_parser/ManifestParser.kt +++ b/projects/sdk/core/manifest-parser/src/main/kotlin/com/tencent/shadow/core/manifest_parser/ManifestParser.kt @@ -1,6 +1,7 @@ package com.tencent.shadow.core.manifest_parser import java.io.File +import java.util.Collections /** * manifest-parser的入口方法 @@ -9,13 +10,73 @@ import java.io.File * 一般位于apk工程的build/intermediates/merged_manifest目录中。 * @param outputDir 生成文件的输出目录 * @param packageName 生成类的包名 + * @param resourceMapper 资源映射器 */ fun generatePluginManifest( xmlFile: File, outputDir: File, - packageName: String + packageName: String, + resourceMapper: (String) -> String ) { val androidManifest = AndroidManifestReader().read(xmlFile) val generator = PluginManifestGenerator() - generator.generate(androidManifest, outputDir, packageName) -} \ No newline at end of file + generator.generate(androidManifest, outputDir, packageName, resourceMapper) +} + +/** + * 创建资源映射器。 + * + * @param rTxt R.txt文件 + * @return 资源映射器 + */ +fun createResourceMapper(rTxt: File): (String) -> String { + val rTxtMap = parseRTxt(rTxt) + + return { resName -> + if (resName.startsWith("@android:")) { + // @android:style/Theme.NoTitleBar -> android.R.style.Theme_NoTitleBar + val parts = resName.substringAfter("@android:").split("/") + val type = parts[0] + val name = parts[1].replace(".", "_") + "android.R.$type.$name" + } else { + // @[package:]type/name -> id 值 + var raw = resName.substringAfter("@") + if (raw.contains(":")) { + raw = raw.substringAfter(":") + } + val parts = raw.split("/") + val type = parts[0] + val name = parts[1].replace('.', '_') + val key = "@$type/$name" + rTxtMap[key] ?: throw IllegalArgumentException("Resource not found in R.txt: $resName (normalized: $key)") + } + } +} + +/** + * 解析 R.txt 文件并生成资源 ID 映射表。 R.txt 包含项目引用的所有资源 ID。 + * + * @param rTxtFile R.txt 文件对象 + * @return 资源全称(如 @string/app_name)到 ID 的映射 + */ +fun parseRTxt(rTxtFile: File): Map { + if (!rTxtFile.exists()) return Collections.emptyMap() + + val map = mutableMapOf() + rTxtFile.useLines { + it.forEach { line -> + if (!(line.startsWith("int "))) { + return@forEach + } + val parts = line.split(Regex("\\s+")).filter { it.isNotBlank() } + if (parts.size == 4 && parts[0] == "int") { + val type = parts[1] + val name = parts[2] + val idStr = parts[3] + map["@$type/$name"] = idStr + } + } + } + return map +} diff --git a/projects/sdk/core/manifest-parser/src/main/kotlin/com/tencent/shadow/core/manifest_parser/PluginManifestGenerator.kt b/projects/sdk/core/manifest-parser/src/main/kotlin/com/tencent/shadow/core/manifest_parser/PluginManifestGenerator.kt index 0a84018c3..b9f29bc94 100644 --- a/projects/sdk/core/manifest-parser/src/main/kotlin/com/tencent/shadow/core/manifest_parser/PluginManifestGenerator.kt +++ b/projects/sdk/core/manifest-parser/src/main/kotlin/com/tencent/shadow/core/manifest_parser/PluginManifestGenerator.kt @@ -3,7 +3,6 @@ package com.tencent.shadow.core.manifest_parser import com.squareup.javapoet.* import com.tencent.shadow.core.runtime.PluginManifest import java.io.File -import java.util.* import javax.lang.model.element.Modifier /** @@ -21,9 +20,15 @@ class PluginManifestGenerator { * @param manifestMap AndroidManifestReader#read的输出Map * @param outputDir 生成文件的输出目录 * @param packageName 生成类的包名 + * @param resourceMapper 资源映射器。用于将资源名称映射为资源 ID 值 */ - fun generate(manifestMap: ManifestMap, outputDir: File, packageName: String) { - val pluginManifestBuilder = PluginManifestBuilder(manifestMap) + fun generate( + manifestMap: ManifestMap, + outputDir: File, + packageName: String, + resourceMapper: (String) -> String + ) { + val pluginManifestBuilder = PluginManifestBuilder(manifestMap, resourceMapper) val pluginManifest = pluginManifestBuilder.build() JavaFile.builder(packageName, pluginManifest) .build() @@ -31,7 +36,10 @@ class PluginManifestGenerator { } } -private class PluginManifestBuilder(val manifestMap: ManifestMap) { +private class PluginManifestBuilder( + val manifestMap: ManifestMap, + val resourceMapper: (String) -> String +) { val classBuilder: TypeSpec.Builder = TypeSpec.classBuilder("PluginManifest") .addSuperinterface(ClassName.get(PluginManifest::class.java)) @@ -172,7 +180,7 @@ private class PluginManifestBuilder(val manifestMap: ManifestMap) { manifestValue: Any, ): FieldSpec { - val resIdLiteral = themeStringToResId(manifestValue) + val resIdLiteral = themeStringToResId(manifestValue, resourceMapper) return privateStaticFinalIntFieldBuilder(fieldName) .initializer( CodeBlock.of("$1L", resIdLiteral) @@ -196,17 +204,17 @@ private class PluginManifestBuilder(val manifestMap: ManifestMap) { } val themeLiteral = makeResIdLiteral(AndroidManifestKeys.theme) { - themeStringToResId(it) + themeStringToResId(it, resourceMapper) } val configChangesLiteral = makeResIdLiteral(AndroidManifestKeys.configChanges) { - it + parseConfigChanges(it) } val softInputModeLiteral = makeResIdLiteral(AndroidManifestKeys.windowSoftInputMode) { - it + parseSoftInputMode(it) } val screenOrientation = makeResIdLiteral(AndroidManifestKeys.screenOrientation, "-1") { - it + parseScreenOrientation(it) } return "new com.tencent.shadow.core.runtime.PluginManifest" + @@ -279,14 +287,88 @@ private class PluginManifestBuilder(val manifestMap: ManifestMap) { fun nullCodeBlock() = CodeBlock.of("null")!! - fun themeStringToResId(manifestValue: Any): String { + fun themeStringToResId(manifestValue: Any, resourceMapper: ((String) -> String)): String { val formatValue = manifestValue as String // for example: @ref/0x7e0b009e if (formatValue.startsWith("@ref/")) { return formatValue.removePrefix("@ref/") + } else if (formatValue.startsWith("@")) { + // @style/Theme.AppCompat + return resourceMapper(formatValue) } else { // 其余格式:https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-main:apkparser/analyzer/src/main/java/com/android/tools/apk/analyzer/BinaryXmlParser.java;l=193 - throw TODO("不支持其他格式") + throw TODO("不支持其他格式: $formatValue") } } + + fun parseConfigChanges(value: String): String { + if (value.startsWith("0x") || value.toIntOrNull() != null) return value + return value.split("|").joinToString("|") { + val constant = when (it) { + "mcc" -> "CONFIG_MCC" + "mnc" -> "CONFIG_MNC" + "locale" -> "CONFIG_LOCALE" + "touchscreen" -> "CONFIG_TOUCHSCREEN" + "keyboard" -> "CONFIG_KEYBOARD" + "keyboardHidden" -> "CONFIG_KEYBOARD_HIDDEN" + "navigation" -> "CONFIG_NAVIGATION" + "orientation" -> "CONFIG_ORIENTATION" + "screenLayout" -> "CONFIG_SCREEN_LAYOUT" + "uiMode" -> "CONFIG_UI_MODE" + "screenSize" -> "CONFIG_SCREEN_SIZE" + "smallestScreenSize" -> "CONFIG_SMALLEST_SCREEN_SIZE" + "density" -> "CONFIG_DENSITY" + "layoutDirection" -> "CONFIG_LAYOUT_DIRECTION" + "fontScale" -> "CONFIG_FONT_SCALE" + "colorMode" -> "CONFIG_COLOR_MODE" // Added in API 26 + else -> throw IllegalArgumentException("Unknown configChanges: $it") + } + "android.content.pm.ActivityInfo.$constant" + } + } + + fun parseSoftInputMode(value: String): String { + if (value.startsWith("0x") || value.toIntOrNull() != null) return value + return value.split("|").joinToString("|") { + val constant = when (it) { + "stateUnspecified" -> "SOFT_INPUT_STATE_UNSPECIFIED" + "stateUnchanged" -> "SOFT_INPUT_STATE_UNCHANGED" + "stateHidden" -> "SOFT_INPUT_STATE_HIDDEN" + "stateAlwaysHidden" -> "SOFT_INPUT_STATE_ALWAYS_HIDDEN" + "stateVisible" -> "SOFT_INPUT_STATE_VISIBLE" + "stateAlwaysVisible" -> "SOFT_INPUT_STATE_ALWAYS_VISIBLE" + "adjustUnspecified" -> "SOFT_INPUT_ADJUST_UNSPECIFIED" + "adjustResize" -> "SOFT_INPUT_ADJUST_RESIZE" + "adjustPan" -> "SOFT_INPUT_ADJUST_PAN" + "adjustNothing" -> "SOFT_INPUT_ADJUST_NOTHING" + "isForwardNavigation" -> "SOFT_INPUT_IS_FORWARD_NAVIGATION" + else -> throw IllegalArgumentException("Unknown windowSoftInputMode: $it") + } + "android.view.WindowManager.LayoutParams.$constant" + } + } + + fun parseScreenOrientation(value: String): String { + if (value.startsWith("0x") || value.toIntOrNull() != null) return value + val constant = when (value) { + "unspecified" -> "SCREEN_ORIENTATION_UNSPECIFIED" + "landscape" -> "SCREEN_ORIENTATION_LANDSCAPE" + "portrait" -> "SCREEN_ORIENTATION_PORTRAIT" + "user" -> "SCREEN_ORIENTATION_USER" + "behind" -> "SCREEN_ORIENTATION_BEHIND" + "sensor" -> "SCREEN_ORIENTATION_SENSOR" + "nosensor" -> "SCREEN_ORIENTATION_NOSENSOR" + "sensorLandscape" -> "SCREEN_ORIENTATION_SENSOR_LANDSCAPE" + "sensorPortrait" -> "SCREEN_ORIENTATION_SENSOR_PORTRAIT" + "reverseLandscape" -> "SCREEN_ORIENTATION_REVERSE_LANDSCAPE" + "reversePortrait" -> "SCREEN_ORIENTATION_REVERSE_PORTRAIT" + "fullSensor" -> "SCREEN_ORIENTATION_FULL_SENSOR" + "userLandscape" -> "SCREEN_ORIENTATION_USER_LANDSCAPE" + "userPortrait" -> "SCREEN_ORIENTATION_USER_PORTRAIT" + "fullUser" -> "SCREEN_ORIENTATION_FULL_USER" + "locked" -> "SCREEN_ORIENTATION_LOCKED" + else -> throw IllegalArgumentException("Unknown screenOrientation: $value") + } + return "android.content.pm.ActivityInfo.$constant" + } } } diff --git a/projects/test/gradle-plugin-agp-compat-test/stub-project/build.gradle b/projects/test/gradle-plugin-agp-compat-test/stub-project/build.gradle index 4aaacabea..85ce056c5 100644 --- a/projects/test/gradle-plugin-agp-compat-test/stub-project/build.gradle +++ b/projects/test/gradle-plugin-agp-compat-test/stub-project/build.gradle @@ -63,6 +63,13 @@ android { versionName VERSION_NAME } + buildTypes { + debug { + minifyEnabled true + shrinkResources true + } + } + // 测试插件项目存在自定义flavorDimensions flavorDimensions(*flavorDimensionList, 'DimensionA', 'DimensionB') productFlavors {