diff --git a/.readme/template_readme.md b/.readme/template_readme.md
index 5e4c9296..023ae4c6 100644
--- a/.readme/template_readme.md
+++ b/.readme/template_readme.md
@@ -55,7 +55,7 @@
| [Auto.js Pro 9](https://pro.autojs.org/) | AutoJsPro | [hyb1996](https://github.com/hyb1996) | `2021/03/28` | `2023/02/09` |
| [AutoJs6](https://github.com/SuperMonster003/AutoJs6) | [AutoJs6](https://github.com/SuperMonster003/AutoJs6/commit/a8ce1b9acb541e9736c33134be3194c3148a15a3#diff-833a46a97033e77558372a2dce103fd6fee29aaaa899f610022a7aece592ee7bR27) | [SuperMonster003](https://github.com/SuperMonster003) | `2021/12/01` | - |
| [AutoX](https://github.com/kkevsekk1/AutoX) | [Autox.js v6](https://github.com/kkevsekk1/AutoX/commit/8b6776cff8b0fca4be4a52719b7d7d07c0a058f3#diff-51a0b488f963eb0be6c6599bf5df497313877cf5bdff3950807373912ac1cdc9R130) | [wilinz](https://github.com/wilinz) | `2022/05/26` | - |
-| [AutoX](https://github.com/kkevsekk1/AutoX) | [Autox.js v7](https://github.com/kkevsekk1/AutoX/commit/484491fd5fe12b8203d0b09c181eb0f471c0ea9f#diff-8cff73265af19c059547b76aca8882cbaa3209291406f52df1dafbbc78e80c46R120) | [aiselp](https://github.com/aiselp) | `2024/04/21` | - |
+| [AutoX](https://github.com/aiselp/AutoX) | [Autox.js v7](https://github.com/aiselp/AutoX/commit/484491fd5fe12b8203d0b09c181eb0f471c0ea9f#diff-8cff73265af19c059547b76aca8882cbaa3209291406f52df1dafbbc78e80c46R120) | [aiselp](https://github.com/aiselp) | `2024/04/21` | - |
{{ p_introduction_related_autojs_table_date_is_estimated }}.
diff --git a/app/src/main/java/org/autojs/autojs/annotation/DeserializedMethodName.java b/app/src/main/java/org/autojs/autojs/annotation/DeserializedMethodName.java
new file mode 100644
index 00000000..3ef56752
--- /dev/null
+++ b/app/src/main/java/org/autojs/autojs/annotation/DeserializedMethodName.java
@@ -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 {};
+}
diff --git a/app/src/main/java/org/autojs/autojs/apkbuilder/ApkBuilder.kt b/app/src/main/java/org/autojs/autojs/apkbuilder/ApkBuilder.kt
index 06ba38d1..374692ec 100644
--- a/app/src/main/java/org/autojs/autojs/apkbuilder/ApkBuilder.kt
+++ b/app/src/main/java/org/autojs/autojs/apkbuilder/ApkBuilder.kt
@@ -16,8 +16,9 @@ import org.autojs.autojs.app.GlobalAppContext
import org.autojs.autojs.engine.encryption.AdvancedEncryptionStandard
import org.autojs.autojs.pio.PFiles
import org.autojs.autojs.project.BuildInfo
+import org.autojs.autojs.project.LaunchConfig
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.JavaScriptFileSource
import org.autojs.autojs.util.FileUtils.TYPE.JAVASCRIPT
@@ -39,7 +40,7 @@ import java.io.InputStream
* Created by Stardust on Oct 24, 2017.
* 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 mArscPackageName: String? = null
@@ -49,7 +50,7 @@ open class ApkBuilder(apkInputStream: InputStream?, private val outApkFile: File
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 }
@@ -57,14 +58,14 @@ open class ApkBuilder(apkInputStream: InputStream?, private val outApkFile: File
private var mAssetsFileIncludes = Libs.defaultAssetFilesToInclude.toMutableList()
private var mAssetsDirExcludes = Libs.defaultAssetDirsToExclude.toMutableList()
- private var mSplashThemeId = 0
- private var mNoSplashThemeId = 0
+ private var mSplashThemeId: Int = 0
+ private var mNoSplashThemeId: Int = 0
private val mManifestFile
- get() = File(workspacePath, "AndroidManifest.xml")
+ get() = File(buildPath, "AndroidManifest.xml")
private val mResourcesArscFile
- get() = File(workspacePath, "resources.arsc")
+ get() = File(buildPath, "resources.arsc")
init {
PFiles.ensureDir(outApkFile.path)
@@ -75,7 +76,7 @@ open class ApkBuilder(apkInputStream: InputStream?, private val outApkFile: File
@Throws(IOException::class)
fun prepare() = also {
mProgressCallback?.let { callback -> GlobalAppContext.post { callback.onPrepare(this) } }
- File(workspacePath).mkdirs()
+ File(buildPath).mkdirs()
mApkPackager.unzip()
}
@@ -97,7 +98,7 @@ open class ApkBuilder(apkInputStream: InputStream?, private val outApkFile: File
@Throws(IOException::class)
fun copyDir(srcFile: File, relativeDestPath: String) {
- val destDirFile = File(workspacePath, relativeDestPath).apply { mkdir() }
+ val destDirFile = File(buildPath, relativeDestPath).apply { mkdir() }
srcFile.listFiles()?.forEach { srcChildFile ->
if (srcChildFile.isFile) {
if (srcChildFile.name.endsWith(JAVASCRIPT.extensionWithDot)) {
@@ -133,7 +134,7 @@ open class ApkBuilder(apkInputStream: InputStream?, private val outApkFile: File
@Throws(IOException::class)
fun replaceFile(srcFile: File, relativeDestPath: String) = also {
- val destFile = File(workspacePath, relativeDestPath)
+ val destFile = File(buildPath, relativeDestPath)
if (destFile.name.endsWith(JAVASCRIPT.extensionWithDot)) {
encrypt(srcFile, destFile)
} else {
@@ -144,70 +145,99 @@ open class ApkBuilder(apkInputStream: InputStream?, private val outApkFile: File
@Throws(IOException::class)
fun withConfig(config: ProjectConfig) = also {
config.also { mProjectConfig = it }.run {
- if (!launchConfig.isSplashVisible) {
- 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()
- }
- }
- mManifestEditor = editManifest()
- .setAppName(name)
- .setVersionName(versionName)
- .setVersionCode(versionCode)
- .setPackageName(packageName)
-
+ retrieveSplashThemeResources(launchConfig)
+ prepareManifestConfiguration(this)
setArscPackageName(packageName)
updateProjectConfig(this)
- copyAssetsRecursively("", File(workspacePath, "assets"))
+ copyAssetsRecursively("", File(buildDir, "assets"))
copyLibrariesByConfig(this)
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)
fun editManifest(): ManifestEditor = ManifestEditorWithAuthorities(FileInputStream(mManifestFile)).also { mManifestEditor = it }
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)
- PFiles.write(ProjectConfig.configFileOfDir(config.sourcePath), it.toJson())
+
+ // 这里为什么要有这样的一个方法? (
+ // 会不会是因为有些配置需要写入到文件中, 这些配置包括自增的版本号, 用户的选择或键入值等等
+ // )
+ // 参数 config 只是获取了一部分的字段用于设置新的 projectConfig, 为什么不直接使用全部的字段? (
+ // 因为有些不需要更新. 如果我们需要将所有设置开放到 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()
- .setMainScriptFileName(DEFAULT_MAIN_SCRIPT_FILE_NAME)
- .setName(config.name)
- .setPackageName(config.packageName)
- .setVersionName(config.versionName)
- .setVersionCode(config.versionCode)
- .also { newProjectConfig ->
- newProjectConfig.buildInfo = BuildInfo.generate(newProjectConfig.versionCode.toLong())
- File(workspacePath, "assets/project/${ProjectConfig.CONFIG_FILE_NAME}").also { file ->
+ // @Hint by SuperMonster003 on Jan 23, 2025.
+ // ! Single file packaging.
+ // ! zh-CN: 打包单独文件.
+ return@run ProjectConfig().also { newProjectConfig ->
+ newProjectConfig
+ .setName(config.name)
+ .setPackageName(config.packageName)
+ .setVersionName(config.versionName)
+ .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() }
}.writeText(newProjectConfig.toJson())
}
- projectConfig.run {
- mKey = MD5Utils.md5(packageName + versionName + mainScriptFileName)
- mInitVector = MD5Utils.md5(buildInfo.buildId + name).substring(0, 16)
- Libs.entries.forEach { entry ->
- if (config.libs.contains(entry.label)) {
- mLibsIncludes += entry.libsToInclude.toSet()
- mAssetsFileIncludes += entry.assetFilesToInclude.toSet()
- mAssetsDirExcludes -= entry.assetDirsToExclude.toSet()
- }
+ }
+
+ mKey = MD5Utils.md5(projectConfig.run { packageName + versionName + mainScriptFileName })
+ mInitVector = MD5Utils.md5(projectConfig.run { buildInfo.buildId + name }).take(16)
+ Libs.entries.forEach { entry ->
+ if (config.libs.contains(entry.label)) {
+ mLibsIncludes += entry.libsToInclude.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 appIconPath = appIcon.resValue.decodeValue()
Log.d(TAG, "Icon path: $appIconPath")
- val file = File(workspacePath, appIconPath).also {
+ val file = File(buildPath, appIconPath).also {
if (!it.exists()) {
File(it.parent!!).mkdirs()
it.createNewFile()
@@ -273,19 +303,20 @@ open class ApkBuilder(apkInputStream: InputStream?, private val outApkFile: File
fun sign() = also {
mProgressCallback?.let { callback -> GlobalAppContext.post { callback.onSign(this) } }
val fos = FileOutputStream(outApkFile)
- TinySign.sign(File(workspacePath), fos)
+ TinySign.sign(File(buildPath), fos)
fos.close()
- val defaultKeyStoreFile = File(workspacePath, "default_key_store.bks")
- val tmpOutputApk = File(workspacePath, "temp.apk")
+ val defaultKeyStoreFile = File(buildPath, "default_key_store.bks")
+ val tmpOutputApk = File(buildPath, "temp.apk")
copyInputStreamToFile(GlobalAppContext.get().assets.open("default_key_store.bks"), defaultKeyStoreFile)
- val signer = ApkSigner(outApkFile, tmpOutputApk)
- signer.useDefaultSignatureVersion = false
- signer.v1SigningEnabled = mProjectConfig.signatureScheme.contains("V1")
- signer.v2SigningEnabled = mProjectConfig.signatureScheme.contains("V2")
- signer.v3SigningEnabled = mProjectConfig.signatureScheme.contains("V3")
- signer.v4SigningEnabled = mProjectConfig.signatureScheme.contains("V4")
+ val signer = ApkSigner(outApkFile, tmpOutputApk).apply {
+ useDefaultSignatureVersion = false
+ v1SigningEnabled = "V1" in mProjectConfig.signatureScheme
+ v2SigningEnabled = "V2" in mProjectConfig.signatureScheme
+ v3SigningEnabled = "V3" in mProjectConfig.signatureScheme
+ v4SigningEnabled = "V4" in mProjectConfig.signatureScheme
+ }
var keyStoreFile = defaultKeyStoreFile
var password = "AutoJs6"
@@ -313,7 +344,7 @@ open class ApkBuilder(apkInputStream: InputStream?, private val outApkFile: File
fun cleanWorkspace() = also {
mProgressCallback?.let { callback -> GlobalAppContext.post { callback.onClean(this) } }
- delete(File(workspacePath))
+ delete(File(buildPath))
}
@Throws(IOException::class)
@@ -321,8 +352,8 @@ open class ApkBuilder(apkInputStream: InputStream?, private val outApkFile: File
@Throws(IOException::class)
private fun buildArsc() {
- val oldArsc = File(workspacePath, "resources.arsc")
- val newArsc = File(workspacePath, "resources.arsc.new")
+ val oldArsc = File(buildPath, "resources.arsc")
+ val newArsc = File(buildPath, "resources.arsc.new")
val decoder = ARSCDecoder(BufferedInputStream(FileInputStream(oldArsc)), null, false)
decoder.CloneArsc(FileOutputStream(newArsc), mArscPackageName, true)
oldArsc.delete()
@@ -345,9 +376,13 @@ open class ApkBuilder(apkInputStream: InputStream?, private val outApkFile: File
private inner class ManifestEditorWithAuthorities(manifestInputStream: InputStream?) : ManifestEditor(manifestInputStream) {
override fun onAttr(attr: AxmlWriter.Attr) {
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) {
value = mNoSplashThemeId
}
+
if (name.data == "authorities" && value is StringItem) {
(value as StringItem).data = "${mProjectConfig.packageName}.fileprovider"
} else {
@@ -388,7 +423,7 @@ open class ApkBuilder(apkInputStream: InputStream?, private val outApkFile: File
mLibsIncludes.distinct().forEach { libName ->
runCatching {
File(srcLibDir, "$abiSrcName/$libName").takeIf { it.exists() }?.copyTo(
- File(workspacePath, "lib/$abiDestName/$libName"),
+ File(buildPath, "lib/$abiDestName/$libName"),
overwrite = true
)
}.onFailure { it.printStackTrace() }
diff --git a/app/src/main/java/org/autojs/autojs/project/ProjectConfig.java b/app/src/main/java/org/autojs/autojs/project/ProjectConfig.java
index d6cf7189..507e21bc 100644
--- a/app/src/main/java/org/autojs/autojs/project/ProjectConfig.java
+++ b/app/src/main/java/org/autojs/autojs/project/ProjectConfig.java
@@ -9,6 +9,7 @@ import androidx.annotation.Nullable;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.annotations.SerializedName;
+import org.autojs.autojs.annotation.DeserializedMethodName;
import org.autojs.autojs.annotation.SerializedNameCompatible;
import org.autojs.autojs.annotation.SerializedNameCompatible.With;
import org.autojs.autojs.apkbuilder.keystore.KeyStore;
@@ -22,14 +23,13 @@ import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
-import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
-import java.util.Map;
-import java.util.Objects;
+import java.util.Set;
import java.util.concurrent.Callable;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import java.util.stream.Collectors;
/**
* Created by Stardust on Jan 24, 2018.
@@ -49,18 +49,24 @@ public class ProjectConfig {
.create();
@SerializedName("name")
- @SerializedNameCompatible(with = {@With(value = "projectName")})
+ @SerializedNameCompatible(with = {
+ @With(value = "projectName"),
+ })
private String mName;
@SerializedName("versionName")
- @SerializedNameCompatible(with = {@With(value = "version")})
+ @SerializedNameCompatible(with = {
+ @With(value = "version"),
+ })
private String mVersionName;
@SerializedName("versionCode")
- private int mVersionCode = -1;
+ private int mVersionCode = 1;
@SerializedName("packageName")
- @SerializedNameCompatible(with = {@With(value = "package")})
+ @SerializedNameCompatible(with = {
+ @With(value = "package"),
+ })
private String mPackageName;
@SerializedName("main")
@@ -73,7 +79,7 @@ public class ProjectConfig {
@With(value = "mainFile"),
@With(value = "mainFileName"),
})
- private String mMainScriptFileName;
+ private String mMainScriptFileName = DEFAULT_MAIN_SCRIPT_FILE_NAME;
@Nullable
@SerializedName(value = "assets")
@@ -84,15 +90,21 @@ public class ProjectConfig {
private List mAssets = new ArrayList<>();
@SerializedName("launchConfig")
- @SerializedNameCompatible(with = {@With(value = "launch")})
+ @SerializedNameCompatible(with = {
+ @With(value = "launch"),
+ })
private LaunchConfig mLaunchConfig = new LaunchConfig();
@SerializedName("build")
- @SerializedNameCompatible(with = {@With(value = "buildInfo")})
+ @SerializedNameCompatible(with = {
+ @With(value = "buildInfo"),
+ })
private BuildInfo mBuildInfo = new BuildInfo();
@SerializedName("icon")
- @SerializedNameCompatible(with = {@With(value = "iconPath")})
+ @SerializedNameCompatible(with = {
+ @With(value = "iconPath"),
+ })
private String mIconPath;
private transient Callable mIconBitmapGetter;
@@ -125,19 +137,23 @@ public class ProjectConfig {
@With(value = "signatureSchemes"),
@With(value = "signature"),
})
+ @DeserializedMethodName(method = "normalizeSignatureScheme", parameterTypes = {String.class})
private String mSignatureScheme = "V1 + V2";
@Nullable
private transient KeyStore mKeyStore = null;
- @SerializedName("scriptConfigs")
- @SerializedNameCompatible(with = {
- @With(value = "scriptsConfigs"),
- @With(value = "scriptsConfig"),
- @With(value = "scriptConfig"),
- @With(value = "scripts", target = {"AutoJs4", "AutoX"}),
- })
- private final Map mScriptConfigs = new HashMap<>();
+ // @Commented by SuperMonster003 on Jan 20, 2025.
+ // ! Unused config options: "scripts".
+ // ! zh-CN: 未使用的配置选项: "scripts".
+ // # @SerializedName("scriptConfigs")
+ // # @SerializedNameCompatible(with = {
+ // # @With(value = "scriptsConfigs"),
+ // # @With(value = "scriptsConfig"),
+ // # @With(value = "scriptConfig"),
+ // # @With(value = "scripts", target = {"AutoJs4", "AutoX"}),
+ // # })
+ // # private final Map mScriptConfigs = new HashMap<>();
@SerializedName(value = "useFeatures")
@SerializedNameCompatible(with = {
@@ -362,8 +378,9 @@ public class ProjectConfig {
return mBuildInfo;
}
- public void setBuildInfo(BuildInfo buildInfo) {
+ public ProjectConfig setBuildInfo(BuildInfo buildInfo) {
mBuildInfo = buildInfo;
+ return this;
}
public String getName() {
@@ -412,7 +429,7 @@ public class ProjectConfig {
@NonNull
public String getMainScriptFileName() {
- return mMainScriptFileName != null ? mMainScriptFileName : DEFAULT_MAIN_SCRIPT_FILE_NAME;
+ return mMainScriptFileName;
}
public ProjectConfig setMainScriptFileName(String mainScriptFileName) {
@@ -420,9 +437,12 @@ public class ProjectConfig {
return this;
}
- public Map getScriptConfigs() {
- return mScriptConfigs;
- }
+ // @Commented by SuperMonster003 on Jan 20, 2025.
+ // ! Unused config options: "scripts".
+ // ! zh-CN: 未使用的配置选项: "scripts".
+ // # public Map getScriptConfigs() {
+ // # return mScriptConfigs;
+ // # }
public List getAssets() {
if (mAssets == null) {
@@ -516,22 +536,20 @@ public class ProjectConfig {
mFeatures = features;
}
- public ScriptConfig getScriptConfig(String path) {
- ScriptConfig scriptConfig = Objects.requireNonNull(mScriptConfigs.getOrDefault(path, new ScriptConfig()));
- List combinedFeatures = getCombinedFeatures(scriptConfig);
- scriptConfig.setFeatures(combinedFeatures);
- return scriptConfig;
- }
-
- @NotNull
- public ArrayList getCombinedFeatures(ScriptConfig scriptConfig) {
- return new ArrayList<>(
- new HashSet<>() {{
- addAll(scriptConfig.getFeatures());
- addAll(mFeatures);
- }}
- );
- }
+ // @Commented by SuperMonster003 on Jan 20, 2025.
+ // ! Unused config options: "scripts".
+ // ! zh-CN: 未使用的配置选项: "scripts".
+ // # public ScriptConfig getScriptConfig(String scriptKeyName) {
+ // # ScriptConfig scriptConfig = Objects.requireNonNull(mScriptConfigs.getOrDefault(scriptKeyName, new ScriptConfig()));
+ // # List combinedFeatures = new ArrayList<>(
+ // # new HashSet<>() {{
+ // # addAll(scriptConfig.getFeatures());
+ // # addAll(mFeatures);
+ // # }}
+ // # );
+ // # scriptConfig.setFeatures(combinedFeatures);
+ // # return scriptConfig;
+ // # }
public List getExcludedDirs() {
return mExcludedDirs;
@@ -566,10 +584,24 @@ public class ProjectConfig {
}
public ProjectConfig setSignatureScheme(String signatureScheme) {
- mSignatureScheme = signatureScheme;
+ mSignatureScheme = normalizeSignatureScheme(signatureScheme);
return this;
}
+ public static String normalizeSignatureScheme(String input) {
+ Pattern pattern = Pattern.compile("v\\d+", Pattern.CASE_INSENSITIVE);
+ Matcher matcher = pattern.matcher(input);
+ Set 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
public KeyStore getKeyStore() {
return mKeyStore;
diff --git a/app/src/main/java/org/autojs/autojs/ui/project/BuildActivity.java b/app/src/main/java/org/autojs/autojs/ui/project/BuildActivity.java
index aad29a68..f61a54b4 100644
--- a/app/src/main/java/org/autojs/autojs/ui/project/BuildActivity.java
+++ b/app/src/main/java/org/autojs/autojs/ui/project/BuildActivity.java
@@ -21,6 +21,7 @@ import android.widget.Spinner;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.core.util.Pair;
import androidx.lifecycle.ViewModelProvider;
import com.afollestad.materialdialogs.MaterialDialog;
import com.google.android.flexbox.FlexboxLayout;
@@ -70,6 +71,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Function;
import java.util.regex.Pattern;
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.util.StringUtils.key;
@@ -77,6 +79,8 @@ import static org.autojs.autojs.util.StringUtils.key;
/**
* Created by Stardust on Oct 22, 2017.
* Modified by SuperMonster003 as of Dec 1, 2023.
+ *
+ * @noinspection ResultOfMethodCallIgnored
*/
public class BuildActivity extends BaseActivity implements ApkBuilder.ProgressCallback {
@@ -105,14 +109,14 @@ public class BuildActivity extends BaseActivity implements ApkBuilder.ProgressCa
private static final Map> LIB_ALIASES = new HashMap<>();
- private static final ArrayList SIGNATURE_SCHEMES = new ArrayList<>() {{
- add("V1 + V2");
- add("V1 + V3");
- add("V1 + V2 + V3");
- add("V1");
- add("V2 + V3 (Android 7.0+)");
- add("V2 (Android 7.0+)");
- add("V3 (Android 9.0+)");
+ private static final List> SIGNATURE_SCHEMES = new ArrayList<>() {{
+ add(new Pair<>("V1 + V2", null));
+ add(new Pair<>("V1 + V3", null));
+ add(new Pair<>("V1 + V2 + V3", null));
+ add(new Pair<>("V1", null));
+ add(new Pair<>("V2 + V3", "Android 7.0+"));
+ add(new Pair<>("V2", "Android 7.0+"));
+ add(new Pair<>("V3", "Android 9.0+"));
}};
private final Map SUPPORTED_PERMISSIONS = new TreeMap<>() {{
@@ -173,16 +177,19 @@ public class BuildActivity extends BaseActivity implements ApkBuilder.ProgressCa
ImageView mIconView;
LinearLayout mAppConfigView;
+ @Nullable
private ProjectConfig mProjectConfig;
+
private MaterialDialog mProgressDialog;
private String mSource;
private boolean mIsDefaultIcon = true;
private boolean mIsProjectLevelBuilding;
- private FlexboxLayout mFlexboxAbis;
- private FlexboxLayout mFlexboxLibs;
- private Spinner mSignatureSchemes;
- private Spinner mVerifiedKeyStores;
- private FlexboxLayout mFlexboxPermissions;
+
+ private FlexboxLayout mFlexboxAbisView;
+ private FlexboxLayout mFlexboxLibsView;
+ private Spinner mSignatureSchemesView;
+ private Spinner mVerifiedKeyStoresView;
+ private FlexboxLayout mFlexboxPermissionsView;
private final ArrayList mInvalidAbis = new ArrayList<>();
private final ArrayList mUnavailableAbis = new ArrayList<>();
@@ -261,35 +268,30 @@ public class BuildActivity extends BaseActivity implements ApkBuilder.ProgressCa
mAppConfigView = binding.appConfig;
- mFlexboxAbis = binding.flexboxAbis;
+ mFlexboxAbisView = binding.flexboxAbis;
initAbisChildren();
- mFlexboxLibs = binding.flexboxLibraries;
+ mFlexboxLibsView = binding.flexboxLibraries;
initLibsChildren();
mKeyStoreViewModel = new ViewModelProvider(this, new KeyStoreViewModel.Factory(getApplicationContext())).get(KeyStoreViewModel.class);
mKeyStoreViewModel.updateVerifiedKeyStores();
- mSignatureSchemes = binding.spinnerSignatureSchemes;
- initSignatureSchemeSpinner();
-
- mVerifiedKeyStores = binding.spinnerVerifiedKeyStores;
- initVerifiedKeyStoresSpinner();
-
- mFlexboxPermissions = binding.flexboxPermissions;
- initPermissionsChildren();
+ mSignatureSchemesView = binding.spinnerSignatureSchemes;
+ mVerifiedKeyStoresView = binding.spinnerVerifiedKeyStores;
+ mFlexboxPermissionsView = binding.flexboxPermissions;
binding.fab.setOnClickListener(v -> buildApk());
binding.selectSource.setOnClickListener(v -> selectSourceFilePath());
binding.selectOutput.setOnClickListener(v -> selectOutputDirPath());
- binding.textAbis.setOnClickListener(v -> toggleAllFlexboxChildren(mFlexboxAbis));
+ binding.textAbis.setOnClickListener(v -> toggleAllFlexboxChildren(mFlexboxAbisView));
binding.textAbis.setOnLongClickListener(v -> {
syncAbisCheckedStates();
return true;
});
- binding.textLibs.setOnClickListener(v -> toggleAllFlexboxChildren(mFlexboxLibs));
+ binding.textLibs.setOnClickListener(v -> toggleAllFlexboxChildren(mFlexboxLibsView));
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);
mSource = getIntent().getStringExtra(EXTRA_SOURCE);
@@ -297,6 +299,10 @@ public class BuildActivity extends BaseActivity implements ApkBuilder.ProgressCa
setupWithSourceFile(new ScriptFile(mSource));
}
+ initSignatureSchemeSpinner();
+ initVerifiedKeyStoresSpinner();
+ initPermissionsChildren();
+
syncAbisCheckedStates();
syncLibsCheckedStates();
@@ -354,7 +360,7 @@ public class BuildActivity extends BaseActivity implements ApkBuilder.ProgressCa
child.setChecked(false);
child.setEnabled(false);
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 filterForDefaultChecked) {
List appSupportedAbiList = AndroidUtils.getAppSupportedAbiList();
- for (int i = 0; i < mFlexboxAbis.getChildCount(); i += 1) {
- View child = mFlexboxAbis.getChildAt(i);
+ for (int i = 0; i < mFlexboxAbisView.getChildCount(); i += 1) {
+ View child = mFlexboxAbisView.getChildAt(i);
if (child instanceof RoundCheckboxWithText) {
CharSequence standardAbi = ((RoundCheckboxWithText) child).getText();
if (standardAbi != null) {
@@ -419,7 +425,7 @@ public class BuildActivity extends BaseActivity implements ApkBuilder.ProgressCa
RoundCheckboxWithText child = new RoundCheckboxWithText(this, null);
child.setText(text);
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);
- for (int i = 0; i < mFlexboxLibs.getChildCount(); i += 1) {
- View child = mFlexboxLibs.getChildAt(i);
+ for (int i = 0; i < mFlexboxLibsView.getChildCount(); i += 1) {
+ View child = mFlexboxLibsView.getChildAt(i);
if (child instanceof RoundCheckboxWithText) {
CharSequence standardLib = ((RoundCheckboxWithText) child).getText();
if (standardLib != null) {
@@ -447,9 +453,23 @@ public class BuildActivity extends BaseActivity implements ApkBuilder.ProgressCa
}
private void initSignatureSchemeSpinner() {
- ArrayAdapter adapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_item, SIGNATURE_SCHEMES);
+ ArrayAdapter 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);
- 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() {
@@ -460,7 +480,7 @@ public class BuildActivity extends BaseActivity implements ApkBuilder.ProgressCa
ArrayAdapter adapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_item, verifiedKeyStores);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
- mVerifiedKeyStores.setAdapter(adapter);
+ mVerifiedKeyStoresView.setAdapter(adapter);
mKeyStoreViewModel.getVerifiedKeyStores().observe(this, keyStores -> {
// 清空现有的选项,但保留第一个元素,即默认密钥库
@@ -487,7 +507,7 @@ public class BuildActivity extends BaseActivity implements ApkBuilder.ProgressCa
int marginInPixels = (int) (8 * getResources().getDisplayMetrics().density);
checkBox.setPadding(marginInPixels, 0, 0, 0);
checkBox.setChecked(false);
- mFlexboxPermissions.addView(checkBox);
+ mFlexboxPermissionsView.addView(checkBox);
});
}
@@ -644,8 +664,8 @@ public class BuildActivity extends BaseActivity implements ApkBuilder.ProgressCa
}
private boolean checkAbis() {
- for (int i = 0; i < mFlexboxAbis.getChildCount(); i += 1) {
- View child = mFlexboxAbis.getChildAt(i);
+ for (int i = 0; i < mFlexboxAbisView.getChildCount(); i += 1) {
+ View child = mFlexboxAbisView.getChildAt(i);
if (child instanceof RoundCheckboxWithText) {
if (((RoundCheckboxWithText) child).isChecked() && child.isEnabled()) {
return true;
@@ -774,11 +794,11 @@ public class BuildActivity extends BaseActivity implements ApkBuilder.ProgressCa
@SuppressLint("CheckResult")
private void doBuildingApk() {
ProjectConfig projectConfig = determineProjectConfig();
- File tmpDir = new File(getCacheDir(), "build/");
+ File buildPath = new File(getCacheDir(), "build/");
File outApk = new File(mOutputPathView.getText().toString(),
String.format("%s_v%s.apk", projectConfig.getName(), projectConfig.getVersionName()));
showProgressDialog();
- Observable.fromCallable(() -> callApkBuilder(tmpDir, outApk, projectConfig))
+ Observable.fromCallable(() -> callApkBuilder(buildPath, outApk, projectConfig))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(apkBuilder -> {
@@ -791,9 +811,9 @@ public class BuildActivity extends BaseActivity implements ApkBuilder.ProgressCa
}
private ProjectConfig determineProjectConfig() {
- ArrayList abis = collectCheckedItems(mFlexboxAbis);
- ArrayList libs = collectCheckedItems(mFlexboxLibs);
- ArrayList permissions = collectCheckedItems(mFlexboxPermissions);
+ ArrayList abis = collectCheckedItems(mFlexboxAbisView);
+ ArrayList libs = collectCheckedItems(mFlexboxLibsView);
+ ArrayList permissions = collectCheckedItems(mFlexboxPermissionsView);
ProjectConfig projectConfig;
if (mProjectConfig != null) {
@@ -814,8 +834,8 @@ public class BuildActivity extends BaseActivity implements ApkBuilder.ProgressCa
return projectConfig
.setAbis(abis)
.setLibs(libs)
- .setKeyStore(mVerifiedKeyStores.getSelectedItemPosition() > 0 ? (KeyStore) mVerifiedKeyStores.getSelectedItem() : null)
- .setSignatureScheme(mSignatureSchemes.getSelectedItem().toString())
+ .setKeyStore(mVerifiedKeyStoresView.getSelectedItemPosition() > 0 ? (KeyStore) mVerifiedKeyStoresView.getSelectedItem() : null)
+ .setSignatureScheme(mSignatureSchemesView.getSelectedItem().toString())
.setPermissions(permissions);
}
@@ -844,9 +864,9 @@ public class BuildActivity extends BaseActivity implements ApkBuilder.ProgressCa
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);
- return new ApkBuilder(templateApk, outApk, tmpDir.getPath())
+ return new ApkBuilder(templateApk, outApk, buildPath.getPath())
.setProgressCallback(BuildActivity.this)
.prepare()
.withConfig(projectConfig)
diff --git a/app/src/main/java/org/autojs/autojs/util/JsonUtils.kt b/app/src/main/java/org/autojs/autojs/util/JsonUtils.kt
index 57193021..95b6d14c 100644
--- a/app/src/main/java/org/autojs/autojs/util/JsonUtils.kt
+++ b/app/src/main/java/org/autojs/autojs/util/JsonUtils.kt
@@ -5,10 +5,11 @@ import com.google.gson.JsonDeserializer
import com.google.gson.JsonElement
import com.google.gson.JsonObject
import com.google.gson.annotations.SerializedName
+import org.autojs.autojs.annotation.DeserializedMethodName
import org.autojs.autojs.annotation.SerializedNameCompatible
-import org.json.JSONArray
-import org.json.JSONObject
+import org.json.JSONTokener
import java.lang.reflect.Field
+import java.lang.reflect.Modifier
import java.lang.reflect.Type
/**
@@ -84,7 +85,34 @@ object JsonUtils {
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 : JsonDeserializer {
@@ -99,7 +127,6 @@ object JsonUtils {
} catch (e: Exception) {
throw RuntimeException("Failed to create an instance of ${clazz.name}", e)
}
-
if (json is JsonObject) {
clazz.declaredFields.forEach { processField(it, json, instance, context) }
}
@@ -111,6 +138,7 @@ object JsonUtils {
val serializedNameAnnotation = field.getAnnotation(SerializedName::class.java)
val compatibleAnnotation = field.getAnnotation(SerializedNameCompatible::class.java)
+ val deserializedAnnotation = field.getAnnotation(DeserializedMethodName::class.java)
val sanitizedPrimaryName = sanitizeKey(serializedNameAnnotation?.value ?: field.name)
val sanitizedAlternateNames = serializedNameAnnotation?.alternate?.map { sanitizeKey(it) } ?: emptyList()
@@ -118,13 +146,16 @@ object JsonUtils {
val serializedNames: List> = (sanitizedAlternateNames + sanitizedPrimaryName).map { it to false } + sanitizedCompatibleNames
- for ((jsonKey, jsonValue) in json.entrySet()) {
+ json.entrySet().forEach { (jsonKey, jsonValue) ->
val sanitizedJsonKey = sanitizeKey(jsonKey)
- for (pair in serializedNames) {
+ serializedNames.forEach { pair ->
val (serializedKey, isReversed) = pair
if (sanitizedJsonKey == serializedKey) {
- when (field.type) {
- Boolean::class.javaPrimitiveType, Boolean::class.java -> {
+ when {
+ deserializedAnnotation != null -> {
+ handleDeserializedMethod(field, instance, jsonValue, deserializedAnnotation)
+ }
+ field.type == Boolean::class.javaPrimitiveType || field.type == Boolean::class.java -> {
if (jsonValue.isJsonPrimitive && jsonValue.asJsonPrimitive.isBoolean) {
val value = jsonValue.asBoolean
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 {
return key.replace(Regex("[^a-zA-Z0-9]"), "").lowercase()
}
diff --git a/version.properties b/version.properties
index b6feca74..8067396f 100644
--- a/version.properties
+++ b/version.properties
@@ -1,5 +1,5 @@
-#Mon Jan 20 15:15:12 CST 2025
-BUILD_TIME=1737357312345
+#Wed Feb 05 20:51:07 CST 2025
+BUILD_TIME=1738759867266
COMPILE_SDK_VERSION=34
JAVA_VERSION=23
JAVA_VERSION_MIN_RADICAL=0
@@ -17,6 +17,6 @@ RAPID_OCR_OPENCV_MOBILE_LABEL_VERSION=13
RAPID_OCR_OPENCV_MOBILE_VERSION=4.5.3
TARGET_SDK_VERSION=34
TARGET_SDK_VERSION_INRT=29
-VERSION_BUILD=2979
+VERSION_BUILD=2982
VERSION_NAME=6.6.2 Alpha2
VSCODE_EXT_REQUIRED_VERSION=1.0.8