6.6.2 - Alpha2 - project.json 支持 signatureScheme 选项宽松匹配

This commit is contained in:
SuperMonster003
2025-02-05 21:07:38 +08:00
parent 08c64bf248
commit 7e59981147
7 changed files with 326 additions and 173 deletions

View File

@@ -55,7 +55,7 @@
| <span style="word-break:keep-all;white-space:nowrap">[Auto.js Pro 9](https://pro.autojs.org/)</span> | <span style="word-break:keep-all;white-space:nowrap">AutoJsPro</span> | <span style="word-break:keep-all;white-space:nowrap">[hyb1996](https://github.com/hyb1996)</span> | <span style="word-break:keep-all;white-space:nowrap">`2021/03/28`</span> | <span style="word-break:keep-all;white-space:nowrap">`2023/02/09`</span> | | <span style="word-break:keep-all;white-space:nowrap">[Auto.js Pro 9](https://pro.autojs.org/)</span> | <span style="word-break:keep-all;white-space:nowrap">AutoJsPro</span> | <span style="word-break:keep-all;white-space:nowrap">[hyb1996](https://github.com/hyb1996)</span> | <span style="word-break:keep-all;white-space:nowrap">`2021/03/28`</span> | <span style="word-break:keep-all;white-space:nowrap">`2023/02/09`</span> |
| <span style="word-break:keep-all;white-space:nowrap">[AutoJs6](https://github.com/SuperMonster003/AutoJs6)</span> | <span style="word-break:keep-all;white-space:nowrap">[AutoJs6](https://github.com/SuperMonster003/AutoJs6/commit/a8ce1b9acb541e9736c33134be3194c3148a15a3#diff-833a46a97033e77558372a2dce103fd6fee29aaaa899f610022a7aece592ee7bR27)</span> | <span style="word-break:keep-all;white-space:nowrap">[SuperMonster003](https://github.com/SuperMonster003)</span> | <span style="word-break:keep-all;white-space:nowrap">`2021/12/01`</span> | <span style="word-break:keep-all;white-space:nowrap">-</span> | | <span style="word-break:keep-all;white-space:nowrap">[AutoJs6](https://github.com/SuperMonster003/AutoJs6)</span> | <span style="word-break:keep-all;white-space:nowrap">[AutoJs6](https://github.com/SuperMonster003/AutoJs6/commit/a8ce1b9acb541e9736c33134be3194c3148a15a3#diff-833a46a97033e77558372a2dce103fd6fee29aaaa899f610022a7aece592ee7bR27)</span> | <span style="word-break:keep-all;white-space:nowrap">[SuperMonster003](https://github.com/SuperMonster003)</span> | <span style="word-break:keep-all;white-space:nowrap">`2021/12/01`</span> | <span style="word-break:keep-all;white-space:nowrap">-</span> |
| <span style="word-break:keep-all;white-space:nowrap">[AutoX](https://github.com/kkevsekk1/AutoX)</span> | <span style="white-space:pre">[Autox.js&#160;v6](https://github.com/kkevsekk1/AutoX/commit/8b6776cff8b0fca4be4a52719b7d7d07c0a058f3#diff-51a0b488f963eb0be6c6599bf5df497313877cf5bdff3950807373912ac1cdc9R130)</span> | <span style="word-break:keep-all;white-space:nowrap">[wilinz](https://github.com/wilinz)</span> | <span style="word-break:keep-all;white-space:nowrap">`2022/05/26`</span> | <span style="word-break:keep-all;white-space:nowrap">-</span> | | <span style="word-break:keep-all;white-space:nowrap">[AutoX](https://github.com/kkevsekk1/AutoX)</span> | <span style="white-space:pre">[Autox.js&#160;v6](https://github.com/kkevsekk1/AutoX/commit/8b6776cff8b0fca4be4a52719b7d7d07c0a058f3#diff-51a0b488f963eb0be6c6599bf5df497313877cf5bdff3950807373912ac1cdc9R130)</span> | <span style="word-break:keep-all;white-space:nowrap">[wilinz](https://github.com/wilinz)</span> | <span style="word-break:keep-all;white-space:nowrap">`2022/05/26`</span> | <span style="word-break:keep-all;white-space:nowrap">-</span> |
| <span style="word-break:keep-all;white-space:nowrap">[AutoX](https://github.com/kkevsekk1/AutoX)</span> | <span style="white-space:pre">[Autox.js&#160;v7](https://github.com/kkevsekk1/AutoX/commit/484491fd5fe12b8203d0b09c181eb0f471c0ea9f#diff-8cff73265af19c059547b76aca8882cbaa3209291406f52df1dafbbc78e80c46R120)</span> | <span style="word-break:keep-all;white-space:nowrap">[aiselp](https://github.com/aiselp)</span> | <span style="word-break:keep-all;white-space:nowrap">`2024/04/21`</span> | <span style="word-break:keep-all;white-space:nowrap">-</span> | | <span style="word-break:keep-all;white-space:nowrap">[AutoX](https://github.com/aiselp/AutoX)</span> | <span style="white-space:pre">[Autox.js&#160;v7](https://github.com/aiselp/AutoX/commit/484491fd5fe12b8203d0b09c181eb0f471c0ea9f#diff-8cff73265af19c059547b76aca8882cbaa3209291406f52df1dafbbc78e80c46R120)</span> | <span style="word-break:keep-all;white-space:nowrap">[aiselp](https://github.com/aiselp)</span> | <span style="word-break:keep-all;white-space:nowrap">`2024/04/21`</span> | <span style="word-break:keep-all;white-space:nowrap">-</span> |
{{ p_introduction_related_autojs_table_date_is_estimated }}. {{ p_introduction_related_autojs_table_date_is_estimated }}.

View File

@@ -0,0 +1,14 @@
package org.autojs.autojs.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface DeserializedMethodName {
String method();
Class<?>[] parameterTypes() default {};
}

View File

@@ -16,8 +16,9 @@ import org.autojs.autojs.app.GlobalAppContext
import org.autojs.autojs.engine.encryption.AdvancedEncryptionStandard import org.autojs.autojs.engine.encryption.AdvancedEncryptionStandard
import org.autojs.autojs.pio.PFiles import org.autojs.autojs.pio.PFiles
import org.autojs.autojs.project.BuildInfo import org.autojs.autojs.project.BuildInfo
import org.autojs.autojs.project.LaunchConfig
import org.autojs.autojs.project.ProjectConfig import org.autojs.autojs.project.ProjectConfig
import org.autojs.autojs.project.ProjectConfig.DEFAULT_MAIN_SCRIPT_FILE_NAME import org.autojs.autojs.project.ProjectConfig.CONFIG_FILE_NAME
import org.autojs.autojs.script.EncryptedScriptFileHeader.writeHeader import org.autojs.autojs.script.EncryptedScriptFileHeader.writeHeader
import org.autojs.autojs.script.JavaScriptFileSource import org.autojs.autojs.script.JavaScriptFileSource
import org.autojs.autojs.util.FileUtils.TYPE.JAVASCRIPT import org.autojs.autojs.util.FileUtils.TYPE.JAVASCRIPT
@@ -39,7 +40,7 @@ import java.io.InputStream
* Created by Stardust on Oct 24, 2017. * Created by Stardust on Oct 24, 2017.
* Modified by SuperMonster003 as of Jul 8, 2022. * Modified by SuperMonster003 as of Jul 8, 2022.
*/ */
open class ApkBuilder(apkInputStream: InputStream?, private val outApkFile: File, private val workspacePath: String) { open class ApkBuilder(apkInputStream: InputStream?, private val outApkFile: File, private val buildPath: String) {
private var mProgressCallback: ProgressCallback? = null private var mProgressCallback: ProgressCallback? = null
private var mArscPackageName: String? = null private var mArscPackageName: String? = null
@@ -49,7 +50,7 @@ open class ApkBuilder(apkInputStream: InputStream?, private val outApkFile: File
private lateinit var mProjectConfig: ProjectConfig private lateinit var mProjectConfig: ProjectConfig
private val mApkPackager = ApkPackager(apkInputStream, workspacePath) private val mApkPackager = ApkPackager(apkInputStream, buildPath)
private val mAssetManager: AssetManager by lazy { globalContext.assets } private val mAssetManager: AssetManager by lazy { globalContext.assets }
@@ -57,14 +58,14 @@ open class ApkBuilder(apkInputStream: InputStream?, private val outApkFile: File
private var mAssetsFileIncludes = Libs.defaultAssetFilesToInclude.toMutableList() private var mAssetsFileIncludes = Libs.defaultAssetFilesToInclude.toMutableList()
private var mAssetsDirExcludes = Libs.defaultAssetDirsToExclude.toMutableList() private var mAssetsDirExcludes = Libs.defaultAssetDirsToExclude.toMutableList()
private var mSplashThemeId = 0 private var mSplashThemeId: Int = 0
private var mNoSplashThemeId = 0 private var mNoSplashThemeId: Int = 0
private val mManifestFile private val mManifestFile
get() = File(workspacePath, "AndroidManifest.xml") get() = File(buildPath, "AndroidManifest.xml")
private val mResourcesArscFile private val mResourcesArscFile
get() = File(workspacePath, "resources.arsc") get() = File(buildPath, "resources.arsc")
init { init {
PFiles.ensureDir(outApkFile.path) PFiles.ensureDir(outApkFile.path)
@@ -75,7 +76,7 @@ open class ApkBuilder(apkInputStream: InputStream?, private val outApkFile: File
@Throws(IOException::class) @Throws(IOException::class)
fun prepare() = also { fun prepare() = also {
mProgressCallback?.let { callback -> GlobalAppContext.post { callback.onPrepare(this) } } mProgressCallback?.let { callback -> GlobalAppContext.post { callback.onPrepare(this) } }
File(workspacePath).mkdirs() File(buildPath).mkdirs()
mApkPackager.unzip() mApkPackager.unzip()
} }
@@ -97,7 +98,7 @@ open class ApkBuilder(apkInputStream: InputStream?, private val outApkFile: File
@Throws(IOException::class) @Throws(IOException::class)
fun copyDir(srcFile: File, relativeDestPath: String) { fun copyDir(srcFile: File, relativeDestPath: String) {
val destDirFile = File(workspacePath, relativeDestPath).apply { mkdir() } val destDirFile = File(buildPath, relativeDestPath).apply { mkdir() }
srcFile.listFiles()?.forEach { srcChildFile -> srcFile.listFiles()?.forEach { srcChildFile ->
if (srcChildFile.isFile) { if (srcChildFile.isFile) {
if (srcChildFile.name.endsWith(JAVASCRIPT.extensionWithDot)) { if (srcChildFile.name.endsWith(JAVASCRIPT.extensionWithDot)) {
@@ -133,7 +134,7 @@ open class ApkBuilder(apkInputStream: InputStream?, private val outApkFile: File
@Throws(IOException::class) @Throws(IOException::class)
fun replaceFile(srcFile: File, relativeDestPath: String) = also { fun replaceFile(srcFile: File, relativeDestPath: String) = also {
val destFile = File(workspacePath, relativeDestPath) val destFile = File(buildPath, relativeDestPath)
if (destFile.name.endsWith(JAVASCRIPT.extensionWithDot)) { if (destFile.name.endsWith(JAVASCRIPT.extensionWithDot)) {
encrypt(srcFile, destFile) encrypt(srcFile, destFile)
} else { } else {
@@ -144,70 +145,99 @@ open class ApkBuilder(apkInputStream: InputStream?, private val outApkFile: File
@Throws(IOException::class) @Throws(IOException::class)
fun withConfig(config: ProjectConfig) = also { fun withConfig(config: ProjectConfig) = also {
config.also { mProjectConfig = it }.run { config.also { mProjectConfig = it }.run {
if (!launchConfig.isSplashVisible) { retrieveSplashThemeResources(launchConfig)
try { prepareManifestConfiguration(this)
val tableBlock = TableBlock.load(mResourcesArscFile)
val packageName = "${GlobalAppContext.get().packageName}.inrt"
val packageBlock = tableBlock.getOrCreatePackage(0x7f, packageName).also {
tableBlock.currentPackage = it
}
packageBlock.getEntry("", "style", "AppTheme.Splash")?.let {
mSplashThemeId = it.resourceId
}
packageBlock.getEntry("", "style", "AppTheme.SevereTransparent")?.let {
mNoSplashThemeId = it.resourceId
}
} catch (e: Exception) {
e.printStackTrace()
}
}
mManifestEditor = editManifest()
.setAppName(name)
.setVersionName(versionName)
.setVersionCode(versionCode)
.setPackageName(packageName)
setArscPackageName(packageName) setArscPackageName(packageName)
updateProjectConfig(this) updateProjectConfig(this)
copyAssetsRecursively("", File(workspacePath, "assets")) copyAssetsRecursively("", File(buildDir, "assets"))
copyLibrariesByConfig(this) copyLibrariesByConfig(this)
setScriptFile(sourcePath) setScriptFile(sourcePath)
} }
} }
private fun prepareManifestConfiguration(config: ProjectConfig) {
mManifestEditor = editManifest()
.setAppName(config.name)
.setVersionName(config.versionName)
.setVersionCode(config.versionCode)
.setPackageName(config.packageName)
}
private fun retrieveSplashThemeResources(launchConfig: LaunchConfig) {
if (launchConfig.isSplashVisible) {
// @Hint by SuperMonster003 on Jan 23, 2024.
// ! Members `mSplashThemeId` and `mNoSplashThemeId` will keep their default values.
// ! zh-CN: 成员变量 `mSplashThemeId` 及 `mNoSplashThemeId` 将保持其默认值.
return
}
try {
val tableBlock = TableBlock.load(mResourcesArscFile)
val packageName = "${GlobalAppContext.get().packageName}.inrt"
val packageBlock = tableBlock.getOrCreatePackage(0x7f, packageName).also {
tableBlock.currentPackage = it
}
packageBlock.getEntry("", "style", "AppTheme.Splash")?.let {
mSplashThemeId = it.resourceId
}
packageBlock.getEntry("", "style", "AppTheme.SevereTransparent")?.let {
mNoSplashThemeId = it.resourceId
}
} catch (e: Exception) {
e.printStackTrace()
}
}
@Throws(FileNotFoundException::class) @Throws(FileNotFoundException::class)
fun editManifest(): ManifestEditor = ManifestEditorWithAuthorities(FileInputStream(mManifestFile)).also { mManifestEditor = it } fun editManifest(): ManifestEditor = ManifestEditorWithAuthorities(FileInputStream(mManifestFile)).also { mManifestEditor = it }
private fun updateProjectConfig(config: ProjectConfig) { private fun updateProjectConfig(config: ProjectConfig) {
val projectConfig = when {
!PFiles.isDir(config.sourcePath) -> null // 这里为什么要有这样的一个方法? (
else -> ProjectConfig.fromProjectDir(config.sourcePath)?.also { // 会不会是因为有些配置需要写入到文件中, 这些配置包括自增的版本号, 用户的选择或键入值等等
val buildNumber = it.buildInfo.buildNumber // )
it.buildInfo = BuildInfo.generate(buildNumber + 1) // 参数 config 只是获取了一部分的字段用于设置新的 projectConfig, 为什么不直接使用全部的字段? (
PFiles.write(ProjectConfig.configFileOfDir(config.sourcePath), it.toJson()) // 因为有些不需要更新. 如果我们需要将所有设置开放到 Activity 页面中, 那么其实所有配置都是需要更新的
// )
// 像 abis, libs, signatureScheme 等信息就全部丢失了. (
// 所以需要添加到这个方法中
// )
val projectConfig = run {
if (PFiles.isDir(config.sourcePath)) {
// @Hint by SuperMonster003 on Jan 23, 2025.
// ! Project directory packaging.
// ! zh-CN: 打包项目目录.
ProjectConfig.fromProjectDir(config.sourcePath)?.let { sourceProjectConfig ->
sourceProjectConfig
.setBuildInfo(BuildInfo.generate(sourceProjectConfig.buildInfo.buildNumber + 1))
File(ProjectConfig.configFileOfDir(config.sourcePath)).writeText(sourceProjectConfig.toJson())
return@run sourceProjectConfig
}
} }
} ?: ProjectConfig() // @Hint by SuperMonster003 on Jan 23, 2025.
.setMainScriptFileName(DEFAULT_MAIN_SCRIPT_FILE_NAME) // ! Single file packaging.
.setName(config.name) // ! zh-CN: 打包单独文件.
.setPackageName(config.packageName) return@run ProjectConfig().also { newProjectConfig ->
.setVersionName(config.versionName) newProjectConfig
.setVersionCode(config.versionCode) .setName(config.name)
.also { newProjectConfig -> .setPackageName(config.packageName)
newProjectConfig.buildInfo = BuildInfo.generate(newProjectConfig.versionCode.toLong()) .setVersionName(config.versionName)
File(workspacePath, "assets/project/${ProjectConfig.CONFIG_FILE_NAME}").also { file -> .setVersionCode(config.versionCode)
.setBuildInfo(BuildInfo.generate(newProjectConfig.versionCode.toLong()))
File(buildPath, "assets/project/$CONFIG_FILE_NAME").also { file ->
file.parentFile?.let { parent -> if (!parent.exists()) parent.mkdirs() } file.parentFile?.let { parent -> if (!parent.exists()) parent.mkdirs() }
}.writeText(newProjectConfig.toJson()) }.writeText(newProjectConfig.toJson())
} }
projectConfig.run { }
mKey = MD5Utils.md5(packageName + versionName + mainScriptFileName)
mInitVector = MD5Utils.md5(buildInfo.buildId + name).substring(0, 16) mKey = MD5Utils.md5(projectConfig.run { packageName + versionName + mainScriptFileName })
Libs.entries.forEach { entry -> mInitVector = MD5Utils.md5(projectConfig.run { buildInfo.buildId + name }).take(16)
if (config.libs.contains(entry.label)) { Libs.entries.forEach { entry ->
mLibsIncludes += entry.libsToInclude.toSet() if (config.libs.contains(entry.label)) {
mAssetsFileIncludes += entry.assetFilesToInclude.toSet() mLibsIncludes += entry.libsToInclude.toSet()
mAssetsDirExcludes -= entry.assetDirsToExclude.toSet() mAssetsFileIncludes += entry.assetFilesToInclude.toSet()
} mAssetsDirExcludes -= entry.assetDirsToExclude.toSet()
} }
} }
} }
@@ -225,7 +255,7 @@ open class ApkBuilder(apkInputStream: InputStream?, private val outApkFile: File
val appIcon = packageBlock.getOrCreate("", ICON_RES_DIR, ICON_NAME) val appIcon = packageBlock.getOrCreate("", ICON_RES_DIR, ICON_NAME)
val appIconPath = appIcon.resValue.decodeValue() val appIconPath = appIcon.resValue.decodeValue()
Log.d(TAG, "Icon path: $appIconPath") Log.d(TAG, "Icon path: $appIconPath")
val file = File(workspacePath, appIconPath).also { val file = File(buildPath, appIconPath).also {
if (!it.exists()) { if (!it.exists()) {
File(it.parent!!).mkdirs() File(it.parent!!).mkdirs()
it.createNewFile() it.createNewFile()
@@ -273,19 +303,20 @@ open class ApkBuilder(apkInputStream: InputStream?, private val outApkFile: File
fun sign() = also { fun sign() = also {
mProgressCallback?.let { callback -> GlobalAppContext.post { callback.onSign(this) } } mProgressCallback?.let { callback -> GlobalAppContext.post { callback.onSign(this) } }
val fos = FileOutputStream(outApkFile) val fos = FileOutputStream(outApkFile)
TinySign.sign(File(workspacePath), fos) TinySign.sign(File(buildPath), fos)
fos.close() fos.close()
val defaultKeyStoreFile = File(workspacePath, "default_key_store.bks") val defaultKeyStoreFile = File(buildPath, "default_key_store.bks")
val tmpOutputApk = File(workspacePath, "temp.apk") val tmpOutputApk = File(buildPath, "temp.apk")
copyInputStreamToFile(GlobalAppContext.get().assets.open("default_key_store.bks"), defaultKeyStoreFile) copyInputStreamToFile(GlobalAppContext.get().assets.open("default_key_store.bks"), defaultKeyStoreFile)
val signer = ApkSigner(outApkFile, tmpOutputApk) val signer = ApkSigner(outApkFile, tmpOutputApk).apply {
signer.useDefaultSignatureVersion = false useDefaultSignatureVersion = false
signer.v1SigningEnabled = mProjectConfig.signatureScheme.contains("V1") v1SigningEnabled = "V1" in mProjectConfig.signatureScheme
signer.v2SigningEnabled = mProjectConfig.signatureScheme.contains("V2") v2SigningEnabled = "V2" in mProjectConfig.signatureScheme
signer.v3SigningEnabled = mProjectConfig.signatureScheme.contains("V3") v3SigningEnabled = "V3" in mProjectConfig.signatureScheme
signer.v4SigningEnabled = mProjectConfig.signatureScheme.contains("V4") v4SigningEnabled = "V4" in mProjectConfig.signatureScheme
}
var keyStoreFile = defaultKeyStoreFile var keyStoreFile = defaultKeyStoreFile
var password = "AutoJs6" var password = "AutoJs6"
@@ -313,7 +344,7 @@ open class ApkBuilder(apkInputStream: InputStream?, private val outApkFile: File
fun cleanWorkspace() = also { fun cleanWorkspace() = also {
mProgressCallback?.let { callback -> GlobalAppContext.post { callback.onClean(this) } } mProgressCallback?.let { callback -> GlobalAppContext.post { callback.onClean(this) } }
delete(File(workspacePath)) delete(File(buildPath))
} }
@Throws(IOException::class) @Throws(IOException::class)
@@ -321,8 +352,8 @@ open class ApkBuilder(apkInputStream: InputStream?, private val outApkFile: File
@Throws(IOException::class) @Throws(IOException::class)
private fun buildArsc() { private fun buildArsc() {
val oldArsc = File(workspacePath, "resources.arsc") val oldArsc = File(buildPath, "resources.arsc")
val newArsc = File(workspacePath, "resources.arsc.new") val newArsc = File(buildPath, "resources.arsc.new")
val decoder = ARSCDecoder(BufferedInputStream(FileInputStream(oldArsc)), null, false) val decoder = ARSCDecoder(BufferedInputStream(FileInputStream(oldArsc)), null, false)
decoder.CloneArsc(FileOutputStream(newArsc), mArscPackageName, true) decoder.CloneArsc(FileOutputStream(newArsc), mArscPackageName, true)
oldArsc.delete() oldArsc.delete()
@@ -345,9 +376,13 @@ open class ApkBuilder(apkInputStream: InputStream?, private val outApkFile: File
private inner class ManifestEditorWithAuthorities(manifestInputStream: InputStream?) : ManifestEditor(manifestInputStream) { private inner class ManifestEditorWithAuthorities(manifestInputStream: InputStream?) : ManifestEditor(manifestInputStream) {
override fun onAttr(attr: AxmlWriter.Attr) { override fun onAttr(attr: AxmlWriter.Attr) {
attr.apply { attr.apply {
// @Reference to aiselp (https://github.com/aiselp) by SuperMonster003 on Jan 18, 2025.
// ! https://github.com/aiselp/AutoX/blob/5b3303926082d591a166b1845702357406811aaf/app/src/main/java/org/autojs/autojs/build/ApkBuilder.kt#L175-L188
if (!mProjectConfig.launchConfig.isSplashVisible && mSplashThemeId != 0 && value == mSplashThemeId) { if (!mProjectConfig.launchConfig.isSplashVisible && mSplashThemeId != 0 && value == mSplashThemeId) {
value = mNoSplashThemeId value = mNoSplashThemeId
} }
if (name.data == "authorities" && value is StringItem) { if (name.data == "authorities" && value is StringItem) {
(value as StringItem).data = "${mProjectConfig.packageName}.fileprovider" (value as StringItem).data = "${mProjectConfig.packageName}.fileprovider"
} else { } else {
@@ -388,7 +423,7 @@ open class ApkBuilder(apkInputStream: InputStream?, private val outApkFile: File
mLibsIncludes.distinct().forEach { libName -> mLibsIncludes.distinct().forEach { libName ->
runCatching { runCatching {
File(srcLibDir, "$abiSrcName/$libName").takeIf { it.exists() }?.copyTo( File(srcLibDir, "$abiSrcName/$libName").takeIf { it.exists() }?.copyTo(
File(workspacePath, "lib/$abiDestName/$libName"), File(buildPath, "lib/$abiDestName/$libName"),
overwrite = true overwrite = true
) )
}.onFailure { it.printStackTrace() } }.onFailure { it.printStackTrace() }

View File

@@ -9,6 +9,7 @@ import androidx.annotation.Nullable;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.GsonBuilder; import com.google.gson.GsonBuilder;
import com.google.gson.annotations.SerializedName; import com.google.gson.annotations.SerializedName;
import org.autojs.autojs.annotation.DeserializedMethodName;
import org.autojs.autojs.annotation.SerializedNameCompatible; import org.autojs.autojs.annotation.SerializedNameCompatible;
import org.autojs.autojs.annotation.SerializedNameCompatible.With; import org.autojs.autojs.annotation.SerializedNameCompatible.With;
import org.autojs.autojs.apkbuilder.keystore.KeyStore; import org.autojs.autojs.apkbuilder.keystore.KeyStore;
@@ -22,14 +23,13 @@ import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Set;
import java.util.Objects;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.stream.Collectors;
/** /**
* Created by Stardust on Jan 24, 2018. * Created by Stardust on Jan 24, 2018.
@@ -49,18 +49,24 @@ public class ProjectConfig {
.create(); .create();
@SerializedName("name") @SerializedName("name")
@SerializedNameCompatible(with = {@With(value = "projectName")}) @SerializedNameCompatible(with = {
@With(value = "projectName"),
})
private String mName; private String mName;
@SerializedName("versionName") @SerializedName("versionName")
@SerializedNameCompatible(with = {@With(value = "version")}) @SerializedNameCompatible(with = {
@With(value = "version"),
})
private String mVersionName; private String mVersionName;
@SerializedName("versionCode") @SerializedName("versionCode")
private int mVersionCode = -1; private int mVersionCode = 1;
@SerializedName("packageName") @SerializedName("packageName")
@SerializedNameCompatible(with = {@With(value = "package")}) @SerializedNameCompatible(with = {
@With(value = "package"),
})
private String mPackageName; private String mPackageName;
@SerializedName("main") @SerializedName("main")
@@ -73,7 +79,7 @@ public class ProjectConfig {
@With(value = "mainFile"), @With(value = "mainFile"),
@With(value = "mainFileName"), @With(value = "mainFileName"),
}) })
private String mMainScriptFileName; private String mMainScriptFileName = DEFAULT_MAIN_SCRIPT_FILE_NAME;
@Nullable @Nullable
@SerializedName(value = "assets") @SerializedName(value = "assets")
@@ -84,15 +90,21 @@ public class ProjectConfig {
private List<String> mAssets = new ArrayList<>(); private List<String> mAssets = new ArrayList<>();
@SerializedName("launchConfig") @SerializedName("launchConfig")
@SerializedNameCompatible(with = {@With(value = "launch")}) @SerializedNameCompatible(with = {
@With(value = "launch"),
})
private LaunchConfig mLaunchConfig = new LaunchConfig(); private LaunchConfig mLaunchConfig = new LaunchConfig();
@SerializedName("build") @SerializedName("build")
@SerializedNameCompatible(with = {@With(value = "buildInfo")}) @SerializedNameCompatible(with = {
@With(value = "buildInfo"),
})
private BuildInfo mBuildInfo = new BuildInfo(); private BuildInfo mBuildInfo = new BuildInfo();
@SerializedName("icon") @SerializedName("icon")
@SerializedNameCompatible(with = {@With(value = "iconPath")}) @SerializedNameCompatible(with = {
@With(value = "iconPath"),
})
private String mIconPath; private String mIconPath;
private transient Callable<Bitmap> mIconBitmapGetter; private transient Callable<Bitmap> mIconBitmapGetter;
@@ -125,19 +137,23 @@ public class ProjectConfig {
@With(value = "signatureSchemes"), @With(value = "signatureSchemes"),
@With(value = "signature"), @With(value = "signature"),
}) })
@DeserializedMethodName(method = "normalizeSignatureScheme", parameterTypes = {String.class})
private String mSignatureScheme = "V1 + V2"; private String mSignatureScheme = "V1 + V2";
@Nullable @Nullable
private transient KeyStore mKeyStore = null; private transient KeyStore mKeyStore = null;
@SerializedName("scriptConfigs") // @Commented by SuperMonster003 on Jan 20, 2025.
@SerializedNameCompatible(with = { // ! Unused config options: "scripts".
@With(value = "scriptsConfigs"), // ! zh-CN: 未使用的配置选项: "scripts".
@With(value = "scriptsConfig"), // # @SerializedName("scriptConfigs")
@With(value = "scriptConfig"), // # @SerializedNameCompatible(with = {
@With(value = "scripts", target = {"AutoJs4", "AutoX"}), // # @With(value = "scriptsConfigs"),
}) // # @With(value = "scriptsConfig"),
private final Map<String, ScriptConfig> mScriptConfigs = new HashMap<>(); // # @With(value = "scriptConfig"),
// # @With(value = "scripts", target = {"AutoJs4", "AutoX"}),
// # })
// # private final Map<String, ScriptConfig> mScriptConfigs = new HashMap<>();
@SerializedName(value = "useFeatures") @SerializedName(value = "useFeatures")
@SerializedNameCompatible(with = { @SerializedNameCompatible(with = {
@@ -362,8 +378,9 @@ public class ProjectConfig {
return mBuildInfo; return mBuildInfo;
} }
public void setBuildInfo(BuildInfo buildInfo) { public ProjectConfig setBuildInfo(BuildInfo buildInfo) {
mBuildInfo = buildInfo; mBuildInfo = buildInfo;
return this;
} }
public String getName() { public String getName() {
@@ -412,7 +429,7 @@ public class ProjectConfig {
@NonNull @NonNull
public String getMainScriptFileName() { public String getMainScriptFileName() {
return mMainScriptFileName != null ? mMainScriptFileName : DEFAULT_MAIN_SCRIPT_FILE_NAME; return mMainScriptFileName;
} }
public ProjectConfig setMainScriptFileName(String mainScriptFileName) { public ProjectConfig setMainScriptFileName(String mainScriptFileName) {
@@ -420,9 +437,12 @@ public class ProjectConfig {
return this; return this;
} }
public Map<String, ScriptConfig> getScriptConfigs() { // @Commented by SuperMonster003 on Jan 20, 2025.
return mScriptConfigs; // ! Unused config options: "scripts".
} // ! zh-CN: 未使用的配置选项: "scripts".
// # public Map<String, ScriptConfig> getScriptConfigs() {
// # return mScriptConfigs;
// # }
public List<String> getAssets() { public List<String> getAssets() {
if (mAssets == null) { if (mAssets == null) {
@@ -516,22 +536,20 @@ public class ProjectConfig {
mFeatures = features; mFeatures = features;
} }
public ScriptConfig getScriptConfig(String path) { // @Commented by SuperMonster003 on Jan 20, 2025.
ScriptConfig scriptConfig = Objects.requireNonNull(mScriptConfigs.getOrDefault(path, new ScriptConfig())); // ! Unused config options: "scripts".
List<String> combinedFeatures = getCombinedFeatures(scriptConfig); // ! zh-CN: 未使用的配置选项: "scripts".
scriptConfig.setFeatures(combinedFeatures); // # public ScriptConfig getScriptConfig(String scriptKeyName) {
return scriptConfig; // # ScriptConfig scriptConfig = Objects.requireNonNull(mScriptConfigs.getOrDefault(scriptKeyName, new ScriptConfig()));
} // # List<String> combinedFeatures = new ArrayList<>(
// # new HashSet<>() {{
@NotNull // # addAll(scriptConfig.getFeatures());
public ArrayList<String> getCombinedFeatures(ScriptConfig scriptConfig) { // # addAll(mFeatures);
return new ArrayList<>( // # }}
new HashSet<>() {{ // # );
addAll(scriptConfig.getFeatures()); // # scriptConfig.setFeatures(combinedFeatures);
addAll(mFeatures); // # return scriptConfig;
}} // # }
);
}
public List<File> getExcludedDirs() { public List<File> getExcludedDirs() {
return mExcludedDirs; return mExcludedDirs;
@@ -566,10 +584,24 @@ public class ProjectConfig {
} }
public ProjectConfig setSignatureScheme(String signatureScheme) { public ProjectConfig setSignatureScheme(String signatureScheme) {
mSignatureScheme = signatureScheme; mSignatureScheme = normalizeSignatureScheme(signatureScheme);
return this; return this;
} }
public static String normalizeSignatureScheme(String input) {
Pattern pattern = Pattern.compile("v\\d+", Pattern.CASE_INSENSITIVE);
Matcher matcher = pattern.matcher(input);
Set<String> matches = new HashSet<>();
while (matcher.find()) {
matches.add(matcher.group().toUpperCase());
}
if (matches.isEmpty()) {
return input.trim();
}
return matches.stream().sorted().collect(Collectors.joining(" + "));
}
@Nullable @Nullable
public KeyStore getKeyStore() { public KeyStore getKeyStore() {
return mKeyStore; return mKeyStore;

View File

@@ -21,6 +21,7 @@ import android.widget.Spinner;
import android.widget.TextView; import android.widget.TextView;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.core.util.Pair;
import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelProvider;
import com.afollestad.materialdialogs.MaterialDialog; import com.afollestad.materialdialogs.MaterialDialog;
import com.google.android.flexbox.FlexboxLayout; import com.google.android.flexbox.FlexboxLayout;
@@ -70,6 +71,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Function; import java.util.function.Function;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.IntStream;
import static org.autojs.autojs.apkbuilder.ApkBuilder.TEMPLATE_APK_NAME; import static org.autojs.autojs.apkbuilder.ApkBuilder.TEMPLATE_APK_NAME;
import static org.autojs.autojs.util.StringUtils.key; import static org.autojs.autojs.util.StringUtils.key;
@@ -77,6 +79,8 @@ import static org.autojs.autojs.util.StringUtils.key;
/** /**
* Created by Stardust on Oct 22, 2017. * Created by Stardust on Oct 22, 2017.
* Modified by SuperMonster003 as of Dec 1, 2023. * Modified by SuperMonster003 as of Dec 1, 2023.
*
* @noinspection ResultOfMethodCallIgnored
*/ */
public class BuildActivity extends BaseActivity implements ApkBuilder.ProgressCallback { public class BuildActivity extends BaseActivity implements ApkBuilder.ProgressCallback {
@@ -105,14 +109,14 @@ public class BuildActivity extends BaseActivity implements ApkBuilder.ProgressCa
private static final Map<String, List<String>> LIB_ALIASES = new HashMap<>(); private static final Map<String, List<String>> LIB_ALIASES = new HashMap<>();
private static final ArrayList<String> SIGNATURE_SCHEMES = new ArrayList<>() {{ private static final List<Pair<String, String>> SIGNATURE_SCHEMES = new ArrayList<>() {{
add("V1 + V2"); add(new Pair<>("V1 + V2", null));
add("V1 + V3"); add(new Pair<>("V1 + V3", null));
add("V1 + V2 + V3"); add(new Pair<>("V1 + V2 + V3", null));
add("V1"); add(new Pair<>("V1", null));
add("V2 + V3 (Android 7.0+)"); add(new Pair<>("V2 + V3", "Android 7.0+"));
add("V2 (Android 7.0+)"); add(new Pair<>("V2", "Android 7.0+"));
add("V3 (Android 9.0+)"); add(new Pair<>("V3", "Android 9.0+"));
}}; }};
private final Map<String, Integer> SUPPORTED_PERMISSIONS = new TreeMap<>() {{ private final Map<String, Integer> SUPPORTED_PERMISSIONS = new TreeMap<>() {{
@@ -173,16 +177,19 @@ public class BuildActivity extends BaseActivity implements ApkBuilder.ProgressCa
ImageView mIconView; ImageView mIconView;
LinearLayout mAppConfigView; LinearLayout mAppConfigView;
@Nullable
private ProjectConfig mProjectConfig; private ProjectConfig mProjectConfig;
private MaterialDialog mProgressDialog; private MaterialDialog mProgressDialog;
private String mSource; private String mSource;
private boolean mIsDefaultIcon = true; private boolean mIsDefaultIcon = true;
private boolean mIsProjectLevelBuilding; private boolean mIsProjectLevelBuilding;
private FlexboxLayout mFlexboxAbis;
private FlexboxLayout mFlexboxLibs; private FlexboxLayout mFlexboxAbisView;
private Spinner mSignatureSchemes; private FlexboxLayout mFlexboxLibsView;
private Spinner mVerifiedKeyStores; private Spinner mSignatureSchemesView;
private FlexboxLayout mFlexboxPermissions; private Spinner mVerifiedKeyStoresView;
private FlexboxLayout mFlexboxPermissionsView;
private final ArrayList<String> mInvalidAbis = new ArrayList<>(); private final ArrayList<String> mInvalidAbis = new ArrayList<>();
private final ArrayList<String> mUnavailableAbis = new ArrayList<>(); private final ArrayList<String> mUnavailableAbis = new ArrayList<>();
@@ -261,35 +268,30 @@ public class BuildActivity extends BaseActivity implements ApkBuilder.ProgressCa
mAppConfigView = binding.appConfig; mAppConfigView = binding.appConfig;
mFlexboxAbis = binding.flexboxAbis; mFlexboxAbisView = binding.flexboxAbis;
initAbisChildren(); initAbisChildren();
mFlexboxLibs = binding.flexboxLibraries; mFlexboxLibsView = binding.flexboxLibraries;
initLibsChildren(); initLibsChildren();
mKeyStoreViewModel = new ViewModelProvider(this, new KeyStoreViewModel.Factory(getApplicationContext())).get(KeyStoreViewModel.class); mKeyStoreViewModel = new ViewModelProvider(this, new KeyStoreViewModel.Factory(getApplicationContext())).get(KeyStoreViewModel.class);
mKeyStoreViewModel.updateVerifiedKeyStores(); mKeyStoreViewModel.updateVerifiedKeyStores();
mSignatureSchemes = binding.spinnerSignatureSchemes; mSignatureSchemesView = binding.spinnerSignatureSchemes;
initSignatureSchemeSpinner(); mVerifiedKeyStoresView = binding.spinnerVerifiedKeyStores;
mFlexboxPermissionsView = binding.flexboxPermissions;
mVerifiedKeyStores = binding.spinnerVerifiedKeyStores;
initVerifiedKeyStoresSpinner();
mFlexboxPermissions = binding.flexboxPermissions;
initPermissionsChildren();
binding.fab.setOnClickListener(v -> buildApk()); binding.fab.setOnClickListener(v -> buildApk());
binding.selectSource.setOnClickListener(v -> selectSourceFilePath()); binding.selectSource.setOnClickListener(v -> selectSourceFilePath());
binding.selectOutput.setOnClickListener(v -> selectOutputDirPath()); binding.selectOutput.setOnClickListener(v -> selectOutputDirPath());
binding.textAbis.setOnClickListener(v -> toggleAllFlexboxChildren(mFlexboxAbis)); binding.textAbis.setOnClickListener(v -> toggleAllFlexboxChildren(mFlexboxAbisView));
binding.textAbis.setOnLongClickListener(v -> { binding.textAbis.setOnLongClickListener(v -> {
syncAbisCheckedStates(); syncAbisCheckedStates();
return true; return true;
}); });
binding.textLibs.setOnClickListener(v -> toggleAllFlexboxChildren(mFlexboxLibs)); binding.textLibs.setOnClickListener(v -> toggleAllFlexboxChildren(mFlexboxLibsView));
binding.manageKeyStore.setOnClickListener(v -> ManageKeyStoreActivity.Companion.startActivity(this)); binding.manageKeyStore.setOnClickListener(v -> ManageKeyStoreActivity.Companion.startActivity(this));
binding.textPermissions.setOnClickListener(v -> toggleAllFlexboxChildren(mFlexboxPermissions)); binding.textPermissions.setOnClickListener(v -> toggleAllFlexboxChildren(mFlexboxPermissionsView));
setToolbarAsBack(R.string.text_build_apk); setToolbarAsBack(R.string.text_build_apk);
mSource = getIntent().getStringExtra(EXTRA_SOURCE); mSource = getIntent().getStringExtra(EXTRA_SOURCE);
@@ -297,6 +299,10 @@ public class BuildActivity extends BaseActivity implements ApkBuilder.ProgressCa
setupWithSourceFile(new ScriptFile(mSource)); setupWithSourceFile(new ScriptFile(mSource));
} }
initSignatureSchemeSpinner();
initVerifiedKeyStoresSpinner();
initPermissionsChildren();
syncAbisCheckedStates(); syncAbisCheckedStates();
syncLibsCheckedStates(); syncLibsCheckedStates();
@@ -354,7 +360,7 @@ public class BuildActivity extends BaseActivity implements ApkBuilder.ProgressCa
child.setChecked(false); child.setChecked(false);
child.setEnabled(false); child.setEnabled(false);
child.setOnBeingUnavailableListener(this::promptForUnavailability); child.setOnBeingUnavailableListener(this::promptForUnavailability);
mFlexboxAbis.addView(child); mFlexboxAbisView.addView(child);
}); });
} }
@@ -397,8 +403,8 @@ public class BuildActivity extends BaseActivity implements ApkBuilder.ProgressCa
private void syncAbisWithDefaultCheckedFilter(Function<String, Boolean> filterForDefaultChecked) { private void syncAbisWithDefaultCheckedFilter(Function<String, Boolean> filterForDefaultChecked) {
List<String> appSupportedAbiList = AndroidUtils.getAppSupportedAbiList(); List<String> appSupportedAbiList = AndroidUtils.getAppSupportedAbiList();
for (int i = 0; i < mFlexboxAbis.getChildCount(); i += 1) { for (int i = 0; i < mFlexboxAbisView.getChildCount(); i += 1) {
View child = mFlexboxAbis.getChildAt(i); View child = mFlexboxAbisView.getChildAt(i);
if (child instanceof RoundCheckboxWithText) { if (child instanceof RoundCheckboxWithText) {
CharSequence standardAbi = ((RoundCheckboxWithText) child).getText(); CharSequence standardAbi = ((RoundCheckboxWithText) child).getText();
if (standardAbi != null) { if (standardAbi != null) {
@@ -419,7 +425,7 @@ public class BuildActivity extends BaseActivity implements ApkBuilder.ProgressCa
RoundCheckboxWithText child = new RoundCheckboxWithText(this, null); RoundCheckboxWithText child = new RoundCheckboxWithText(this, null);
child.setText(text); child.setText(text);
child.setChecked(false); child.setChecked(false);
mFlexboxLibs.addView(child); mFlexboxLibsView.addView(child);
}); });
} }
@@ -432,8 +438,8 @@ public class BuildActivity extends BaseActivity implements ApkBuilder.ProgressCa
// 创建一个新的副本 // 创建一个新的副本
var candidates = new ArrayList<>(configLibs); var candidates = new ArrayList<>(configLibs);
for (int i = 0; i < mFlexboxLibs.getChildCount(); i += 1) { for (int i = 0; i < mFlexboxLibsView.getChildCount(); i += 1) {
View child = mFlexboxLibs.getChildAt(i); View child = mFlexboxLibsView.getChildAt(i);
if (child instanceof RoundCheckboxWithText) { if (child instanceof RoundCheckboxWithText) {
CharSequence standardLib = ((RoundCheckboxWithText) child).getText(); CharSequence standardLib = ((RoundCheckboxWithText) child).getText();
if (standardLib != null) { if (standardLib != null) {
@@ -447,9 +453,23 @@ public class BuildActivity extends BaseActivity implements ApkBuilder.ProgressCa
} }
private void initSignatureSchemeSpinner() { private void initSignatureSchemeSpinner() {
ArrayAdapter<String> adapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_item, SIGNATURE_SCHEMES); ArrayAdapter<String> adapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_item, SIGNATURE_SCHEMES.stream().map(pair -> {
if (pair.second == null || pair.second.isEmpty()) {
return pair.first;
}
return pair.first + " (" + pair.second + ")";
}).collect(Collectors.toList()));
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
mSignatureSchemes.setAdapter(adapter); mSignatureSchemesView.setAdapter(adapter);
if (mProjectConfig != null) {
int initialSelection = IntStream.range(0, SIGNATURE_SCHEMES.size())
.filter(i -> SIGNATURE_SCHEMES.get(i).first.equalsIgnoreCase(mProjectConfig.getSignatureScheme()))
.findFirst()
.orElse(-1);
if (initialSelection >= 0) {
mSignatureSchemesView.setSelection(initialSelection);
}
}
} }
private void initVerifiedKeyStoresSpinner() { private void initVerifiedKeyStoresSpinner() {
@@ -460,7 +480,7 @@ public class BuildActivity extends BaseActivity implements ApkBuilder.ProgressCa
ArrayAdapter<KeyStore> adapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_item, verifiedKeyStores); ArrayAdapter<KeyStore> adapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_item, verifiedKeyStores);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
mVerifiedKeyStores.setAdapter(adapter); mVerifiedKeyStoresView.setAdapter(adapter);
mKeyStoreViewModel.getVerifiedKeyStores().observe(this, keyStores -> { mKeyStoreViewModel.getVerifiedKeyStores().observe(this, keyStores -> {
// 清空现有的选项,但保留第一个元素,即默认密钥库 // 清空现有的选项,但保留第一个元素,即默认密钥库
@@ -487,7 +507,7 @@ public class BuildActivity extends BaseActivity implements ApkBuilder.ProgressCa
int marginInPixels = (int) (8 * getResources().getDisplayMetrics().density); int marginInPixels = (int) (8 * getResources().getDisplayMetrics().density);
checkBox.setPadding(marginInPixels, 0, 0, 0); checkBox.setPadding(marginInPixels, 0, 0, 0);
checkBox.setChecked(false); checkBox.setChecked(false);
mFlexboxPermissions.addView(checkBox); mFlexboxPermissionsView.addView(checkBox);
}); });
} }
@@ -644,8 +664,8 @@ public class BuildActivity extends BaseActivity implements ApkBuilder.ProgressCa
} }
private boolean checkAbis() { private boolean checkAbis() {
for (int i = 0; i < mFlexboxAbis.getChildCount(); i += 1) { for (int i = 0; i < mFlexboxAbisView.getChildCount(); i += 1) {
View child = mFlexboxAbis.getChildAt(i); View child = mFlexboxAbisView.getChildAt(i);
if (child instanceof RoundCheckboxWithText) { if (child instanceof RoundCheckboxWithText) {
if (((RoundCheckboxWithText) child).isChecked() && child.isEnabled()) { if (((RoundCheckboxWithText) child).isChecked() && child.isEnabled()) {
return true; return true;
@@ -774,11 +794,11 @@ public class BuildActivity extends BaseActivity implements ApkBuilder.ProgressCa
@SuppressLint("CheckResult") @SuppressLint("CheckResult")
private void doBuildingApk() { private void doBuildingApk() {
ProjectConfig projectConfig = determineProjectConfig(); ProjectConfig projectConfig = determineProjectConfig();
File tmpDir = new File(getCacheDir(), "build/"); File buildPath = new File(getCacheDir(), "build/");
File outApk = new File(mOutputPathView.getText().toString(), File outApk = new File(mOutputPathView.getText().toString(),
String.format("%s_v%s.apk", projectConfig.getName(), projectConfig.getVersionName())); String.format("%s_v%s.apk", projectConfig.getName(), projectConfig.getVersionName()));
showProgressDialog(); showProgressDialog();
Observable.fromCallable(() -> callApkBuilder(tmpDir, outApk, projectConfig)) Observable.fromCallable(() -> callApkBuilder(buildPath, outApk, projectConfig))
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe(apkBuilder -> { .subscribe(apkBuilder -> {
@@ -791,9 +811,9 @@ public class BuildActivity extends BaseActivity implements ApkBuilder.ProgressCa
} }
private ProjectConfig determineProjectConfig() { private ProjectConfig determineProjectConfig() {
ArrayList<String> abis = collectCheckedItems(mFlexboxAbis); ArrayList<String> abis = collectCheckedItems(mFlexboxAbisView);
ArrayList<String> libs = collectCheckedItems(mFlexboxLibs); ArrayList<String> libs = collectCheckedItems(mFlexboxLibsView);
ArrayList<String> permissions = collectCheckedItems(mFlexboxPermissions); ArrayList<String> permissions = collectCheckedItems(mFlexboxPermissionsView);
ProjectConfig projectConfig; ProjectConfig projectConfig;
if (mProjectConfig != null) { if (mProjectConfig != null) {
@@ -814,8 +834,8 @@ public class BuildActivity extends BaseActivity implements ApkBuilder.ProgressCa
return projectConfig return projectConfig
.setAbis(abis) .setAbis(abis)
.setLibs(libs) .setLibs(libs)
.setKeyStore(mVerifiedKeyStores.getSelectedItemPosition() > 0 ? (KeyStore) mVerifiedKeyStores.getSelectedItem() : null) .setKeyStore(mVerifiedKeyStoresView.getSelectedItemPosition() > 0 ? (KeyStore) mVerifiedKeyStoresView.getSelectedItem() : null)
.setSignatureScheme(mSignatureSchemes.getSelectedItem().toString()) .setSignatureScheme(mSignatureSchemesView.getSelectedItem().toString())
.setPermissions(permissions); .setPermissions(permissions);
} }
@@ -844,9 +864,9 @@ public class BuildActivity extends BaseActivity implements ApkBuilder.ProgressCa
return libs; return libs;
} }
private ApkBuilder callApkBuilder(File tmpDir, File outApk, ProjectConfig projectConfig) throws Exception { private ApkBuilder callApkBuilder(File buildPath, File outApk, ProjectConfig projectConfig) throws Exception {
InputStream templateApk = getAssets().open(TEMPLATE_APK_NAME); InputStream templateApk = getAssets().open(TEMPLATE_APK_NAME);
return new ApkBuilder(templateApk, outApk, tmpDir.getPath()) return new ApkBuilder(templateApk, outApk, buildPath.getPath())
.setProgressCallback(BuildActivity.this) .setProgressCallback(BuildActivity.this)
.prepare() .prepare()
.withConfig(projectConfig) .withConfig(projectConfig)

View File

@@ -5,10 +5,11 @@ import com.google.gson.JsonDeserializer
import com.google.gson.JsonElement import com.google.gson.JsonElement
import com.google.gson.JsonObject import com.google.gson.JsonObject
import com.google.gson.annotations.SerializedName import com.google.gson.annotations.SerializedName
import org.autojs.autojs.annotation.DeserializedMethodName
import org.autojs.autojs.annotation.SerializedNameCompatible import org.autojs.autojs.annotation.SerializedNameCompatible
import org.json.JSONArray import org.json.JSONTokener
import org.json.JSONObject
import java.lang.reflect.Field import java.lang.reflect.Field
import java.lang.reflect.Modifier
import java.lang.reflect.Type import java.lang.reflect.Type
/** /**
@@ -84,7 +85,34 @@ object JsonUtils {
return json return json
} }
fun isValidJson(json: String): Boolean = runCatching { JSONObject(json) }.isSuccess || runCatching { JSONArray(json) }.isSuccess /**
* This method checks whether the given string is a valid JSON format.
*
* This method supports the following common JSON formats:
* - Objects (e.g., `{"key": "value"}`)
* - Arrays (e.g., `[1, 2, 3]`)
* - Single values (e.g., `"string"`, `123`, `true`, `null`)
*
* Note:
* 1. Empty strings or strings containing only whitespace are considered invalid JSON.
* 2. If the JSON contains comments (e.g., `// comment` or `/* multi-line comment */`),
* this method will consider it invalid. Because comments are not allowed in strict JSON standards.
*
* zh-CN:
*
* 判断给定的字符串是否是有效的 JSON 格式.
*
* 支持以下几种常见 JSON 格式:
* - 对象 (例如: `{"key": "value"}`)
* - 数组 (例如: `[1, 2, 3]`)
* - 单一值 (例如: `"string"`, `123`, `true`, `null`)
*
* 注意:
* 1. 空字符串或仅包含空白字符的字符串被视为无效的 JSON.
* 2. 如果 JSON 中包含注释 (例如: `// 注释` 或 `/* 多行注释 */`), 此方法会判断为无效. 因为注释不符合严格的 JSON 规范.
*/
@JvmStatic
fun isValidJson(json: String) = json.isNotBlank() && runCatching { JSONTokener(json).nextValue() }.isSuccess
class FuzzyDeserializer<T> : JsonDeserializer<T> { class FuzzyDeserializer<T> : JsonDeserializer<T> {
@@ -99,7 +127,6 @@ object JsonUtils {
} catch (e: Exception) { } catch (e: Exception) {
throw RuntimeException("Failed to create an instance of ${clazz.name}", e) throw RuntimeException("Failed to create an instance of ${clazz.name}", e)
} }
if (json is JsonObject) { if (json is JsonObject) {
clazz.declaredFields.forEach { processField(it, json, instance, context) } clazz.declaredFields.forEach { processField(it, json, instance, context) }
} }
@@ -111,6 +138,7 @@ object JsonUtils {
val serializedNameAnnotation = field.getAnnotation(SerializedName::class.java) val serializedNameAnnotation = field.getAnnotation(SerializedName::class.java)
val compatibleAnnotation = field.getAnnotation(SerializedNameCompatible::class.java) val compatibleAnnotation = field.getAnnotation(SerializedNameCompatible::class.java)
val deserializedAnnotation = field.getAnnotation(DeserializedMethodName::class.java)
val sanitizedPrimaryName = sanitizeKey(serializedNameAnnotation?.value ?: field.name) val sanitizedPrimaryName = sanitizeKey(serializedNameAnnotation?.value ?: field.name)
val sanitizedAlternateNames = serializedNameAnnotation?.alternate?.map { sanitizeKey(it) } ?: emptyList() val sanitizedAlternateNames = serializedNameAnnotation?.alternate?.map { sanitizeKey(it) } ?: emptyList()
@@ -118,13 +146,16 @@ object JsonUtils {
val serializedNames: List<Pair<String, Boolean>> = (sanitizedAlternateNames + sanitizedPrimaryName).map { it to false } + sanitizedCompatibleNames val serializedNames: List<Pair<String, Boolean>> = (sanitizedAlternateNames + sanitizedPrimaryName).map { it to false } + sanitizedCompatibleNames
for ((jsonKey, jsonValue) in json.entrySet()) { json.entrySet().forEach { (jsonKey, jsonValue) ->
val sanitizedJsonKey = sanitizeKey(jsonKey) val sanitizedJsonKey = sanitizeKey(jsonKey)
for (pair in serializedNames) { serializedNames.forEach { pair ->
val (serializedKey, isReversed) = pair val (serializedKey, isReversed) = pair
if (sanitizedJsonKey == serializedKey) { if (sanitizedJsonKey == serializedKey) {
when (field.type) { when {
Boolean::class.javaPrimitiveType, Boolean::class.java -> { deserializedAnnotation != null -> {
handleDeserializedMethod(field, instance, jsonValue, deserializedAnnotation)
}
field.type == Boolean::class.javaPrimitiveType || field.type == Boolean::class.java -> {
if (jsonValue.isJsonPrimitive && jsonValue.asJsonPrimitive.isBoolean) { if (jsonValue.isJsonPrimitive && jsonValue.asJsonPrimitive.isBoolean) {
val value = jsonValue.asBoolean val value = jsonValue.asBoolean
field.set(instance, if (isReversed) !value else value) field.set(instance, if (isReversed) !value else value)
@@ -137,12 +168,33 @@ object JsonUtils {
} }
} }
} }
return return@processField
} }
} }
} }
} }
private fun handleDeserializedMethod(field: Field, instance: T, jsonValue: JsonElement, annotation: DeserializedMethodName) {
require(jsonValue.isJsonPrimitive && jsonValue.asJsonPrimitive.isString) {
"Field with @DeserializedMethodName must map to a JSON string."
}
val methodName = annotation.method
val methodInput = jsonValue.asString
val parameterTypes = annotation.parameterTypes.map { it.java }.toTypedArray()
val method = runCatching {
instance!!::class.java.getMethod(methodName, *parameterTypes)
}.getOrElse { e ->
throw RuntimeException("Method $methodName with parameter types ${parameterTypes.contentToString()} not found in ${instance!!::class.java.name}", e)
}
try {
val isStatic = Modifier.isStatic(method.modifiers)
val result = method.invoke(instance.takeUnless { isStatic }, methodInput)
field.set(instance, result)
} catch (e: Exception) {
throw RuntimeException("Failed to invoke method $methodName on ${field.name}", e)
}
}
private fun sanitizeKey(key: String): String { private fun sanitizeKey(key: String): String {
return key.replace(Regex("[^a-zA-Z0-9]"), "").lowercase() return key.replace(Regex("[^a-zA-Z0-9]"), "").lowercase()
} }

View File

@@ -1,5 +1,5 @@
#Mon Jan 20 15:15:12 CST 2025 #Wed Feb 05 20:51:07 CST 2025
BUILD_TIME=1737357312345 BUILD_TIME=1738759867266
COMPILE_SDK_VERSION=34 COMPILE_SDK_VERSION=34
JAVA_VERSION=23 JAVA_VERSION=23
JAVA_VERSION_MIN_RADICAL=0 JAVA_VERSION_MIN_RADICAL=0
@@ -17,6 +17,6 @@ RAPID_OCR_OPENCV_MOBILE_LABEL_VERSION=13
RAPID_OCR_OPENCV_MOBILE_VERSION=4.5.3 RAPID_OCR_OPENCV_MOBILE_VERSION=4.5.3
TARGET_SDK_VERSION=34 TARGET_SDK_VERSION=34
TARGET_SDK_VERSION_INRT=29 TARGET_SDK_VERSION_INRT=29
VERSION_BUILD=2979 VERSION_BUILD=2982
VERSION_NAME=6.6.2 Alpha2 VERSION_NAME=6.6.2 Alpha2
VSCODE_EXT_REQUIRED_VERSION=1.0.8 VSCODE_EXT_REQUIRED_VERSION=1.0.8