6.6.2 - Alpha2 - 整合 AppConfig 打包类; project.json 支持更多打包选项 (issue #305, #306)

This commit is contained in:
SuperMonster003
2025-01-20 14:39:11 +08:00
parent 436645e921
commit e8cc12dbb8
13 changed files with 379 additions and 274 deletions

View File

@@ -0,0 +1,7 @@
package org.autojs.autojs.annotation
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.PROPERTY, AnnotationTarget.FIELD, AnnotationTarget.FUNCTION)
annotation class SerializedNameCompatible(vararg val with: With) {
annotation class With(val value: String, val target: Array<String> = ["AutoJs6"], val isReversed: Boolean = false)
}

View File

@@ -5,7 +5,6 @@ import android.content.pm.PackageManager.ApplicationInfoFlags
import android.content.pm.PackageManager.GET_SHARED_LIBRARY_FILES
import android.content.res.AssetManager
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.os.Build
import android.util.Log
import com.mcal.apksigner.ApkSigner
@@ -13,7 +12,6 @@ import com.reandroid.arsc.chunk.TableBlock
import org.apache.commons.io.FileUtils.copyFile
import org.apache.commons.io.FileUtils.copyInputStreamToFile
import org.autojs.autojs.apkbuilder.keystore.AESUtils
import org.autojs.autojs.apkbuilder.keystore.KeyStore
import org.autojs.autojs.app.GlobalAppContext
import org.autojs.autojs.engine.encryption.AdvancedEncryptionStandard
import org.autojs.autojs.pio.PFiles
@@ -35,7 +33,6 @@ import java.io.FileNotFoundException
import java.io.FileOutputStream
import java.io.IOException
import java.io.InputStream
import java.util.concurrent.Callable
/**
* Created by Stardust on Oct 24, 2017.
@@ -49,7 +46,7 @@ open class ApkBuilder(apkInputStream: InputStream?, private val outApkFile: File
private var mInitVector: String? = null
private var mKey: String? = null
private lateinit var mAppConfig: AppConfig
private lateinit var mProjectConfig: ProjectConfig
private val mApkPackager = ApkPackager(apkInputStream, workspacePath)
@@ -59,6 +56,9 @@ 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 val mManifestFile
get() = File(workspacePath, "AndroidManifest.xml")
@@ -105,7 +105,7 @@ open class ApkBuilder(apkInputStream: InputStream?, private val outApkFile: File
srcChildFile.copyTo(File(destDirFile, srcChildFile.name), true)
}
} else {
if (!mAppConfig.ignoredDirs.contains(srcChildFile)) {
if (!mProjectConfig.ignoredDirs.contains(srcChildFile)) {
copyDir(srcChildFile, PFiles.join(relativeDestPath, srcChildFile.name + File.separator))
}
}
@@ -141,10 +141,27 @@ open class ApkBuilder(apkInputStream: InputStream?, private val outApkFile: File
}
@Throws(IOException::class)
fun withConfig(config: AppConfig) = also {
config.also { mAppConfig = it }.run {
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(appName)
.setAppName(name)
.setVersionName(versionName)
.setVersionCode(versionCode)
.setPackageName(packageName)
@@ -160,32 +177,32 @@ open class ApkBuilder(apkInputStream: InputStream?, private val outApkFile: File
@Throws(FileNotFoundException::class)
fun editManifest(): ManifestEditor = ManifestEditorWithAuthorities(FileInputStream(mManifestFile)).also { mManifestEditor = it }
private fun updateProjectConfig(appConfig: AppConfig) {
private fun updateProjectConfig(config: ProjectConfig) {
val projectConfig = when {
!PFiles.isDir(appConfig.sourcePath) -> null
else -> ProjectConfig.fromProjectDir(appConfig.sourcePath)?.also {
!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(appConfig.sourcePath), it.toJson())
PFiles.write(ProjectConfig.configFileOfDir(config.sourcePath), it.toJson())
}
} ?: ProjectConfig()
.setMainScriptFile("main.js")
.setName(appConfig.appName)
.setPackageName(appConfig.packageName)
.setVersionName(appConfig.versionName)
.setVersionCode(appConfig.versionCode)
.also { config ->
config.buildInfo = BuildInfo.generate(appConfig.versionCode.toLong())
.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 ->
file.parentFile?.let { parent -> if (!parent.exists()) parent.mkdirs() }
}.writeText(config.toJson())
}.writeText(newProjectConfig.toJson())
}
projectConfig.run {
mKey = MD5Utils.md5(packageName + versionName + mainScriptFile)
mInitVector = MD5Utils.md5(buildInfo.buildId + name).substring(0, 16)
Libs.entries.forEach { entry ->
if (appConfig.libs.contains(entry.label)) {
if (config.libs.contains(entry.label)) {
mLibsIncludes += entry.libsToInclude.toSet()
mAssetsFileIncludes += entry.assetFilesToInclude.toSet()
mAssetsDirExcludes -= entry.assetDirsToExclude.toSet()
@@ -197,7 +214,7 @@ open class ApkBuilder(apkInputStream: InputStream?, private val outApkFile: File
@Throws(Exception::class)
fun build() = also {
mProgressCallback?.let { callback -> GlobalAppContext.post { callback.onBuild(this) } }
mAppConfig.icon?.let { callable ->
mProjectConfig.iconBitmapGetter?.let { callable ->
runCatching {
val tableBlock = TableBlock.load(mResourcesArscFile)
val packageName = "${GlobalAppContext.get().packageName}.inrt"
@@ -264,17 +281,17 @@ open class ApkBuilder(apkInputStream: InputStream?, private val outApkFile: File
val signer = ApkSigner(outApkFile, tmpOutputApk)
signer.useDefaultSignatureVersion = false
signer.v1SigningEnabled = mAppConfig.signatureSchemes.contains("V1")
signer.v2SigningEnabled = mAppConfig.signatureSchemes.contains("V2")
signer.v3SigningEnabled = mAppConfig.signatureSchemes.contains("V3")
signer.v4SigningEnabled = mAppConfig.signatureSchemes.contains("V4")
signer.v1SigningEnabled = mProjectConfig.signatureSchemes.contains("V1")
signer.v2SigningEnabled = mProjectConfig.signatureSchemes.contains("V2")
signer.v3SigningEnabled = mProjectConfig.signatureSchemes.contains("V3")
signer.v4SigningEnabled = mProjectConfig.signatureSchemes.contains("V4")
var keyStoreFile = defaultKeyStoreFile
var password = "AutoJs6"
var alias = "AutoJs6"
var aliasPassword = "AutoJs6"
mAppConfig.keyStore?.let {
mProjectConfig.keyStore?.let {
keyStoreFile = File(it.absolutePath)
password = AESUtils.decrypt(it.password)
alias = it.alias
@@ -324,76 +341,14 @@ open class ApkBuilder(apkInputStream: InputStream?, private val outApkFile: File
}
class AppConfig {
var appName: String? = null
private set
var versionName: String? = null
private set
var versionCode = 0
private set
var sourcePath: String? = null
private set
var packageName: String? = null
private set
var ignoredDirs = ArrayList<File>()
var icon: Callable<Bitmap>? = null
private set
var abis: List<String> = emptyList()
private set
var libs: List<String> = emptyList()
private set
var keyStore: KeyStore? = null
private set
var signatureSchemes: String = "V1 + V2"
private set
var permissions: List<String> = emptyList()
private set
fun ignoreDir(dir: File) = also { ignoredDirs.add(dir) }
fun setAppName(appName: String?) = also { appName?.let { this.appName = it } }
fun setVersionName(versionName: String?) = also { versionName?.let { this.versionName = it } }
fun setVersionCode(versionCode: Int?) = also { versionCode?.let { this.versionCode = it } }
fun setSourcePath(sourcePath: String?) = also { sourcePath?.let { this.sourcePath = it } }
fun setPackageName(packageName: String?) = also { packageName?.let { this.packageName = it } }
fun setIcon(icon: Callable<Bitmap>?) = also { icon?.let { this.icon = it } }
fun setIcon(iconPath: String?) = also { iconPath?.let { this.icon = Callable { BitmapFactory.decodeFile(it) } } }
fun setAbis(abis: List<String>) = also { this.abis = abis }
fun setLibs(libs: List<String>) = also { this.libs = libs }
fun setKeyStore(keyStore: KeyStore?) = also { this.keyStore = keyStore }
fun setSignatureSchemes(signatureSchemes: String) = also { this.signatureSchemes = signatureSchemes }
fun setPermissions(permissions: List<String>) = also { this.permissions = permissions }
companion object {
@JvmStatic
fun fromProjectConfig(projectDir: String?, projectConfig: ProjectConfig) = AppConfig()
.setAppName(projectConfig.name)
.setPackageName(projectConfig.packageName)
.ignoreDir(File(projectDir, projectConfig.buildDir))
.setVersionCode(projectConfig.versionCode)
.setVersionName(projectConfig.versionName)
.setSourcePath(projectDir)
.setIcon(projectConfig.icon?.let { File(projectDir, it).path })
}
}
private inner class ManifestEditorWithAuthorities(manifestInputStream: InputStream?) : ManifestEditor(manifestInputStream) {
override fun onAttr(attr: AxmlWriter.Attr) {
attr.apply {
if (!mProjectConfig.launchConfig.isSplashVisible && mSplashThemeId != 0 && value == mSplashThemeId) {
value = mNoSplashThemeId
}
if (name.data == "authorities" && value is StringItem) {
(value as StringItem).data = "${mAppConfig.packageName}.fileprovider"
(value as StringItem).data = "${mProjectConfig.packageName}.fileprovider"
} else {
super.onAttr(this)
}
@@ -401,11 +356,11 @@ open class ApkBuilder(apkInputStream: InputStream?, private val outApkFile: File
}
override fun isPermissionRequired(permissionName: String): Boolean {
return mAppConfig.permissions.contains(permissionName)
return mProjectConfig.permissions.contains(permissionName)
}
}
private fun copyLibrariesByConfig(config: AppConfig) {
private fun copyLibrariesByConfig(config: ProjectConfig) {
// @Hint by SuperMonster003 on Dec 11, 2023.
// ! The list contains only abi names not matching the canonical name itself.
@@ -461,6 +416,7 @@ open class ApkBuilder(apkInputStream: InputStream?, private val outApkFile: File
}
@Suppress("SpellCheckingInspection")
enum class Libs(
@JvmField val label: String,
@JvmField val aliases: List<String> = emptyList(),

View File

@@ -10,10 +10,11 @@ import android.os.Handler
import android.os.Looper
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isVisible
import org.autojs.autojs.inrt.autojs.AutoJs
import org.autojs.autojs.inrt.launch.GlobalProjectLauncher
import org.autojs.autojs.project.ProjectConfig
import org.autojs.autojs.ui.splash.SplashActivity.Companion.INIT_TIMEOUT
import org.autojs.autojs6.R
import org.autojs.autojs6.databinding.ActivitySplashInrtBinding
import kotlin.concurrent.thread
@@ -24,14 +25,29 @@ import kotlin.concurrent.thread
class SplashActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
setContentView(R.layout.activity_splash_inrt)
val binding = ActivitySplashInrtBinding.inflate(layoutInflater)
binding.slug.typeface = Typeface.createFromAsset(assets, "roboto_medium.ttf")
val binding = ActivitySplashInrtBinding.inflate(layoutInflater).apply {
setContentView(root)
}
val handler = Handler(Looper.myLooper()!!)
val projectConfig = ProjectConfig.fromAssets(this, ProjectConfig.configFileOfDir("project"))
var timeout = INIT_TIMEOUT
if (!projectConfig.launchConfig.isSplashVisible) {
timeout = 0L
} else {
handler.post {
binding.slug.typeface = Typeface.createFromAsset(assets, "roboto_medium.ttf")
binding.slug.isVisible = true
binding.icon.isVisible = true
}
}
if (!Pref.isFirstUsing) {
main()
} else {
Handler(Looper.myLooper()!!).postDelayed({ this@SplashActivity.main() }, INIT_TIMEOUT)
handler.postDelayed({ this@SplashActivity.main() }, timeout)
}
super.onCreate(savedInstanceState)
}

View File

@@ -36,7 +36,7 @@ open class AssetsProjectLauncher(private val mAssetsProjectDir: String, private
fun launch(activity: Activity) {
// 如果需要隐藏日志界面, 则直接运行脚本
if (mProjectConfig.launchConfig.shouldHideLogs() || Pref.shouldHideLogs()) {
if (!mProjectConfig.launchConfig.isLogsVisible || Pref.shouldHideLogs()) {
runScript(activity)
} else {
// 如果不隐藏日志界面

View File

@@ -6,13 +6,13 @@ import java.util.zip.CRC32;
public class BuildInfo {
@SerializedName("build_time")
@SerializedName(value = "buildTime", alternate = {"build_time"})
private long mBuildTime;
@SerializedName("build_id")
@SerializedName(value = "buildId", alternate = {"build_id"})
private String mBuildId;
@SerializedName("build_number")
@SerializedName(value = "buildNumber", alternate = {"build_number"})
private long mBuildNumber;
public long getBuildNumber() {

View File

@@ -1,21 +1,57 @@
package org.autojs.autojs.project;
package org.autojs.autojs.project
import com.google.gson.annotations.SerializedName;
import com.google.gson.annotations.SerializedName
import org.autojs.autojs.annotation.SerializedNameCompatible
import org.autojs.autojs.annotation.SerializedNameCompatible.With
import org.autojs.autojs.app.GlobalAppContext
import org.autojs.autojs6.R
/**
* Created by Stardust on Jan 25, 2018.
* Modified by SuperMonster003 as of Jan 15, 2025.
* Created by SuperMonster003 on Jan 15, 2025.
*/
public class LaunchConfig {
class LaunchConfig {
@SerializedName("hideLogs")
private boolean mHideLogs = false;
@Transient
private val mContext = GlobalAppContext.get()
public boolean shouldHideLogs() {
return mHideLogs;
}
@SerializedName("logsVisible")
@field:SerializedNameCompatible(
With(value = "showLogs"),
With(value = "hideLogs", target = ["AutoJs4", "AutoX"], isReversed = true),
With(value = "displayLogs"),
)
var isLogsVisible = true
public void setHideLogs(boolean hideLogs) {
mHideLogs = hideLogs;
}
@SerializedName("splashVisible")
@field:SerializedNameCompatible(
With(value = "showSplash"),
With(value = "hideSplash", isReversed = true),
With(value = "displaySplash", target = ["AutoX"]),
)
var isSplashVisible = true
@SerializedName("launcherVisible")
@field:SerializedNameCompatible(
With(value = "showLauncher"),
With(value = "hideLauncher", target = ["AutoX"], isReversed = true),
With(value = "displayLauncher"),
)
var isLauncherVisible = true
@SerializedName("slug")
@field:SerializedNameCompatible(
With(value = "slugText"),
With(value = "splashText", target = ["AutoX"]),
)
var slug = mContext.getString(R.string.text_powered_by_autojs)
@SerializedName("permissions")
@field:SerializedNameCompatible(
With(value = "permission"),
With(value = "permissionList"),
)
var permissions = emptyList<String>()
}

View File

@@ -1,12 +1,15 @@
package org.autojs.autojs.project;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.annotations.SerializedName;
import org.autojs.autojs.apkbuilder.keystore.KeyStore;
import org.autojs.autojs.model.explorer.ExplorerPage;
import org.autojs.autojs.pio.PFiles;
import org.autojs.autojs.util.JsonUtils;
@@ -20,6 +23,7 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -30,7 +34,9 @@ public class ProjectConfig {
public static final String CONFIG_FILE_NAME = "project.json";
private static final Gson sGson = new GsonBuilder().setPrettyPrinting().create();
private static final Gson sGson = new GsonBuilder()
.setPrettyPrinting()
.create();
@SerializedName("name")
private String mName;
@@ -52,13 +58,15 @@ public class ProjectConfig {
private List<String> mAssets = new ArrayList<>();
@SerializedName("launchConfig")
private LaunchConfig mLaunchConfig;
private LaunchConfig mLaunchConfig = new LaunchConfig();
@SerializedName("build")
private BuildInfo mBuildInfo = new BuildInfo();
@SerializedName("icon")
private String mIcon;
private String mIconPath;
private transient Callable<Bitmap> mIconBitmapGetter;
@Nullable
@SerializedName(value = "abis", alternate = {"abi", "abiList"})
@@ -68,12 +76,27 @@ public class ProjectConfig {
@SerializedName(value = "libs", alternate = {"lib", "libList"})
private List<String> mLibs = new ArrayList<>();
@SerializedName("permissions")
private List<String> mPermissions = new ArrayList<>();
@SerializedName("signatureSchemes")
private String mSignatureSchemes = "V1 + V2";
@Nullable
private transient KeyStore mKeyStore = null;
@SerializedName("scripts")
private final Map<String, ScriptConfig> mScriptConfigs = new HashMap<>();
@SerializedName(value = "useFeatures", alternate = {"useFeature", "useFeatureList"})
private List<String> mFeatures = new ArrayList<>();
@SerializedName("ignoredDirs")
private final List<File> mIgnoredDirs = new ArrayList<>();
@Nullable
private transient String mSourcePath = null;
public static ProjectConfig fromJson(String json) {
if (json == null) {
return null;
@@ -117,7 +140,7 @@ public class ProjectConfig {
return fromJson(fileContents);
} catch (Exception e1) {
if (fileContents == null) return null;
try {
try {
return fromJson(JsonUtils.repairJson(fileContents));
} catch (Exception e2) {
return tryReadCrucialData(fileContents, path);
@@ -126,10 +149,9 @@ public class ProjectConfig {
}
private static ProjectConfig tryReadCrucialData(String s, String jsonFilePath) {
ProjectConfig config = new ProjectConfig();
BuildInfo buildInfo = new BuildInfo();
ScriptConfig scriptConfig = new ScriptConfig();
ProjectConfig projectConfig = new ProjectConfig();
LaunchConfig launchConfig = new LaunchConfig();
BuildInfo buildInfo = new BuildInfo();
Pattern namePattern = Pattern.compile(stringPattern("name"));
Pattern versionNamePattern = Pattern.compile(stringPattern("versionName"));
@@ -147,40 +169,47 @@ public class ProjectConfig {
Pattern buildNumberPattern = Pattern.compile(numberPattern("buildNumber"));
Pattern buildIdPattern = Pattern.compile(stringPattern("buildId"));
Pattern launchConfigPattern = Pattern.compile(booleanPattern("hideLogs"));
Pattern launchConfigHideLogsPattern = Pattern.compile(booleanPattern("hideLogs"));
Pattern launchConfigLogsVisiblePattern = Pattern.compile(booleanPattern("logsVisible"));
Pattern scriptsUiModePattern = Pattern.compile(booleanPattern("uiMode"));
Pattern launchConfigDisplaySplashPattern = Pattern.compile(booleanPattern("displaySplash"));
Pattern launchConfigSplashVisiblePattern = Pattern.compile(booleanPattern("splashVisible"));
setFieldIfMatches(namePattern, s, config::setName);
setFieldIfMatches(versionNamePattern, s, config::setVersionName);
setFieldForIntIfMatches(versionCodePattern, s, config::setVersionCode);
setFieldIfMatches(packageNamePattern, s, config::setPackageName);
setFieldIfMatches(mainPattern, s, config::setMainScriptFile);
setFieldIfMatches(iconPattern, s, config::setIcon);
setFieldIfMatches(namePattern, s, projectConfig::setName);
setFieldIfMatches(versionNamePattern, s, projectConfig::setVersionName);
setFieldForIntIfMatches(versionCodePattern, s, projectConfig::setVersionCode);
setFieldIfMatches(packageNamePattern, s, projectConfig::setPackageName);
setFieldIfMatches(mainPattern, s, projectConfig::setMainScriptFile);
setFieldIfMatches(iconPattern, s, projectConfig::setIconPath);
setListIfMatches(assetsPattern, s, config::setAssets);
setListIfMatches(abisPattern, s, config::setAbis);
setListIfMatches(libsPattern, s, config::setLibs);
setListIfMatches(useFeaturesPattern, s, config::setFeatures);
setListIfMatches(assetsPattern, s, projectConfig::setAssets);
setListIfMatches(abisPattern, s, projectConfig::setAbis);
setListIfMatches(libsPattern, s, projectConfig::setLibs);
setListIfMatches(useFeaturesPattern, s, projectConfig::setFeatures);
setFieldForIntIfMatches(buildTimePattern, s, buildInfo::setBuildTime);
setFieldForIntIfMatches(buildNumberPattern, s, buildInfo::setBuildNumber);
setFieldIfMatches(buildIdPattern, s, buildInfo::setBuildId);
setFieldForBooleanIfMatches(launchConfigPattern, s, launchConfig::setHideLogs);
setFieldForBooleanIfMatches(launchConfigHideLogsPattern, s, value -> launchConfig.setLogsVisible(!value));
setFieldForBooleanIfMatches(launchConfigLogsVisiblePattern, s, launchConfig::setLogsVisible); /* 优先. */
setFieldForBooleanIfMatches(scriptsUiModePattern, s, scriptConfig::setUiMode);
setFieldForBooleanIfMatches(launchConfigDisplaySplashPattern, s, launchConfig::setSplashVisible);
setFieldForBooleanIfMatches(launchConfigSplashVisiblePattern, s, launchConfig::setSplashVisible); /* 优先. */
if (config.getName() == null || config.getName().isBlank()) {
if (projectConfig.getName() == null || projectConfig.getName().isBlank()) {
if (jsonFilePath.endsWith(CONFIG_FILE_NAME)) {
File parentFile = new File(jsonFilePath).getParentFile();
if (parentFile != null) {
config.setName(parentFile.getName());
projectConfig.setName(parentFile.getName());
}
}
}
return config;
projectConfig.setBuildInfo(buildInfo);
projectConfig.setLaunchConfig(launchConfig);
return projectConfig;
}
@NotNull
@@ -350,13 +379,10 @@ public class ProjectConfig {
}
public LaunchConfig getLaunchConfig() {
if (mLaunchConfig == null) {
mLaunchConfig = new LaunchConfig();
}
return mLaunchConfig;
}
public void setLaunchConfig(LaunchConfig launchConfig) {
public void setLaunchConfig(@NonNull LaunchConfig launchConfig) {
mLaunchConfig = launchConfig;
}
@@ -364,12 +390,23 @@ public class ProjectConfig {
return sGson.toJson(this);
}
public String getIcon() {
return mIcon;
public String getIconPath() {
return mIconPath;
}
public void setIcon(String icon) {
mIcon = icon;
public ProjectConfig setIconPath(String iconPath) {
mIconPath = iconPath;
mIconBitmapGetter = () -> BitmapFactory.decodeFile(iconPath);
return this;
}
public Callable<Bitmap> getIconBitmapGetter() {
return mIconBitmapGetter;
}
public ProjectConfig setIconGetter(@Nullable Callable<Bitmap> getter) {
mIconBitmapGetter = getter;
return this;
}
public List<String> getAbis() {
@@ -379,8 +416,9 @@ public class ProjectConfig {
return mAbis;
}
public void setAbis(@Nullable List<String> abis) {
public ProjectConfig setAbis(@Nullable List<String> abis) {
mAbis = abis;
return this;
}
public List<String> getLibs() {
@@ -390,8 +428,9 @@ public class ProjectConfig {
return mLibs;
}
public void setLibs(@Nullable List<String> libs) {
public ProjectConfig setLibs(@Nullable List<String> libs) {
mLibs = libs;
return this;
}
public String getBuildDir() {
@@ -427,4 +466,51 @@ public class ProjectConfig {
config.setFeatures(features);
return config;
}
public List<File> getIgnoredDirs() {
return mIgnoredDirs;
}
public ProjectConfig ignoredDir(File ignoredDir) {
mIgnoredDirs.add(ignoredDir);
return this;
}
@Nullable
public String getSourcePath() {
return mSourcePath;
}
public ProjectConfig setSourcePath(@Nullable String sourcePath) {
mSourcePath = sourcePath;
return this;
}
public List<String> getPermissions() {
return mPermissions;
}
public ProjectConfig setPermissions(List<String> permissions) {
mPermissions = permissions;
return this;
}
public String getSignatureSchemes() {
return mSignatureSchemes;
}
public ProjectConfig setSignatureSchemes(String signatureSchemes) {
mSignatureSchemes = signatureSchemes;
return this;
}
@Nullable
public KeyStore getKeyStore() {
return mKeyStore;
}
public ProjectConfig setKeyStore(@Nullable KeyStore keyStore) {
mKeyStore = keyStore;
return this;
}
}

View File

@@ -2,17 +2,14 @@ package org.autojs.autojs.project
import com.google.gson.annotations.SerializedName
data class ScriptConfig(
@SerializedName("useFeatures") var features: List<String>,
@SerializedName("uiMode") var uiMode: Boolean
data class ScriptConfig @JvmOverloads constructor(
@SerializedName("useFeatures") var features: List<String> = emptyList(),
) {
constructor() : this(emptyList(), false)
fun hasFeature(feature: String): Boolean {
return features.contains(feature)
}
fun hasFeature(feature: String) = features.contains(feature)
companion object {
const val FEATURE_CONTINUATION = "continuation"
}
}

View File

@@ -160,18 +160,18 @@ public class BuildActivity extends BaseActivity implements ApkBuilder.ProgressCa
put("moe.shizuku.manager.permission.API_V23", R.string.text_permission_shizuku);
}};
EditText mSourcePath;
View mSourcePathContainer;
EditText mOutputPath;
EditText mAppName;
EditText mPackageName;
TextInputLayout mPackageNameParent;
EditText mVersionName;
TextInputLayout mVersionNameParent;
EditText mVersionCode;
TextInputLayout mVersionCodeParent;
ImageView mIcon;
LinearLayout mAppConfig;
EditText mSourcePathView;
View mSourcePathContainerView;
EditText mOutputPathView;
EditText mAppNameView;
EditText mPackageNameView;
TextInputLayout mPackageNameParentView;
EditText mVersionNameView;
TextInputLayout mVersionNameParentView;
EditText mVersionCodeView;
TextInputLayout mVersionCodeParentView;
ImageView mIconView;
LinearLayout mAppConfigView;
private ProjectConfig mProjectConfig;
private MaterialDialog mProgressDialog;
@@ -209,24 +209,24 @@ public class BuildActivity extends BaseActivity implements ApkBuilder.ProgressCa
ActivityBuildBinding binding = ActivityBuildBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
mSourcePath = binding.sourcePath;
mSourcePath.setOnKeyListener((v, keyCode, event) -> {
mSourcePathView = binding.sourcePath;
mSourcePathView.setOnKeyListener((v, keyCode, event) -> {
if (keyCode == KeyEvent.KEYCODE_ENTER) {
if (event.getAction() == KeyEvent.ACTION_UP) {
mOutputPath.requestFocus();
mOutputPathView.requestFocus();
}
return true;
}
return false;
});
mSourcePathContainer = binding.sourcePathContainer;
mSourcePathContainerView = binding.sourcePathContainer;
mOutputPath = binding.outputPath;
mOutputPath.setOnKeyListener((v, keyCode, event) -> {
mOutputPathView = binding.outputPath;
mOutputPathView.setOnKeyListener((v, keyCode, event) -> {
if (keyCode == KeyEvent.KEYCODE_ENTER) {
if (event.getAction() == KeyEvent.ACTION_UP) {
TextView nextField = (TextView) mOutputPath.focusSearch(View.FOCUS_DOWN);
TextView nextField = (TextView) mOutputPathView.focusSearch(View.FOCUS_DOWN);
if (nextField != null) {
nextField.requestFocus();
}
@@ -236,30 +236,30 @@ public class BuildActivity extends BaseActivity implements ApkBuilder.ProgressCa
return false;
});
mAppName = binding.appName;
mAppNameView = binding.appName;
mPackageName = binding.packageName;
mPackageName.setOnKeyListener((v, keyCode, event) -> {
mPackageNameView = binding.packageName;
mPackageNameView.setOnKeyListener((v, keyCode, event) -> {
if (keyCode == KeyEvent.KEYCODE_ENTER) {
if (event.getAction() == KeyEvent.ACTION_UP) {
mVersionName.requestFocus();
mVersionNameView.requestFocus();
}
return true;
}
return false;
});
mPackageNameParent = binding.packageNameParent;
mPackageNameParentView = binding.packageNameParent;
mVersionName = binding.versionName;
mVersionNameParent = binding.versionNameParent;
mVersionCode = binding.versionCode;
mVersionCodeParent = binding.versionCodeParent;
mVersionNameView = binding.versionName;
mVersionNameParentView = binding.versionNameParent;
mVersionCodeView = binding.versionCode;
mVersionCodeParentView = binding.versionCodeParent;
mIcon = binding.appIcon;
mIcon.setVisibility(View.INVISIBLE);
mIcon.setOnClickListener(v -> selectIcon());
mIconView = binding.appIcon;
mIconView.setVisibility(View.INVISIBLE);
mIconView.setOnClickListener(v -> selectIcon());
mAppConfig = binding.appConfig;
mAppConfigView = binding.appConfig;
mFlexboxAbis = binding.flexboxAbis;
initAbisChildren();
@@ -515,8 +515,8 @@ public class BuildActivity extends BaseActivity implements ApkBuilder.ProgressCa
if (dir != null && dir.startsWith(getFilesDir().getPath())) {
dir = WorkingDirectoryUtils.getPath();
}
mOutputPath.setText(dir);
mAppName.setText(file.getSimplifiedName());
mOutputPathView.setText(dir);
mAppNameView.setText(file.getSimplifiedName());
Observable.fromCallable(() -> {
String packageNameSuffix = generatePackageNameSuffix(file);
@@ -525,34 +525,34 @@ public class BuildActivity extends BaseActivity implements ApkBuilder.ProgressCa
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(packageName -> {
mPackageName.setText(packageName);
mPackageNameParent.setHint(R.string.text_package_name);
mPackageNameView.setText(packageName);
mPackageNameParentView.setHint(R.string.text_package_name);
SimpleVersionInfo nextVersionInfo = AppUtils.generateNextVersionInfo(packageName);
if (nextVersionInfo != null) {
mVersionName.setText(nextVersionInfo.versionName);
mVersionCode.setText(nextVersionInfo.versionCodeString);
mVersionNameView.setText(nextVersionInfo.versionName);
mVersionCodeView.setText(nextVersionInfo.versionCodeString);
} else {
mVersionName.setText(R.string.default_build_apk_version_name);
mVersionCode.setText(R.string.default_build_apk_version_code);
mVersionNameView.setText(R.string.default_build_apk_version_name);
mVersionCodeView.setText(R.string.default_build_apk_version_code);
}
mVersionNameParent.setHint(R.string.text_version_name);
mVersionCodeParent.setHint(R.string.text_version_code);
mVersionNameParentView.setHint(R.string.text_version_name);
mVersionCodeParentView.setHint(R.string.text_version_code);
Drawable iconDrawable = AppUtils.getInstalledAppIcon(packageName);
if (iconDrawable != null) {
mIcon.setImageDrawable(iconDrawable);
mIconView.setImageDrawable(iconDrawable);
mIsDefaultIcon = false;
}
mIcon.setVisibility(View.VISIBLE);
mIconView.setVisibility(View.VISIBLE);
}, throwable -> {
mPackageName.setText(getString(R.string.format_default_package_name, file.getSimplifiedName().toLowerCase(Language.getPrefLanguage().getLocale())));
mVersionName.setText(R.string.default_build_apk_version_name);
mVersionCode.setText(R.string.default_build_apk_version_code);
mPackageNameParent.setHint(R.string.text_package_name);
mVersionNameParent.setHint(R.string.text_version_name);
mVersionCodeParent.setHint(R.string.text_version_code);
mIcon.setVisibility(View.VISIBLE);
mPackageNameView.setText(getString(R.string.format_default_package_name, file.getSimplifiedName().toLowerCase(Language.getPrefLanguage().getLocale())));
mVersionNameView.setText(R.string.default_build_apk_version_name);
mVersionCodeView.setText(R.string.default_build_apk_version_code);
mPackageNameParentView.setHint(R.string.text_package_name);
mVersionNameParentView.setHint(R.string.text_version_name);
mVersionCodeParentView.setHint(R.string.text_version_code);
mIconView.setVisibility(View.VISIBLE);
});
setSource(file);
@@ -579,7 +579,7 @@ public class BuildActivity extends BaseActivity implements ApkBuilder.ProgressCa
}
void selectSourceFilePath() {
String initialDir = new File(mSourcePath.getText().toString()).getParent();
String initialDir = new File(mSourcePathView.getText().toString()).getParent();
new FileChooserDialogBuilder(this)
.title(R.string.text_source_file_path)
.dir(EnvironmentUtils.getExternalStoragePath(),
@@ -590,7 +590,7 @@ public class BuildActivity extends BaseActivity implements ApkBuilder.ProgressCa
private void setSource(File file) {
if (!file.isDirectory()) {
mSourcePath.setText(file.getPath());
mSourcePathView.setText(file.getPath());
return;
}
mProjectConfig = ProjectConfig.fromProjectDir(file.getPath());
@@ -598,20 +598,20 @@ public class BuildActivity extends BaseActivity implements ApkBuilder.ProgressCa
return;
}
mIsProjectLevelBuilding = true;
mOutputPath.setText(new File(mSource, mProjectConfig.getBuildDir()).getPath());
mAppConfig.setVisibility(View.GONE);
mSourcePathContainer.setVisibility(View.GONE);
mOutputPathView.setText(new File(mSource, mProjectConfig.getBuildDir()).getPath());
mAppConfigView.setVisibility(View.GONE);
mSourcePathContainerView.setVisibility(View.GONE);
}
void selectOutputDirPath() {
String initialDir = new File(mOutputPath.getText().toString()).exists()
? mOutputPath.getText().toString()
String initialDir = new File(mOutputPathView.getText().toString()).exists()
? mOutputPathView.getText().toString()
: WorkingDirectoryUtils.getPath();
new FileChooserDialogBuilder(this)
.title(R.string.text_output_apk_path)
.dir(initialDir)
.chooseDir()
.singleChoice(dir -> mOutputPath.setText(dir.getPath()))
.singleChoice(dir -> mOutputPathView.setText(dir.getPath()))
.show();
}
@@ -633,14 +633,14 @@ public class BuildActivity extends BaseActivity implements ApkBuilder.ProgressCa
private boolean checkInputs() {
if (mIsProjectLevelBuilding) {
return checkNotEmpty(mOutputPath);
return checkNotEmpty(mOutputPathView);
}
return checkNotEmpty(mSourcePath)
& checkNotEmpty(mOutputPath)
& checkNotEmpty(mAppName)
& checkNotEmpty(mVersionCode)
& checkNotEmpty(mVersionName)
& checkPackageNameValid(mPackageName);
return checkNotEmpty(mSourcePathView)
& checkNotEmpty(mOutputPathView)
& checkNotEmpty(mAppNameView)
& checkNotEmpty(mVersionCodeView)
& checkNotEmpty(mVersionNameView)
& checkPackageNameValid(mPackageNameView);
}
private boolean checkAbis() {
@@ -773,12 +773,12 @@ public class BuildActivity extends BaseActivity implements ApkBuilder.ProgressCa
@SuppressWarnings("ResultOfMethodCallIgnored")
@SuppressLint("CheckResult")
private void doBuildingApk() {
ApkBuilder.AppConfig appConfig = createAppConfig();
ProjectConfig projectConfig = determineProjectConfig();
File tmpDir = new File(getCacheDir(), "build/");
File outApk = new File(mOutputPath.getText().toString(),
String.format("%s_v%s.apk", appConfig.getAppName(), appConfig.getVersionName()));
File outApk = new File(mOutputPathView.getText().toString(),
String.format("%s_v%s.apk", projectConfig.getName(), projectConfig.getVersionName()));
showProgressDialog();
Observable.fromCallable(() -> callApkBuilder(tmpDir, outApk, appConfig))
Observable.fromCallable(() -> callApkBuilder(tmpDir, outApk, projectConfig))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(apkBuilder -> {
@@ -790,32 +790,33 @@ public class BuildActivity extends BaseActivity implements ApkBuilder.ProgressCa
}, this::onBuildFailed);
}
private ApkBuilder.AppConfig createAppConfig() {
private ProjectConfig determineProjectConfig() {
ArrayList<String> abis = collectCheckedItems(mFlexboxAbis);
ArrayList<String> libs = collectCheckedItems(mFlexboxLibs);
ArrayList<String> permissions = collectCheckedItems(mFlexboxPermissions);
ApkBuilder.AppConfig appConfig = mProjectConfig != null
? ApkBuilder.AppConfig.fromProjectConfig(mSource, mProjectConfig)
: new ApkBuilder.AppConfig()
.setAppName(mAppName.getText().toString())
.setSourcePath(mSourcePath.getText().toString())
.setPackageName(mPackageName.getText().toString())
.setVersionName(mVersionName.getText().toString())
.setVersionCode(Integer.parseInt(mVersionCode.getText().toString()))
.setIcon(mIsDefaultIcon ? null : () -> BitmapUtils.drawableToBitmap(mIcon.getDrawable()));
appConfig.setAbis(abis);
appConfig.setLibs(libs);
appConfig.setSignatureSchemes(mSignatureSchemes.getSelectedItem().toString());
if (mVerifiedKeyStores.getSelectedItemPosition() > 0) {
appConfig.setKeyStore((KeyStore) mVerifiedKeyStores.getSelectedItem());
ProjectConfig projectConfig;
if (mProjectConfig != null) {
projectConfig = mProjectConfig
.ignoredDir(new File(mSource, mProjectConfig.getBuildDir()))
.setSourcePath(mSource)
.setIconPath(mProjectConfig.getIconPath() == null ? null : new File(mSource, mProjectConfig.getIconPath()).getPath());
} else {
appConfig.setKeyStore(null);
projectConfig = new ProjectConfig()
.setName(mAppNameView.getText().toString())
.setSourcePath(mSourcePathView.getText().toString())
.setPackageName(mPackageNameView.getText().toString())
.setVersionName(mVersionNameView.getText().toString())
.setVersionCode(Integer.parseInt(mVersionCodeView.getText().toString()))
.setIconGetter(mIsDefaultIcon ? null : () -> BitmapUtils.drawableToBitmap(mIconView.getDrawable()));
}
appConfig.setPermissions(permissions);
return appConfig;
return projectConfig
.setAbis(abis)
.setLibs(libs)
.setKeyStore(mVerifiedKeyStores.getSelectedItemPosition() > 0 ? (KeyStore) mVerifiedKeyStores.getSelectedItem() : null)
.setSignatureSchemes(mSignatureSchemes.getSelectedItem().toString())
.setPermissions(permissions);
}
@NotNull
@@ -843,12 +844,12 @@ public class BuildActivity extends BaseActivity implements ApkBuilder.ProgressCa
return libs;
}
private ApkBuilder callApkBuilder(File tmpDir, File outApk, ApkBuilder.AppConfig appConfig) throws Exception {
private ApkBuilder callApkBuilder(File tmpDir, File outApk, ProjectConfig projectConfig) throws Exception {
InputStream templateApk = getAssets().open(TEMPLATE_APK_NAME);
return new ApkBuilder(templateApk, outApk, tmpDir.getPath())
.setProgressCallback(BuildActivity.this)
.prepare()
.withConfig(appConfig)
.withConfig(projectConfig)
.build()
.sign()
.cleanWorkspace();
@@ -920,7 +921,7 @@ public class BuildActivity extends BaseActivity implements ApkBuilder.ProgressCa
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(drawable -> {
mIcon.setImageDrawable(drawable);
mIconView.setImageDrawable(drawable);
mIsDefaultIcon = false;
}, Throwable::printStackTrace);
}

View File

@@ -122,12 +122,12 @@ class ProjectConfigActivity : BaseActivity() {
} else {
mAppName.setText(config.name)
setToolbarAsBack(config.name)
mVersionCode.setText(config.versionCode.toString())
mVersionCode.setText(config.versionCode.coerceAtLeast(0).toString())
mPackageName.setText(config.packageName)
mVersionName.setText(config.versionName)
mMainFileName.setText(config.mainScriptFile)
mProjectLocationWrapper.visibility = View.GONE
config.icon?.let { icon ->
config.iconPath?.let { icon ->
File(mDirectory, icon).takeIf { it.exists() }?.let { iconFile ->
Glide.with(this)
.load(iconFile)
@@ -259,7 +259,7 @@ class ProjectConfigActivity : BaseActivity() {
private fun saveIcon(b: Bitmap): Observable<String> {
return Observable.just(b)
.map { bitmap: Bitmap ->
var iconPath = mProjectConfig!!.icon
var iconPath = mProjectConfig!!.iconPath
if (iconPath == null) {
iconPath = "res/logo.png"
}
@@ -272,7 +272,7 @@ class ProjectConfigActivity : BaseActivity() {
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnNext { iconPath: String? -> mProjectConfig!!.icon = iconPath }
.doOnNext { iconPath: String? -> mProjectConfig!!.iconPath = iconPath }
}
override fun onDestroy() {

View File

@@ -1,27 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="128dp"
android:layout_gravity="center|bottom"
android:layout_marginBottom="16dp"
android:gravity="center"
android:orientation="vertical">
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="128dp"
android:layout_gravity="center|bottom"
android:layout_marginBottom="16dp"
android:gravity="center"
android:orientation="vertical">
<ImageView
android:layout_width="72dp"
android:layout_height="72dp"
android:layout_gravity="center"
android:src="@mipmap/ic_launcher" />
android:id="@+id/icon"
android:visibility="gone"
tools:visibility="visible"
android:layout_width="72dp"
android:layout_height="72dp"
android:layout_gravity="center"
android:src="@mipmap/ic_launcher" />
<TextView
android:id="@+id/slug"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="20dp"
android:text="@string/text_powered_by_autojs"
android:textColor="@color/day_night_full"
android:textSize="16sp" />
android:id="@+id/slug"
android:visibility="gone"
tools:visibility="visible"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="20dp"
android:text="@string/text_powered_by_autojs"
android:textColor="@color/day_night_full"
android:textSize="16sp" />
</LinearLayout>