diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 933f8797..4ea47673 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -193,6 +193,14 @@ dependencies /* Unclassified */ {
// Toaster
implementation("com.github.getActivity:Toaster:12.6")
implementation("com.github.getActivity:EasyWindow:10.3")
+
+ // apksigner
+ implementation("com.github.TimScriptov:apksigner:1.2.0")
+
+ // room
+ implementation("androidx.room:room-runtime:2.6.1")
+ implementation("androidx.room:room-ktx:2.6.1")
+ ksp("androidx.room:room-compiler:2.6.1")
}
dependencies /* MIME */ {
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 4845d52d..5067b3c6 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -256,6 +256,9 @@
+
+
= emptyList()
private set
+ var keyStore: KeyStore? = null
+ private set
+ var signatureSchemes: String = "V1 + V2"
+ private set
+ var permissions: List = emptyList()
+ private set
fun ignoreDir(dir: File) = also { ignoredDirs.add(dir) }
@@ -342,6 +387,12 @@ open class ApkBuilder(apkInputStream: InputStream?, private val outApkFile: File
fun setLibs(libs: List) = also { this.libs = libs }
+ fun setKeyStore(keyStore: KeyStore?) = also { this.keyStore = keyStore }
+
+ fun setSignatureSchemes(signatureSchemes: String) = also { this.signatureSchemes = signatureSchemes }
+
+ fun setPermissions(permissions: List) = also { this.permissions = permissions }
+
companion object {
@JvmStatic
fun fromProjectConfig(projectDir: String?, projectConfig: ProjectConfig) = AppConfig()
@@ -365,6 +416,10 @@ open class ApkBuilder(apkInputStream: InputStream?, private val outApkFile: File
}
}
}
+
+ override fun isPermissionRequired(permissionName: String): Boolean {
+ return mAppConfig.permissions.contains(permissionName)
+ }
}
private fun copyLibrariesByConfig(config: AppConfig) {
diff --git a/app/src/main/java/org/autojs/autojs/apkbuilder/ManifestEditor.java b/app/src/main/java/org/autojs/autojs/apkbuilder/ManifestEditor.java
index 73e5376b..c89317be 100644
--- a/app/src/main/java/org/autojs/autojs/apkbuilder/ManifestEditor.java
+++ b/app/src/main/java/org/autojs/autojs/apkbuilder/ManifestEditor.java
@@ -88,6 +88,10 @@ public void onAttr(AxmlWriter.Attr attr) {
}
}
+ public boolean isPermissionRequired(String permissionName) {
+ return true;
+ }
+
private class MutableAxmlWriter extends AxmlWriter {
private class MutableNodeImpl extends AxmlWriter.NodeImpl {
@@ -97,6 +101,9 @@ private class MutableNodeImpl extends AxmlWriter.NodeImpl {
@Override
protected void onAttr(AxmlWriter.Attr a) {
+ if ("uses-permission".equals(this.name.data) && "name".equals(a.name.data) && a.value instanceof StringItem) {
+ this.ignore = !ManifestEditor.this.isPermissionRequired(((StringItem) a.value).data);
+ }
ManifestEditor.this.onAttr(a);
super.onAttr(a);
}
diff --git a/app/src/main/java/org/autojs/autojs/apkbuilder/keystore/AESUtils.kt b/app/src/main/java/org/autojs/autojs/apkbuilder/keystore/AESUtils.kt
new file mode 100644
index 00000000..945e7953
--- /dev/null
+++ b/app/src/main/java/org/autojs/autojs/apkbuilder/keystore/AESUtils.kt
@@ -0,0 +1,71 @@
+package org.autojs.autojs.apkbuilder.keystore
+
+import android.util.Base64
+import javax.crypto.Cipher
+import javax.crypto.SecretKey
+import javax.crypto.spec.GCMParameterSpec
+import android.security.keystore.KeyGenParameterSpec
+import android.security.keystore.KeyProperties
+import java.security.KeyStore
+import javax.crypto.KeyGenerator
+
+object AESUtils {
+
+ private const val TRANSFORMATION = "AES/GCM/NoPadding"
+ private const val TAG_LENGTH = 128
+
+ private const val KEY_ALIAS = "autojs6_key_store_aes_key"
+
+ private fun getKey(): SecretKey {
+ val keyStore = KeyStore.getInstance("AndroidKeyStore")
+ keyStore.load(null)
+
+ val key = keyStore.getKey(KEY_ALIAS, null)
+ if (key != null) {
+ return key as SecretKey
+ }
+
+ val keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore")
+ val keyGenParameterSpec = KeyGenParameterSpec.Builder(KEY_ALIAS, KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
+ .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
+ .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
+ .build()
+
+ keyGenerator.init(keyGenParameterSpec)
+ return keyGenerator.generateKey()
+ }
+
+ // 加密
+ fun encrypt(data: String): String {
+ val secretKey: SecretKey = getKey()
+ val cipher = Cipher.getInstance(TRANSFORMATION)
+
+ cipher.init(Cipher.ENCRYPT_MODE, secretKey)
+
+ val encryptedData = cipher.doFinal(data.toByteArray())
+ val encryptedBase64 = Base64.encodeToString(encryptedData, Base64.NO_WRAP)
+ val ivBase64 = Base64.encodeToString(cipher.iv, Base64.NO_WRAP)
+
+ return "$ivBase64:$encryptedBase64"
+ }
+
+ // 解密
+ fun decrypt(encryptedData: String): String {
+ val parts = encryptedData.split(":")
+ val ivBase64 = parts[0]
+ val encryptedBase64 = parts[1]
+
+ val iv = Base64.decode(ivBase64, Base64.NO_WRAP)
+ val encrypted = Base64.decode(encryptedBase64, Base64.NO_WRAP)
+
+ val secretKey: SecretKey = getKey()
+ val cipher = Cipher.getInstance(TRANSFORMATION)
+
+ val gcmSpec = GCMParameterSpec(TAG_LENGTH, iv)
+ cipher.init(Cipher.DECRYPT_MODE, secretKey, gcmSpec)
+
+ val decryptedData = cipher.doFinal(encrypted)
+
+ return String(decryptedData)
+ }
+}
diff --git a/app/src/main/java/org/autojs/autojs/apkbuilder/keystore/KeyStore.kt b/app/src/main/java/org/autojs/autojs/apkbuilder/keystore/KeyStore.kt
new file mode 100644
index 00000000..9174d898
--- /dev/null
+++ b/app/src/main/java/org/autojs/autojs/apkbuilder/keystore/KeyStore.kt
@@ -0,0 +1,18 @@
+package org.autojs.autojs.apkbuilder.keystore
+
+import androidx.room.ColumnInfo
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+
+
+@Entity
+data class KeyStore(
+ @PrimaryKey val absolutePath: String, // 密钥库绝对路径
+ @ColumnInfo(name = "filename") val filename: String = "", // 文件名
+ @ColumnInfo(name = "password") val password: String = "", // 密码
+ @ColumnInfo(name = "alias") val alias: String = "", // 别名
+ @ColumnInfo(name = "alias_password") val aliasPassword: String = "", // 别名密码
+ @ColumnInfo(name = "verified") val verified: Boolean = false, // 验证状态
+) {
+ override fun toString(): String = filename
+}
diff --git a/app/src/main/java/org/autojs/autojs/apkbuilder/keystore/KeyStoreDao.kt b/app/src/main/java/org/autojs/autojs/apkbuilder/keystore/KeyStoreDao.kt
new file mode 100644
index 00000000..6fbb5008
--- /dev/null
+++ b/app/src/main/java/org/autojs/autojs/apkbuilder/keystore/KeyStoreDao.kt
@@ -0,0 +1,29 @@
+package org.autojs.autojs.apkbuilder.keystore
+
+import androidx.room.Dao
+import androidx.room.Delete
+import androidx.room.Query
+import androidx.room.Upsert
+
+
+@Dao
+interface KeyStoreDao {
+
+ @Query("SELECT * FROM keystore WHERE absolutePath = :absolutePath LIMIT 1")
+ suspend fun getByAbsolutePath(absolutePath: String): KeyStore?
+
+ @Upsert
+ suspend fun upsert(vararg keyStores: KeyStore)
+
+ @Query("SELECT * FROM keystore")
+ suspend fun getAll(): List
+
+ @Delete
+ suspend fun delete(vararg keyStores: KeyStore)
+
+ @Query("DELETE FROM keystore WHERE absolutePath = :absolutePath")
+ suspend fun deleteByAbsolutePath(absolutePath: String): Int
+
+ @Query("DELETE FROM keystore")
+ suspend fun deleteAll()
+}
diff --git a/app/src/main/java/org/autojs/autojs/apkbuilder/keystore/KeyStoreDatabase.kt b/app/src/main/java/org/autojs/autojs/apkbuilder/keystore/KeyStoreDatabase.kt
new file mode 100644
index 00000000..d6be0605
--- /dev/null
+++ b/app/src/main/java/org/autojs/autojs/apkbuilder/keystore/KeyStoreDatabase.kt
@@ -0,0 +1,28 @@
+package org.autojs.autojs.apkbuilder.keystore
+
+import android.content.Context
+import androidx.room.Database
+import androidx.room.Room
+import androidx.room.RoomDatabase
+
+@Database(entities = [KeyStore::class], version = 1, exportSchema = false)
+abstract class KeyStoreDatabase : RoomDatabase() {
+ abstract fun keyStoreDao(): KeyStoreDao
+
+ companion object {
+ @Volatile
+ private var INSTANCE: KeyStoreDatabase? = null
+
+ fun getDatabase(context: Context): KeyStoreDatabase {
+ return INSTANCE ?: synchronized(this) {
+ val instance = Room.databaseBuilder(
+ context.applicationContext,
+ KeyStoreDatabase::class.java,
+ "keystore-database"
+ ).build()
+ INSTANCE = instance
+ instance
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/org/autojs/autojs/apkbuilder/keystore/KeyStoreRepository.kt b/app/src/main/java/org/autojs/autojs/apkbuilder/keystore/KeyStoreRepository.kt
new file mode 100644
index 00000000..937d5ff8
--- /dev/null
+++ b/app/src/main/java/org/autojs/autojs/apkbuilder/keystore/KeyStoreRepository.kt
@@ -0,0 +1,50 @@
+package org.autojs.autojs.apkbuilder.keystore
+
+import android.content.Context
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
+
+class KeyStoreRepository(context: Context) {
+
+ private var dao: KeyStoreDao
+
+ init {
+ val keyStoreDatabase = KeyStoreDatabase.getDatabase(context)
+ dao = keyStoreDatabase.keyStoreDao()
+ }
+
+ // 获取所有 KeyStore
+ suspend fun getAllKeyStores(): List {
+ return withContext(Dispatchers.IO) {
+ dao.getAll()
+ }
+ }
+
+ // 插入或更新 KeyStore
+ suspend fun upsertKeyStores(vararg keyStores: KeyStore) {
+ withContext(Dispatchers.IO) {
+ dao.upsert(*keyStores)
+ }
+ }
+
+ // 根据绝对路径获取 KeyStore
+ suspend fun getKeyStoreAbsolutePath(absolutePath: String): KeyStore? {
+ return withContext(Dispatchers.IO) {
+ dao.getByAbsolutePath(absolutePath)
+ }
+ }
+
+ // 删除 KeyStore
+ suspend fun deleteKeyStores(vararg keyStores: KeyStore) {
+ withContext(Dispatchers.IO) {
+ dao.delete(*keyStores)
+ }
+ }
+
+ // 删除所有 KeyStore
+ suspend fun deleteAllKeyStores() {
+ withContext(Dispatchers.IO) {
+ dao.deleteAll()
+ }
+ }
+}
diff --git a/app/src/main/java/org/autojs/autojs/core/console/ConsoleView.java b/app/src/main/java/org/autojs/autojs/core/console/ConsoleView.java
index 76181da0..2b447af6 100644
--- a/app/src/main/java/org/autojs/autojs/core/console/ConsoleView.java
+++ b/app/src/main/java/org/autojs/autojs/core/console/ConsoleView.java
@@ -178,7 +178,7 @@ public float getTextSize() {
return /* default text size */ 14;
}
- public void setTextColors(@NotNull Integer[] colors) {
+ public void setTextColors(Integer[] colors) {
Adapter adapter = (Adapter) mLogListRecyclerView.getAdapter();
if (adapter != null) {
adapter.setTextColors(colors);
diff --git a/app/src/main/java/org/autojs/autojs/core/pref/Pref.kt b/app/src/main/java/org/autojs/autojs/core/pref/Pref.kt
index b2be4c82..7fe436b7 100644
--- a/app/src/main/java/org/autojs/autojs/core/pref/Pref.kt
+++ b/app/src/main/java/org/autojs/autojs/core/pref/Pref.kt
@@ -172,6 +172,11 @@ object Pref {
@ScriptInterfaceCompatible
fun getScriptDirPath() = WorkingDirectoryUtils.path
+ @JvmStatic
+ fun getKeyStorePath(): String {
+ return getScriptDirPath() + "/.KeyStore/"
+ }
+
@JvmStatic
fun registerOnSharedPreferenceChangeListener(listener: OnSharedPreferenceChangeListener) {
sPref.registerOnSharedPreferenceChangeListener(listener)
diff --git a/app/src/main/java/org/autojs/autojs/ui/keystore/KeyStoreAdaptor.kt b/app/src/main/java/org/autojs/autojs/ui/keystore/KeyStoreAdaptor.kt
new file mode 100644
index 00000000..ffd58fcb
--- /dev/null
+++ b/app/src/main/java/org/autojs/autojs/ui/keystore/KeyStoreAdaptor.kt
@@ -0,0 +1,82 @@
+package org.autojs.autojs.ui.keystore
+
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import androidx.recyclerview.widget.DiffUtil
+import androidx.recyclerview.widget.ListAdapter
+import androidx.recyclerview.widget.RecyclerView
+import org.autojs.autojs.apkbuilder.keystore.KeyStore
+import org.autojs.autojs6.R
+import org.autojs.autojs6.databinding.ItemKeyStoreBinding
+
+class KeyStoreAdaptor(
+ private val keyStoreAdapterCallback: KeyStoreAdapterCallback,
+) : ListAdapter(KeyStoreDiffCallback()) {
+
+ class KeyStoreDiffCallback : DiffUtil.ItemCallback() {
+ override fun areItemsTheSame(oldItem: KeyStore, newItem: KeyStore): Boolean {
+ return oldItem.absolutePath == newItem.absolutePath
+ }
+
+ override fun areContentsTheSame(oldItem: KeyStore, newItem: KeyStore): Boolean {
+ return oldItem.filename == newItem.filename &&
+ oldItem.password == newItem.password &&
+ oldItem.alias == newItem.alias &&
+ oldItem.aliasPassword == newItem.aliasPassword &&
+ oldItem.verified == newItem.verified
+ }
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): KeyStoreViewHolder {
+ val binding = ItemKeyStoreBinding.inflate(
+ LayoutInflater.from(parent.context), parent, false
+ )
+ return KeyStoreViewHolder(binding).apply {
+ binding.delete.setOnClickListener {
+ if (bindingAdapterPosition != RecyclerView.NO_POSITION) {
+ keyStoreAdapterCallback.onDeleteButtonClicked(getItem(bindingAdapterPosition))
+ }
+ }
+ binding.verify.setOnClickListener {
+ if (bindingAdapterPosition != RecyclerView.NO_POSITION) {
+ keyStoreAdapterCallback.onVerifyButtonClicked(getItem(bindingAdapterPosition))
+ }
+ }
+ itemView.setOnClickListener {
+ if (bindingAdapterPosition != RecyclerView.NO_POSITION) {
+ keyStoreAdapterCallback.onVerifyButtonClicked(getItem(bindingAdapterPosition))
+ }
+ }
+ }
+ }
+
+ override fun onBindViewHolder(holder: KeyStoreViewHolder, position: Int) {
+ holder.bind(getItem(position))
+ }
+
+ inner class KeyStoreViewHolder(private val binding: ItemKeyStoreBinding) :
+ RecyclerView.ViewHolder(binding.root) {
+ fun bind(item: KeyStore) {
+ binding.apply {
+ filename.text = itemView.context.getString(
+ R.string.text_str_colon_space_str_formatter,
+ itemView.context.getString(R.string.text_file_name),
+ item.filename
+ )
+ alias.text = itemView.context.getString(
+ R.string.text_str_colon_space_str_formatter,
+ itemView.context.getString(R.string.text_key_alias),
+ item.alias
+ )
+ verify.setImageResource(
+ if (item.verified) R.drawable.ic_key_store_verified else R.drawable.ic_key_store_unverified
+ )
+ }
+ }
+ }
+
+ interface KeyStoreAdapterCallback {
+ fun onDeleteButtonClicked(keyStore: KeyStore)
+ fun onVerifyButtonClicked(keyStore: KeyStore)
+ }
+}
diff --git a/app/src/main/java/org/autojs/autojs/ui/keystore/ManageKeyStoreActivity.kt b/app/src/main/java/org/autojs/autojs/ui/keystore/ManageKeyStoreActivity.kt
new file mode 100644
index 00000000..66a6e9f9
--- /dev/null
+++ b/app/src/main/java/org/autojs/autojs/ui/keystore/ManageKeyStoreActivity.kt
@@ -0,0 +1,267 @@
+package org.autojs.autojs.ui.keystore
+
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import android.view.Menu
+import android.view.MenuItem
+import android.widget.Toast
+import androidx.annotation.StringRes
+import androidx.core.content.ContextCompat
+import androidx.lifecycle.ViewModelProvider
+import androidx.recyclerview.widget.DefaultItemAnimator
+import androidx.recyclerview.widget.LinearLayoutManager
+import com.afollestad.materialdialogs.DialogAction
+import com.afollestad.materialdialogs.MaterialDialog
+import com.mcal.apksigner.CertCreator
+import com.mcal.apksigner.utils.DistinguishedNameValues
+import com.mcal.apksigner.utils.KeyStoreHelper
+import org.autojs.autojs.apkbuilder.keystore.AESUtils
+import org.autojs.autojs.apkbuilder.keystore.KeyStore
+import org.autojs.autojs.core.pref.Pref
+import org.autojs.autojs6.R
+import org.autojs.autojs6.databinding.ActivityManageKeyStoreBinding
+import org.autojs.autojs.ui.BaseActivity
+import org.autojs.autojs.ui.keystore.NewKeyStoreDialog.NewKeyStoreConfigs
+import org.autojs.autojs.ui.viewmodel.KeyStoreViewModel
+import java.io.File
+import java.io.IOException
+
+
+class ManageKeyStoreActivity : BaseActivity() {
+
+ private lateinit var binding: ActivityManageKeyStoreBinding
+ private lateinit var keyStoreAdapter: KeyStoreAdaptor
+ private lateinit var keyStoreViewModel: KeyStoreViewModel
+
+ companion object {
+ fun startActivity(context: Context) {
+ Intent(context, ManageKeyStoreActivity::class.java).apply {}.also {
+ ContextCompat.startActivity(context, it, null)
+ }
+ }
+ }
+
+ private val newKeyStoreDialogCallback = object : NewKeyStoreDialog.Callback {
+ override fun onConfirmButtonClicked(configs: NewKeyStoreConfigs) {
+ createKeyStore(configs)
+ }
+ }
+
+ private val verifyKeyStoreDialog = object : VerifyKeyStoreDialog.Callback {
+ override fun onVerifyButtonClicked(
+ configs: VerifyKeyStoreDialog.VerifyKeyStoreConfigs, keyStore: KeyStore,
+ ) {
+ verifyKeyStore(configs, keyStore)
+ }
+ }
+
+ private val keyStoreAdapterCallback = object : KeyStoreAdaptor.KeyStoreAdapterCallback {
+ override fun onDeleteButtonClicked(keyStore: KeyStore) {
+ MaterialDialog.Builder(this@ManageKeyStoreActivity)
+ .title(getString(R.string.text_confirm_to_delete))
+ .positiveText(R.string.text_ok).negativeText(R.string.text_cancel)
+ .onPositive { _: MaterialDialog, _: DialogAction ->
+ deleteKeyStore(keyStore)
+ }.show()
+ }
+
+ override fun onVerifyButtonClicked(keyStore: KeyStore) {
+ VerifyKeyStoreDialog(verifyKeyStoreDialog, keyStore).show(supportFragmentManager, null)
+ }
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ binding = ActivityManageKeyStoreBinding.inflate(layoutInflater)
+ setContentView(binding.root)
+
+ setToolbarAsBack(getString(R.string.text_manage_key_store))
+
+ keyStoreViewModel =
+ ViewModelProvider(this, KeyStoreViewModel.Factory(this))[KeyStoreViewModel::class.java]
+
+ binding.fab.setOnClickListener {
+ NewKeyStoreDialog(newKeyStoreDialogCallback).show(supportFragmentManager, null)
+ }
+
+ keyStoreAdapter = KeyStoreAdaptor(keyStoreAdapterCallback)
+ binding.recyclerView.apply {
+ adapter = keyStoreAdapter
+ layoutManager = LinearLayoutManager(this@ManageKeyStoreActivity)
+ itemAnimator = DefaultItemAnimator()
+ }
+ binding.swipeRefreshLayout.setOnRefreshListener {
+ loadKeyStores()
+ binding.recyclerView.postDelayed({
+ binding.swipeRefreshLayout.isRefreshing = false
+ }, 800)
+ }
+
+ keyStoreViewModel.allKeyStores.observe(this@ManageKeyStoreActivity) {
+ keyStoreAdapter.submitList(it.toList())
+ }
+
+ loadKeyStores()
+ }
+
+ override fun onResume() {
+ super.onResume()
+ loadKeyStores()
+ }
+
+ override fun onCreateOptionsMenu(menu: Menu): Boolean {
+ menuInflater.inflate(R.menu.menu_manage_key_store, menu)
+ return true
+ }
+
+ override fun onOptionsItemSelected(item: MenuItem): Boolean {
+ when (item.itemId) {
+ R.id.action_delete_all -> {
+ MaterialDialog.Builder(this@ManageKeyStoreActivity)
+ .title(getString(R.string.text_delete_all))
+ .positiveText(R.string.text_ok).negativeText(R.string.text_cancel)
+ .onPositive { _: MaterialDialog, _: DialogAction ->
+ deleteAllKeyStores()
+ }.show()
+ }
+
+ else -> {}
+ }
+ return super.onOptionsItemSelected(item)
+ }
+
+ private fun loadKeyStores() {
+ val path = File(Pref.getKeyStorePath())
+ if (!path.isDirectory) {
+ return
+ }
+
+ val filteredFiles = path.listFiles { _, name ->
+ name.endsWith(".bks") || name.endsWith(".jks")
+ } ?: emptyArray()
+
+ keyStoreViewModel.updateAllKeyStoresFromFiles(filteredFiles)
+ }
+
+ fun createKeyStore(configs: NewKeyStoreConfigs) {
+ val keyStorePath = File(Pref.getKeyStorePath())
+ keyStorePath.mkdirs()
+ val file = File(keyStorePath, configs.filename)
+
+ val distinguishedNameValues = DistinguishedNameValues().apply {
+ setCommonName(configs.firstAndLastName)
+ setOrganization(configs.organization)
+ setOrganizationalUnit(configs.organizationalUnit)
+ setCountry(configs.countryCode)
+ setState(configs.stateOrProvince)
+ setLocality(configs.cityOrLocality)
+ setStreet(configs.street)
+ }
+
+ try {
+ CertCreator.createKeystoreAndKey(
+ file,
+ configs.password.toCharArray(),
+ "RSA",
+ 2048,
+ configs.alias,
+ configs.aliasPassword.toCharArray(),
+ configs.signatureAlgorithm,
+ configs.validityYears,
+ distinguishedNameValues
+ )
+ val newKeyStore = KeyStore(
+ absolutePath = file.absolutePath,
+ filename = file.name,
+ password = AESUtils.encrypt(configs.password),
+ alias = configs.alias,
+ aliasPassword = AESUtils.encrypt(configs.aliasPassword),
+ verified = true
+ )
+ keyStoreViewModel.upsertKeyStore(newKeyStore)
+ showToast(R.string.text_successfully_created_key_store)
+ } catch (e: IOException) {
+ showToast(getString(R.string.text_failed_to_create_key_store) + " " + e.message)
+ } catch (e: Exception) {
+ showToast(getString(R.string.text_failed_to_create_key_store) + " " + e.message)
+ }
+ }
+
+ fun deleteKeyStore(keyStore: KeyStore) {
+ val keyStorePath = keyStore.absolutePath
+ val keyStoreFile = File(keyStorePath)
+
+ try {
+ if (keyStoreFile.delete()) {
+ keyStoreViewModel.deleteKeyStore(keyStore)
+ showToast(getString(R.string.text_already_deleted) + " " + keyStore.filename)
+ } else {
+ showToast(getString(R.string.text_failed_to_delete))
+ }
+ } catch (e: Exception) {
+ showToast(getString(R.string.text_failed_to_delete) + ": " + e.message)
+ }
+ }
+
+ private fun deleteAllKeyStores() {
+ val path = File(Pref.getKeyStorePath())
+ if (!path.isDirectory) return
+
+ val files = path.listFiles { _, name -> name.endsWith(".bks") || name.endsWith(".jks") }
+ files?.forEach { file ->
+ file.delete()
+ }
+
+ keyStoreViewModel.deleteAllKeyStores()
+ showToast(getString(R.string.text_already_deleted))
+ }
+
+ fun verifyKeyStore(
+ configs: VerifyKeyStoreDialog.VerifyKeyStoreConfigs, keyStore: KeyStore,
+ ) {
+ // 验证密钥库密码
+ val tmpKeyStore = try {
+ KeyStoreHelper.loadKeyStore(File(keyStore.absolutePath), configs.password.toCharArray())
+ } catch (e: Exception) {
+ null
+ }
+
+ if (tmpKeyStore == null) {
+ showToast(R.string.text_verify_failed)
+ return
+ }
+
+ // 验证别名和别名密码
+ val tmpKey = try {
+ tmpKeyStore.getKey(configs.alias, configs.aliasPassword.toCharArray())
+ } catch (e: Exception) {
+ null
+ }
+
+ if (tmpKey == null) {
+ showToast(R.string.text_verify_failed)
+ return
+ }
+
+ val verifiedKeyStore = KeyStore(
+ absolutePath = keyStore.absolutePath,
+ filename = keyStore.filename,
+ password = AESUtils.encrypt(configs.password),
+ alias = configs.alias,
+ aliasPassword = AESUtils.encrypt(configs.aliasPassword),
+ verified = true
+ )
+ keyStoreViewModel.upsertKeyStore(verifiedKeyStore)
+ showToast(R.string.text_verify_success)
+ }
+
+ private fun showToast(@StringRes messageResId: Int) {
+ Toast.makeText(this, getString(messageResId), Toast.LENGTH_SHORT).show()
+ }
+
+ private fun showToast(message: String) {
+ Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
+ }
+}
diff --git a/app/src/main/java/org/autojs/autojs/ui/keystore/NewKeyStoreDialog.kt b/app/src/main/java/org/autojs/autojs/ui/keystore/NewKeyStoreDialog.kt
new file mode 100644
index 00000000..35616908
--- /dev/null
+++ b/app/src/main/java/org/autojs/autojs/ui/keystore/NewKeyStoreDialog.kt
@@ -0,0 +1,247 @@
+package org.autojs.autojs.ui.keystore
+
+import android.annotation.SuppressLint
+import android.app.Dialog
+import android.graphics.Color
+import android.graphics.drawable.ColorDrawable
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.view.WindowManager
+import android.widget.ArrayAdapter
+import android.widget.LinearLayout
+import androidx.fragment.app.DialogFragment
+import org.autojs.autojs6.R
+import org.autojs.autojs6.databinding.DialogNewKeyStoreBinding
+
+open class NewKeyStoreDialog(
+ private val callback: Callback,
+) : DialogFragment() {
+
+ private lateinit var binding: DialogNewKeyStoreBinding
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?,
+ ): View {
+ binding = DialogNewKeyStoreBinding.inflate(inflater)
+ return binding.root
+ }
+
+ @SuppressLint("SetTextI18n")
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ dialog?.window?.setLayout(
+ (resources.displayMetrics.widthPixels * 0.85f).toInt(),
+ LinearLayout.LayoutParams.WRAP_CONTENT
+ )
+ dialog?.setCanceledOnTouchOutside(true)
+
+ val signatureAlgorithms = arrayOf("MD5withRSA", "SHA1withRSA", "SHA256withRSA", "SHA512withRSA")
+ val adapter = ArrayAdapter(requireContext(), android.R.layout.simple_spinner_item, signatureAlgorithms)
+ adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
+ binding.signatureAlgorithms.adapter = adapter
+
+ binding.confirm.setOnClickListener {
+ var error = false
+ val filename = binding.filename.text.toString()
+ val password = binding.password.text.toString()
+ val alias = binding.alias.text.toString()
+ val aliasPassword = binding.aliasPassword.text.toString()
+ var valvalidityYears = 25
+
+ // 检查文件名是否符合Android命名规格
+ when {
+ filename.isEmpty() -> {
+ binding.filenameTextInputLayout.error = getString(R.string.text_filename_cannot_be_empty)
+ error = true
+ }
+
+ !containsSpecialCharacters(filename) -> {
+ binding.filenameTextInputLayout.error = getString(R.string.text_filename_cannot_contain_invalid_character)
+ error = true
+ }
+
+ filename.length > 255 -> {
+ binding.filenameTextInputLayout.error = getString(R.string.text_filename_is_too_long)
+ error = true
+ }
+
+ else -> binding.filenameTextInputLayout.error = null
+ }
+
+ // 检查密码是否符合要求
+ when {
+ password.isEmpty() -> {
+ binding.passwordTextInputLayout.error = getString(R.string.text_password_cannot_be_empty)
+ error = true
+ }
+
+ password.length < 6 -> {
+ binding.passwordTextInputLayout.error = getString(R.string.text_password_requires_at_least_n_characters, 6)
+ error = true
+ }
+
+ else -> binding.passwordTextInputLayout.error = null
+ }
+
+ // 检查别名密码是否符合要求
+ when {
+ aliasPassword.isEmpty() -> {
+ binding.aliasPasswordTextInputLayout.error = getString(R.string.text_password_cannot_be_empty)
+ error = true
+ }
+
+ aliasPassword.length < 6 -> {
+ binding.aliasPasswordTextInputLayout.error = getString(R.string.text_password_requires_at_least_n_characters, 6)
+ error = true
+ }
+
+ else -> binding.aliasPasswordTextInputLayout.error = null
+ }
+
+ // 检查别名是否符合要求
+ if (alias.isEmpty()) {
+ binding.aliasTextInputLayout.error = getString(R.string.text_alias_cannot_be_empty)
+ error = true
+ } else {
+ binding.aliasTextInputLayout.error = null
+ }
+
+ // 检查有效期是否符合要求
+ if (binding.validityYears.text.toString().isEmpty()) {
+ binding.validityYearsTextInputLayout.error = getString(R.string.text_validity_years_cannot_be_empty)
+ error = true
+ } else {
+ val years = binding.validityYears.text.toString().toInt()
+ if (years == 0) {
+ binding.validityYearsTextInputLayout.error = getString(R.string.text_validity_years_cannot_be_zero)
+ error = true
+ } else {
+ binding.validityYearsTextInputLayout.error = null
+ valvalidityYears = years
+ }
+ }
+
+
+ val firstAndLastName = binding.firstAndLastName.text.toString()
+
+ val organization = binding.organization.text.toString()
+ val organizationalUnit = binding.organizationalUnit.text.toString()
+
+ val countryCode = binding.countryCode.text.toString()
+ val stateOrProvince = binding.stateOrProvince.text.toString()
+ val cityOrLocality = binding.cityOrLocality.text.toString()
+ val street = binding.street.text.toString()
+
+ if (firstAndLastName.isEmpty() && organization.isEmpty() &&
+ organizationalUnit.isEmpty() && stateOrProvince.isEmpty() &&
+ cityOrLocality.isEmpty() && street.isEmpty() && countryCode.isEmpty()
+ ) {
+ binding.firstAndLastNameTextInputLayout.error = getString(R.string.text_at_least_one_certificate_issuer_field_is_not_empty)
+ binding.organizationTextInputLayout.error = getString(R.string.text_at_least_one_certificate_issuer_field_is_not_empty)
+ binding.organizationalUnitTextInputLayout.error = getString(R.string.text_at_least_one_certificate_issuer_field_is_not_empty)
+ binding.countryCodeTextInputLayout.error = getString(R.string.text_at_least_one_certificate_issuer_field_is_not_empty)
+ binding.stateOrProvinceTextInputLayout.error = getString(R.string.text_at_least_one_certificate_issuer_field_is_not_empty)
+ binding.cityOrLocalityTextInputLayout.error = getString(R.string.text_at_least_one_certificate_issuer_field_is_not_empty)
+ binding.streetTextInputLayout.error = getString(R.string.text_at_least_one_certificate_issuer_field_is_not_empty)
+ error = true
+ } else {
+ binding.firstAndLastNameTextInputLayout.error = null
+ binding.organizationTextInputLayout.error = null
+ binding.organizationalUnitTextInputLayout.error = null
+ binding.countryCodeTextInputLayout.error = null
+ binding.stateOrProvinceTextInputLayout.error = null
+ binding.cityOrLocalityTextInputLayout.error = null
+ binding.streetTextInputLayout.error = null
+ }
+
+ // 检查国家代码是否符合要求 (ISO3166-1-Alpha-2: https://countrycodedata.com/)
+ val countryCodeRegex = "^[A-Z]{2}$".toRegex()
+ if (countryCode.isNotEmpty() && !countryCodeRegex.matches(countryCode)) {
+ binding.countryCodeTextInputLayout.error = getString(R.string.text_country_code_must_be_two_capital_letters)
+ error = true
+ }
+
+ if (error) return@setOnClickListener
+
+ val suffix = getString(
+ if (binding.typeJks.isChecked) R.string.text_jks
+ else R.string.text_bks
+ ).lowercase()
+
+ val signatureAlgorithm = binding.signatureAlgorithms.selectedItem.toString()
+
+ val configs = NewKeyStoreConfigs(
+ filename = "$filename.$suffix",
+ password = password,
+ alias = alias,
+ aliasPassword = aliasPassword,
+ signatureAlgorithm = signatureAlgorithm,
+ validityYears = valvalidityYears,
+ firstAndLastName = firstAndLastName,
+ organization = organization,
+ organizationalUnit = organizationalUnit,
+ countryCode = countryCode,
+ stateOrProvince = stateOrProvince,
+ cityOrLocality = cityOrLocality,
+ street = street
+ )
+ callback.onConfirmButtonClicked(configs)
+ dismiss()
+ }
+
+ binding.cancel.setOnClickListener {
+ dismiss()
+ }
+
+ binding.moreOptions.setOnCheckedChangeListener { _, isChecked ->
+ binding.moreOptionsContainer.visibility = if (isChecked) View.VISIBLE else View.GONE
+ }
+ }
+
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+ return super.onCreateDialog(savedInstanceState).apply {
+ window?.apply {
+ setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
+ setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN)
+ }
+ }
+ }
+
+ private fun containsSpecialCharacters(fileName: String): Boolean {
+ // 定义不允许的字符
+ val invalidCharacters = listOf("\\", "/", ":", "*", "?", "\"", "<", ">", "|")
+
+ // 检查文件名是否包含无效字符
+ for (char in invalidCharacters) {
+ if (fileName.contains(char)) {
+ return false
+ }
+ }
+
+ return true
+ }
+
+ data class NewKeyStoreConfigs(
+ val filename: String,
+ val password: String,
+ val alias: String,
+ val aliasPassword: String,
+ val signatureAlgorithm: String,
+ val validityYears: Int,
+ val firstAndLastName: String,
+ val organizationalUnit: String,
+ val organization: String,
+ val countryCode: String,
+ val stateOrProvince: String,
+ val cityOrLocality: String,
+ val street: String,
+ )
+
+
+ interface Callback {
+ fun onConfirmButtonClicked(configs: NewKeyStoreConfigs)
+ }
+}
+
diff --git a/app/src/main/java/org/autojs/autojs/ui/keystore/VerifyKeyStoreDialog.kt b/app/src/main/java/org/autojs/autojs/ui/keystore/VerifyKeyStoreDialog.kt
new file mode 100644
index 00000000..ef361a53
--- /dev/null
+++ b/app/src/main/java/org/autojs/autojs/ui/keystore/VerifyKeyStoreDialog.kt
@@ -0,0 +1,134 @@
+package org.autojs.autojs.ui.keystore
+
+import android.annotation.SuppressLint
+import android.app.Dialog
+import android.graphics.Color
+import android.graphics.drawable.ColorDrawable
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.view.WindowManager
+import android.widget.LinearLayout
+import androidx.fragment.app.DialogFragment
+import org.autojs.autojs.apkbuilder.keystore.AESUtils
+import org.autojs.autojs.apkbuilder.keystore.KeyStore
+import org.autojs.autojs6.R
+import org.autojs.autojs6.databinding.DialogVerifyKeyStoreBinding
+
+open class VerifyKeyStoreDialog(
+ private val callback: Callback,
+ private val keyStore: KeyStore,
+) : DialogFragment() {
+
+ private lateinit var binding: DialogVerifyKeyStoreBinding
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?,
+ ): View {
+ binding = DialogVerifyKeyStoreBinding.inflate(inflater)
+ return binding.root
+ }
+
+ @SuppressLint("SetTextI18n")
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ dialog?.window?.setLayout(
+ (resources.displayMetrics.widthPixels * 0.85f).toInt(),
+ LinearLayout.LayoutParams.WRAP_CONTENT
+ )
+ dialog?.setCanceledOnTouchOutside(true)
+
+ binding.filePath.text = keyStore.absolutePath
+
+ if (keyStore.verified) {
+ binding.imgVerifyState.setImageResource(R.drawable.ic_key_store_verified)
+ binding.textVerifyState.text = getString(R.string.text_verified)
+ binding.password.setText(AESUtils.decrypt(keyStore.password))
+ binding.alias.setText(keyStore.alias)
+ binding.aliasPassword.setText(AESUtils.decrypt(keyStore.aliasPassword))
+ } else {
+ binding.imgVerifyState.setImageResource(R.drawable.ic_key_store_unverified)
+ binding.textVerifyState.text = getString(R.string.text_unverified)
+ }
+
+ binding.verify.setOnClickListener {
+ var error = false
+ val password = binding.password.text.toString()
+ val alias = binding.alias.text.toString()
+ val aliasPassword = binding.aliasPassword.text.toString()
+
+ // 检查密码是否符合要求
+ when {
+ password.isEmpty() -> {
+ binding.passwordTextInputLayout.error = getString(R.string.text_password_cannot_be_empty)
+ error = true
+ }
+
+ password.length < 6 -> {
+ binding.passwordTextInputLayout.error = getString(R.string.text_password_requires_at_least_n_characters, 6)
+ error = true
+ }
+
+ else -> binding.passwordTextInputLayout.error = null
+ }
+
+ // 检查别名密码是否符合要求
+ when {
+ aliasPassword.isEmpty() -> {
+ binding.aliasPasswordTextInputLayout.error = getString(R.string.text_password_cannot_be_empty)
+ error = true
+ }
+
+ aliasPassword.length < 6 -> {
+ binding.aliasPasswordTextInputLayout.error = getString(R.string.text_password_requires_at_least_n_characters, 6)
+ error = true
+ }
+
+ else -> binding.aliasPasswordTextInputLayout.error = null
+ }
+
+ // 检查别名是否符合要求
+ if (alias.isEmpty()) {
+ binding.aliasTextInputLayout.error = getString(R.string.text_alias_cannot_be_empty)
+ error = true
+ } else {
+ binding.aliasTextInputLayout.error = null
+ }
+
+ if (error) return@setOnClickListener
+
+ val configs = VerifyKeyStoreConfigs(
+ password = password,
+ alias = alias,
+ aliasPassword = aliasPassword,
+ )
+ callback.onVerifyButtonClicked(configs, keyStore)
+ dismiss()
+ }
+
+ binding.cancel.setOnClickListener {
+ dismiss()
+ }
+ }
+
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+ return super.onCreateDialog(savedInstanceState).apply {
+ window?.apply {
+ setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
+ setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN)
+ }
+ }
+ }
+
+ data class VerifyKeyStoreConfigs(
+ val password: String,
+ val alias: String,
+ val aliasPassword: String,
+ )
+
+ interface Callback {
+ fun onVerifyButtonClicked(configs: VerifyKeyStoreConfigs, keyStore: KeyStore)
+ }
+}
+
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 6592dc0b..09ea7065 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
@@ -9,14 +9,20 @@
import android.text.TextUtils;
import android.text.util.Linkify;
import android.util.Log;
+import android.view.Gravity;
import android.view.KeyEvent;
import android.view.View;
+import android.widget.CheckBox;
+import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.LinearLayout;
+import android.widget.Spinner;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.lifecycle.ViewModelProvider;
+
import com.afollestad.materialdialogs.MaterialDialog;
import com.google.android.flexbox.FlexboxLayout;
import com.google.android.material.textfield.TextInputLayout;
@@ -34,6 +40,9 @@
import org.autojs.autojs.ui.BaseActivity;
import org.autojs.autojs.ui.common.NotAskAgainDialog;
import org.autojs.autojs.ui.filechooser.FileChooserDialogBuilder;
+import org.autojs.autojs.apkbuilder.keystore.KeyStore;
+import org.autojs.autojs.ui.viewmodel.KeyStoreViewModel;
+import org.autojs.autojs.ui.keystore.ManageKeyStoreActivity;
import org.autojs.autojs.ui.shortcut.AppsIconSelectActivity;
import org.autojs.autojs.ui.widget.RoundCheckboxWithText;
import org.autojs.autojs.util.AndroidUtils;
@@ -56,6 +65,7 @@
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.TreeMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Function;
import java.util.regex.Pattern;
@@ -109,6 +119,61 @@ public class BuildActivity extends BaseActivity implements ApkBuilder.ProgressCa
put(ApkBuilder.Constants.MLKIT_BARCODE, /* MLKit Barcode */ List.of("barcode", "mlkit-barcode", "mlkit_barcode"));
}};
+ 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 final Map SUPPORTED_PERMISSIONS = new TreeMap<>() {{
+ put("android.permission.ACCESS_COARSE_LOCATION", R.string.text_permission_access_coarse_location);
+ put("android.permission.ACCESS_FINE_LOCATION", R.string.text_permission_access_fine_location);
+ put("android.permission.ACCESS_NETWORK_STATE", R.string.text_permission_access_network_state);
+ put("android.permission.ACCESS_WIFI_STATE", R.string.text_permission_access_wifi_state);
+ put("android.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS", R.string.text_permission_broadcast_close_system_dialogs);
+ put("android.permission.CAPTURE_VIDEO_OUTPUT", R.string.text_permission_capture_video_output);
+ put("android.permission.DUMP", R.string.text_permission_dump);
+ put("android.permission.FOREGROUND_SERVICE", R.string.text_permission_foreground_service);
+ put("android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION", R.string.text_permission_foreground_service_media_projection);
+ put("android.permission.FOREGROUND_SERVICE_SPECIAL_USE", R.string.text_permission_foreground_service_special_use);
+ put("android.permission.INTERNET", R.string.text_permission_internet);
+ put("android.permission.INTERACT_ACROSS_USERS_FULL", R.string.text_permission_interact_across_users_full);
+ put("android.permission.MANAGE_EXTERNAL_STORAGE", R.string.text_permission_manage_external_storage);
+ put("android.permission.MANAGE_USERS", R.string.text_permission_manage_users);
+ put("android.permission.POST_NOTIFICATIONS", R.string.text_permission_post_notifications);
+ put("android.permission.QUERY_ALL_PACKAGES", R.string.text_permission_query_all_packages);
+ put("android.permission.READ_EXTERNAL_STORAGE", R.string.text_permission_read_external_storage);
+ put("android.permission.READ_MEDIA_AUDIO", R.string.text_permission_read_media_audio);
+ put("android.permission.READ_MEDIA_IMAGES", R.string.text_permission_read_media_images);
+ put("android.permission.READ_MEDIA_VIDEO", R.string.text_permission_read_media_video);
+ put("android.permission.READ_PHONE_STATE", R.string.text_permission_read_phone_state);
+ put("android.permission.READ_PRIVILEGED_PHONE_STATE", R.string.text_permission_read_privileged_phone_state);
+ put("android.permission.READ_SMS", R.string.text_permission_read_sms);
+ put("android.permission.RECEIVE_BOOT_COMPLETED", R.string.text_permission_receive_boot_completed);
+ put("android.permission.RECORD_AUDIO", R.string.text_permission_record_audio);
+ put("android.permission.REORDER_TASKS", R.string.text_permission_reorder_tasks);
+ put("android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS", R.string.text_permission_request_ignore_battery_optimizations);
+ put("android.permission.REQUEST_INSTALL_PACKAGES", R.string.text_permission_request_install_packages);
+ put("android.permission.SCHEDULE_EXACT_ALARM", R.string.text_permission_schedule_exact_alarm);
+ put("android.permission.SYSTEM_ALERT_WINDOW", R.string.text_permission_system_alert_window);
+ put("android.permission.UNLIMITED_TOASTS", R.string.text_permission_unlimited_toasts);
+ put("android.permission.UNINSTALL_SHORTCUT", R.string.text_permission_uninstall_shortcut);
+ put("android.permission.USE_EXACT_ALARM", R.string.text_permission_use_exact_alarm);
+ put("android.permission.VIBRATE", R.string.text_permission_vibrate);
+ put("android.permission.WAKE_LOCK", R.string.text_permission_wake_lock);
+ put("android.permission.WRITE_EXTERNAL_STORAGE", R.string.text_permission_write_external_storage);
+ put("android.permission.WRITE_SECURE_SETTINGS", R.string.text_permission_write_secure_settings);
+ put("android.permission.WRITE_SETTINGS", R.string.text_permission_write_settings);
+ put("com.android.launcher.permission.INSTALL_SHORTCUT", R.string.text_permission_install_shortcut);
+ put("com.android.launcher.permission.UNINSTALL_SHORTCUT", R.string.text_permission_uninstall_shortcut);
+ put("com.termux.permission.RUN_COMMAND", R.string.text_permission_run_command);
+ put("moe.shizuku.manager.permission.API_V23", R.string.text_permission_shizuku);
+ }};
+
EditText mSourcePath;
View mSourcePathContainer;
EditText mOutputPath;
@@ -126,6 +191,9 @@ public class BuildActivity extends BaseActivity implements ApkBuilder.ProgressCa
private boolean mIsProjectLevelBuilding;
private FlexboxLayout mFlexboxAbis;
private FlexboxLayout mFlexboxLibs;
+ private Spinner mSignatureSchemes;
+ private Spinner mVerifiedKeyStores;
+ private FlexboxLayout mFlexboxPermissions;
private final ArrayList mInvalidAbis = new ArrayList<>();
private final ArrayList mUnavailableAbis = new ArrayList<>();
@@ -133,6 +201,8 @@ public class BuildActivity extends BaseActivity implements ApkBuilder.ProgressCa
private final ArrayList mInvalidLibs = new ArrayList<>();
private final ArrayList mUnavailableLibs = new ArrayList<>();
+ private KeyStoreViewModel mKeyStoreViewModel;
+
@SuppressLint("SetTextI18n")
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
@@ -195,6 +265,18 @@ protected void onCreate(@Nullable Bundle savedInstanceState) {
mFlexboxLibs = 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();
+
binding.fab.setOnClickListener(v -> buildApk());
binding.selectSource.setOnClickListener(v -> selectSourceFilePath());
binding.selectOutput.setOnClickListener(v -> selectOutputDirPath());
@@ -204,6 +286,8 @@ protected void onCreate(@Nullable Bundle savedInstanceState) {
return true;
});
binding.textLibs.setOnClickListener(v -> toggleAllFlexboxChildren(mFlexboxLibs));
+ binding.manageKeyStore.setOnClickListener(v -> ManageKeyStoreActivity.Companion.startActivity(this));
+ binding.textPermissions.setOnClickListener(v -> toggleAllFlexboxChildren(mFlexboxPermissions));
setToolbarAsBack(R.string.text_build_apk);
mSource = getIntent().getStringExtra(EXTRA_SOURCE);
@@ -217,6 +301,12 @@ protected void onCreate(@Nullable Bundle savedInstanceState) {
showHintDialogIfNeeded();
}
+ @Override
+ protected void onResume() {
+ super.onResume();
+ mKeyStoreViewModel.updateVerifiedKeyStores();
+ }
+
private void toggleAllFlexboxChildren(FlexboxLayout mFlexboxLibs) {
boolean isAllChecked = true;
for (int i = 0; i < mFlexboxLibs.getChildCount(); i += 1) {
@@ -229,6 +319,14 @@ private void toggleAllFlexboxChildren(FlexboxLayout mFlexboxLibs) {
isAllChecked = false;
break;
}
+ } else if (child instanceof CheckBox) {
+ if (!child.isEnabled()) {
+ continue;
+ }
+ if (!((CheckBox) child).isChecked()) {
+ isAllChecked = false;
+ break;
+ }
}
}
for (int i = 0; i < mFlexboxLibs.getChildCount(); i += 1) {
@@ -238,6 +336,11 @@ private void toggleAllFlexboxChildren(FlexboxLayout mFlexboxLibs) {
continue;
}
((RoundCheckboxWithText) child).setChecked(!isAllChecked);
+ } else if (child instanceof CheckBox) {
+ if (!child.isEnabled()) {
+ continue;
+ }
+ ((CheckBox) child).setChecked(!isAllChecked);
}
}
}
@@ -341,6 +444,51 @@ private void syncLibsCheckedStates() {
mInvalidLibs.addAll(candidates);
}
+ private void initSignatureSchemeSpinner() {
+ ArrayAdapter adapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_item, SIGNATURE_SCHEMES);
+ adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ mSignatureSchemes.setAdapter(adapter);
+ }
+
+ private void initVerifiedKeyStoresSpinner() {
+ ArrayList verifiedKeyStores = new ArrayList<>();
+ // 添加 默认密钥库 下拉选项
+ KeyStore defaultKeyStore = new KeyStore("", getString(R.string.text_default_key_store), "", "", "", false); // 仅用于显示下拉列表
+ verifiedKeyStores.add(defaultKeyStore);
+
+ ArrayAdapter adapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_item, verifiedKeyStores);
+ adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ mVerifiedKeyStores.setAdapter(adapter);
+
+ mKeyStoreViewModel.getVerifiedKeyStores().observe(this, keyStores -> {
+ // 清空现有的选项,但保留第一个元素,即默认密钥库
+ if (verifiedKeyStores.size() > 1) {
+ verifiedKeyStores.subList(1, verifiedKeyStores.size()).clear();
+ }
+ verifiedKeyStores.addAll(keyStores);
+ adapter.notifyDataSetChanged();
+ });
+ }
+
+ @SuppressLint("SetTextI18n")
+ private void initPermissionsChildren() {
+ SUPPORTED_PERMISSIONS.forEach((permission, descriptionResId) -> {
+ CheckBox checkBox = new CheckBox(this);
+ checkBox.setLayoutParams(new LinearLayout.LayoutParams(
+ LinearLayout.LayoutParams.MATCH_PARENT,
+ LinearLayout.LayoutParams.WRAP_CONTENT));
+ checkBox.setAlpha(0.87f);
+ checkBox.setText(permission + "\n" + getString(descriptionResId));
+ checkBox.setButtonDrawable(R.drawable.round_checkbox);
+ checkBox.setGravity(Gravity.CENTER_VERTICAL);
+ checkBox.setTextSize(12);
+ int marginInPixels = (int) (8 * getResources().getDisplayMetrics().density);
+ checkBox.setPadding(marginInPixels, 0, 0, 0);
+ checkBox.setChecked(false);
+ mFlexboxPermissions.addView(checkBox);
+ });
+ }
+
private boolean isAliasMatching(Map> aliases, String aliasKey, List candidates) {
AtomicBoolean result = new AtomicBoolean(false);
var aliasList = aliases.getOrDefault(aliasKey, Collections.emptyList());
@@ -619,6 +767,7 @@ private void doBuildingApk() {
private ApkBuilder.AppConfig createAppConfig() {
ArrayList abis = collectCheckedItems(mFlexboxAbis);
ArrayList libs = collectCheckedItems(mFlexboxLibs);
+ ArrayList permissions = collectCheckedItems(mFlexboxPermissions);
ApkBuilder.AppConfig appConfig = mProjectConfig != null
? ApkBuilder.AppConfig.fromProjectConfig(mSource, mProjectConfig)
@@ -632,6 +781,13 @@ private ApkBuilder.AppConfig createAppConfig() {
appConfig.setAbis(abis);
appConfig.setLibs(libs);
+ appConfig.setSignatureSchemes(mSignatureSchemes.getSelectedItem().toString());
+ if (mVerifiedKeyStores.getSelectedItemPosition() > 0) {
+ appConfig.setKeyStore((KeyStore) mVerifiedKeyStores.getSelectedItem());
+ } else {
+ appConfig.setKeyStore(null);
+ }
+ appConfig.setPermissions(permissions);
return appConfig;
}
@@ -649,6 +805,13 @@ private ArrayList collectCheckedItems(FlexboxLayout flexboxLayout) {
libs.add(charSequence.toString());
}
}
+ } else if (child instanceof CheckBox) {
+ if (((CheckBox) child).isChecked()) {
+ CharSequence charSequence = ((CheckBox) child).getText();
+ if (charSequence != null) {
+ libs.add(charSequence.toString().split("\n")[0]);
+ }
+ }
}
}
return libs;
diff --git a/app/src/main/java/org/autojs/autojs/ui/viewmodel/KeyStoreViewModel.kt b/app/src/main/java/org/autojs/autojs/ui/viewmodel/KeyStoreViewModel.kt
new file mode 100644
index 00000000..a19a2937
--- /dev/null
+++ b/app/src/main/java/org/autojs/autojs/ui/viewmodel/KeyStoreViewModel.kt
@@ -0,0 +1,104 @@
+package org.autojs.autojs.ui.viewmodel
+
+import android.content.Context
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.viewModelScope
+import kotlinx.coroutines.launch
+import org.autojs.autojs.apkbuilder.keystore.KeyStore
+import org.autojs.autojs.apkbuilder.keystore.KeyStoreRepository
+import java.io.File
+
+class KeyStoreViewModel(context: Context) : ViewModel() {
+ private val keyStoreRepository: KeyStoreRepository = KeyStoreRepository(context)
+
+ private val _allKeyStores = MutableLiveData>()
+ val allKeyStores: LiveData> get() = _allKeyStores
+
+ private val _verifiedKeyStores = MutableLiveData>()
+ val verifiedKeyStores: LiveData> get() = _verifiedKeyStores
+
+ init {
+ updateVerifiedKeyStores()
+ }
+
+ fun updateVerifiedKeyStores() {
+ viewModelScope.launch {
+ val keyStores = keyStoreRepository.getAllKeyStores()
+ val validKeyStores = mutableListOf()
+
+ keyStores.forEach { keyStore ->
+ val file = File(keyStore.absolutePath)
+ if (file.exists()) {
+ validKeyStores.add(keyStore)
+ } else {
+ keyStoreRepository.deleteKeyStores(keyStore)
+ }
+ }
+
+ _verifiedKeyStores.value = validKeyStores
+ }
+ }
+
+ fun updateAllKeyStoresFromFiles(files: Array) {
+ viewModelScope.launch {
+ val updatedKeyStores = files.map { file ->
+ keyStoreRepository.getKeyStoreAbsolutePath(file.absolutePath) ?: KeyStore(
+ absolutePath = file.absolutePath,
+ filename = file.name
+ )
+ }
+
+ _allKeyStores.value = updatedKeyStores
+ }
+ }
+
+ fun upsertKeyStore(keyStore: KeyStore) {
+ viewModelScope.launch {
+ keyStoreRepository.upsertKeyStores(keyStore)
+
+ val currentKeyStores = _allKeyStores.value ?: emptyList()
+
+ val updatedKeyStores =
+ if (currentKeyStores.any { it.absolutePath == keyStore.absolutePath }) {
+ currentKeyStores.map {
+ if (keyStore.absolutePath == it.absolutePath) {
+ keyStore
+ } else {
+ it
+ }
+ }
+ } else {
+ currentKeyStores + keyStore
+ }
+
+ _allKeyStores.value = updatedKeyStores
+ }
+ }
+
+ fun deleteKeyStore(keyStore: KeyStore) {
+ viewModelScope.launch {
+ keyStoreRepository.deleteKeyStores(keyStore)
+
+ val currentKeyStores = _allKeyStores.value ?: emptyList()
+ val updatedKeyStores = currentKeyStores.filter { it != keyStore }
+ _allKeyStores.value = updatedKeyStores
+ }
+ }
+
+ fun deleteAllKeyStores() {
+ viewModelScope.launch {
+ keyStoreRepository.deleteAllKeyStores()
+ _allKeyStores.value = emptyList()
+ }
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ class Factory(private val context: Context) : ViewModelProvider.Factory {
+ override fun create(modelClass: Class): T {
+ return KeyStoreViewModel(context) as T
+ }
+ }
+}
diff --git a/app/src/main/java/pxb/android/axml/AxmlWriter.java b/app/src/main/java/pxb/android/axml/AxmlWriter.java
index 0c9f0de5..b814dee1 100644
--- a/app/src/main/java/pxb/android/axml/AxmlWriter.java
+++ b/app/src/main/java/pxb/android/axml/AxmlWriter.java
@@ -94,6 +94,9 @@ private int prepare() throws IOException {
int size = 0;
for (NodeImpl first : firsts) {
+ if (first.ignore) {
+ continue;
+ }
size += first.prepare(this);
}
int a = 0;
@@ -163,6 +166,9 @@ public byte[] toByteArray() throws IOException {
}
for (NodeImpl first : firsts) {
+ if (first.ignore) {
+ continue;
+ }
first.write(out);
}
@@ -259,10 +265,11 @@ protected static class NodeImpl extends NodeVisitor {
Attr clz;
protected List children = new ArrayList();
private int line;
- private StringItem name;
+ protected StringItem name;
private StringItem ns;
private StringItem text;
private int textLineNumber;
+ protected boolean ignore = false;
public NodeImpl(String ns, String name) {
super(null);
@@ -344,6 +351,9 @@ public int prepare(AxmlWriter axmlWriter) {
int size = 24 + 36 + attrs.size() * 20;// 24 for end tag,36+x*20 for
// start tag
for (NodeImpl child : children) {
+ if (child.ignore) {
+ continue;
+ }
size += child.prepare(axmlWriter);
}
if (text != null) {
@@ -400,6 +410,9 @@ void write(ByteBuffer out) throws IOException {
// children
for (NodeImpl child : children) {
+ if (child.ignore) {
+ continue;
+ }
child.write(out);
}
diff --git a/app/src/main/res/drawable/ic_delete_all.xml b/app/src/main/res/drawable/ic_delete_all.xml
new file mode 100644
index 00000000..76a5b46c
--- /dev/null
+++ b/app/src/main/res/drawable/ic_delete_all.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_key_store_delete.xml b/app/src/main/res/drawable/ic_key_store_delete.xml
new file mode 100644
index 00000000..2d0d9de1
--- /dev/null
+++ b/app/src/main/res/drawable/ic_key_store_delete.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_key_store_unverified.xml b/app/src/main/res/drawable/ic_key_store_unverified.xml
new file mode 100644
index 00000000..76826422
--- /dev/null
+++ b/app/src/main/res/drawable/ic_key_store_unverified.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_key_store_verified.xml b/app/src/main/res/drawable/ic_key_store_verified.xml
new file mode 100644
index 00000000..8a8ec804
--- /dev/null
+++ b/app/src/main/res/drawable/ic_key_store_verified.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/layout/activity_build.xml b/app/src/main/res/layout/activity_build.xml
index 35898030..22f6e0d9 100644
--- a/app/src/main/res/layout/activity_build.xml
+++ b/app/src/main/res/layout/activity_build.xml
@@ -315,6 +315,120 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/dialog_new_key_store.xml b/app/src/main/res/layout/dialog_new_key_store.xml
new file mode 100644
index 00000000..25585129
--- /dev/null
+++ b/app/src/main/res/layout/dialog_new_key_store.xml
@@ -0,0 +1,317 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/dialog_verify_key_store.xml b/app/src/main/res/layout/dialog_verify_key_store.xml
new file mode 100644
index 00000000..5399969f
--- /dev/null
+++ b/app/src/main/res/layout/dialog_verify_key_store.xml
@@ -0,0 +1,163 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/item_key_store.xml b/app/src/main/res/layout/item_key_store.xml
new file mode 100644
index 00000000..788b3d74
--- /dev/null
+++ b/app/src/main/res/layout/item_key_store.xml
@@ -0,0 +1,81 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/menu/menu_manage_key_store.xml b/app/src/main/res/menu/menu_manage_key_store.xml
new file mode 100644
index 00000000..88ebe2c5
--- /dev/null
+++ b/app/src/main/res/menu/menu_manage_key_store.xml
@@ -0,0 +1,9 @@
+
+
diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml
index 70063799..68bca4c2 100644
--- a/app/src/main/res/values-ar/strings.xml
+++ b/app/src/main/res/values-ar/strings.xml
@@ -759,6 +759,88 @@
ABIs (32 بت)
ABIs (64 بت)
المكتبات
+ تكوين التوقيع
+ مخزن المفاتيح
+ مخزن المفاتيح الافتراضي
+ إدارة مخزن المفاتيح
+ خطة التوقيع
+ إنشاء مفتاح التوقيع
+ كلمة مرور مخزن المفاتيح
+ الاسم المستعار
+ كلمة مرور الاسم المستعار
+ خوارزمية التوقيع
+ المدة (سنوات)
+ الاسم
+ اسم المنظمة
+ وحدة التنظيم
+ رمز الدولة (XX)
+ الولاية أو المقاطعة
+ المدينة أو المنطقة
+ الشارع
+ اسم الملف لا يمكن أن يكون فارغًا
+ اسم الملف لا يمكن أن يحتوي على أي من الأحرف التالية: \\ / : * ? " < > |
+ اسم الملف طويل جدًا
+ يجب أن يحتوي كلمة المرور على %d على الأقل من الأحرف
+ الاسم المستعار لا يمكن أن يكون فارغًا
+ مدة الصلاحية لا يمكن أن تكون فارغة
+ مدة الصلاحية لا يمكن أن تكون صفرًا
+ رمز الدولة يجب أن يكون حرفين كبيرين
+ يجب ملء حقل واحد على الأقل من "الاسم، اسم المنظمة، وحدة التنظيم، رمز الدولة، الولاية أو المقاطعة، المدينة أو المنطقة، الشارع"
+ تم إنشاء مفتاح التوقيع بنجاح
+ فشل في إنشاء مفتاح التوقيع
+ لم يتم التحقق من المخزن
+ تم التحقق من المخزن
+ التحقق
+ حذف الكل
+ لم يتم التحقق
+ تم التحقق
+ التحقق من مخزن المفاتيح
+ تم التحقق بنجاح
+ فشل التحقق
+ مسار الملف
+ الأذونات المطلوبة
+ السماح للتطبيق بالوصول إلى حالة الواي فاي
+ السماح للتطبيق بكتابة البيانات إلى التخزين الخارجي
+ السماح للتطبيق بقراءة البيانات من التخزين الخارجي
+ السماح للتطبيق بقراءة ملفات الصور من التخزين المشترك
+ السماح للتطبيق بقراءة ملفات الفيديو من التخزين المشترك
+ السماح للتطبيق بقراءة ملفات الصوت من التخزين المشترك
+ السماح للتطبيق بإدارة جميع الملفات في التخزين الخارجي
+ السماح للتطبيق بإرسال نية لإغلاق حوارات النظام
+ السماح للتطبيق بالوصول إلى جميع معلومات حالة الهاتف
+ السماح للتطبيق بتثبيت اختصار
+ السماح للتطبيق بإلغاء تثبيت الاختصار
+ السماح للتطبيق بطلب تثبيت تطبيقات أخرى
+ السماح للتطبيق بمنع الهاتف من الدخول في وضع السكون
+ السماح للتطبيق بجدولة المنبهات الدقيقة أو التنبيهات
+ السماح للتطبيق بجدولة عمليات دقيقة بالوقت
+ السماح للتطبيق بالتحكم في اهتزاز الهاتف
+ السماح للتطبيق بالوصول إلى الإنترنت
+ السماح للتطبيق بمعرفة حالة الاتصال بالشبكة
+ السماح للتطبيق بالظهور فوق التطبيقات الأخرى
+ السماح للتطبيق بإعادة ترتيب التطبيقات التي تعمل حالياً
+ السماح للتطبيق بطلب تجاهل تحسينات البطارية
+ السماح للتطبيق بتلقي إشعارات عند اكتمال إقلاع الجهاز
+ السماح للتطبيق بعرض الإشعارات
+ السماح للتطبيق بتشغيل خدمة في المقدمة
+ السماح للتطبيق باستخدام تسجيل الشاشة وإسقاط الوسائط في خدمة أمامية
+ السماح للتطبيق بإجراء عمليات خاصة في خدمة أمامية
+ السماح للتطبيق بعرض عدد غير محدود من إشعارات التوست
+ السماح للتطبيق بالتقاط إخراج الفيديو
+ السماح للتطبيق بتفريغ معلومات النظام
+ السماح للتطبيق بالاستعلام عن جميع الحزم
+ السماح للتطبيق بالوصول إلى بيانات استخدام التطبيقات الأخرى
+ السماح للتطبيق بتعديل إعدادات النظام
+ السماح للتطبيق بقراءة وكتابة إعدادات النظام الحساسة
+ السماح للتطبيق بإدارة المستخدمين
+ السماح للتطبيق بالتفاعل عبر المستخدمين
+ السماح للتطبيق بالوصول إلى الموقع التقريبي عبر الشبكة
+ السماح للتطبيق بالحصول على الموقع الدقيق عبر GPS
+ السماح للتطبيق بتسجيل الصوت
+ مخصص لتطبيق Termux، السماح بتنفيذ الأوامر
+ السماح للتطبيق بقراءة حالة الهاتف والهوية
+ السماح للتطبيق بقراءة الرسائل النصية
+ السماح للتطبيق بالتفاعل مع النظام من خلال خدمة Shizuku للحصول على صلاحيات أعلى
تبديل النافذة
إعدادات الرموز
مطور مصممة خصيصا
diff --git a/app/src/main/res/values-en/strings.xml b/app/src/main/res/values-en/strings.xml
index d12ef96a..b7feacf4 100644
--- a/app/src/main/res/values-en/strings.xml
+++ b/app/src/main/res/values-en/strings.xml
@@ -755,6 +755,88 @@
ABIs (32-bit)
ABIs (64-bit)
Libraries
+ Signature Configuration
+ Keystore
+ Default Keystore
+ Manage Keystore
+ Signature Scheme
+ New Key Store
+ Key Store Password
+ Alias
+ Alias Password
+ Signature Algorithm
+ Validity (Years)
+ Full Name
+ Organization Name
+ Organizational Unit
+ Country Code (XX)
+ State or Province
+ City or Locality
+ Street
+ Filename cannot be empty
+ Filename cannot contain the following characters: \\ / : * ? " < > |
+ Filename is too long
+ Password must be at least %d characters long
+ Alias cannot be empty
+ Validity years cannot be empty
+ Validity years cannot be zero
+ Country code must be two capital letters
+ At least one field from \"Full Name, Organization Name, Organizational Unit, Country Code, State or Province, City or Locality, Street\" must be filled
+ Successfully created signature key
+ Failed to create signature key
+ Keystore has not been verified
+ Keystore has been verified
+ Verify
+ Delete All
+ Unverified
+ Verified
+ Verify Keystore
+ Verification successful
+ Verification failed
+ File Path
+ Permissions
+ Allow the app to access Wi-Fi state
+ Allow the app to write to external storage
+ Allow the app to read from external storage
+ Allow the app to read image files from shared storage
+ Allow the app to read video files from shared storage
+ Allow the app to read audio files from shared storage
+ Allow the app to manage all files in external storage
+ Allow the app to send an intent to close system dialogs
+ Allow the app to access all phone state information
+ Allow the app to install shortcuts
+ Allow the app to uninstall shortcuts
+ Allow the app to request installation of other apps
+ Prevent the phone from going to sleep
+ Allow the app to schedule exact alarms or event reminders
+ Allow the app to schedule time-precise operations
+ Allow the app to control phone vibration
+ Allow the app to access the internet
+ Allow the app to view network connection status
+ Allow the app to display over other apps
+ Allow the app to reorder running tasks
+ Allow the app to request ignoring battery optimizations
+ Allow the app to receive broadcast when device boot completes
+ Allow the app to post notifications
+ Allow the app to run foreground services
+ Allow the app to use screen recording and media projection in foreground services
+ Allow the app to perform special operations in foreground services
+ Allow the app to show unlimited Toast notifications
+ Allow the app to capture video output
+ Allow the app to dump system information
+ Allow the app to query all packages
+ Allow the app to access usage statistics of other apps
+ Allow the app to modify system settings
+ Allow the app to read/write secure system settings
+ Allow the app to manage users
+ Allow the app to interact across users
+ Allow the app to access approximate location via network
+ Allow the app to access precise location via GPS
+ Allow the app to record audio
+ Specific to Termux app, allow running commands
+ Allow the app to read phone state and identity
+ Allow the app to read SMS
+ Allow the app to interact with the system with elevated permissions via Shizuku service
Switch window
Symbols settings
Tailor-made developer
diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml
index 8f867ffc..1032979b 100644
--- a/app/src/main/res/values-es/strings.xml
+++ b/app/src/main/res/values-es/strings.xml
@@ -758,6 +758,88 @@
ABIs (32 bits)
ABIs (64 bits)
Bibliotecas
+ Configuración de firma
+ Almacenamiento de claves
+ Almacenamiento de claves predeterminado
+ Gestionar almacenamiento de claves
+ Esquema de firma
+ Generar clave de firma
+ Contraseña del almacén de claves
+ Alias
+ Contraseña del alias
+ Algoritmo de firma
+ Validez (años)
+ Nombre completo
+ Nombre de la organización
+ Unidad organizacional
+ Código de país (XX)
+ Estado o provincia
+ Ciudad o localidad
+ Calle
+ El nombre de archivo no puede estar vacío
+ El nombre del archivo no puede contener ninguno de los siguientes caracteres: \\ / : * ? " < > |
+ El nombre del archivo es demasiado largo
+ La contraseña debe tener al menos %d caracteres
+ El alias no puede estar vacío
+ El número de años de validez no puede estar vacío
+ El número de años de validez no puede ser cero
+ El código del país debe ser de dos letras mayúsculas
+ Al menos uno de los campos “Nombre completo, Nombre de la organización, Unidad organizacional, Código de país, Estado o provincia, Ciudad o localidad, Calle” debe estar lleno
+ Clave de firma creada con éxito
+ Error al crear la clave de firma
+ El almacén de claves no ha sido verificado
+ El almacén de claves ha sido verificado
+ Verificar
+ Eliminar todo
+ No verificado
+ Verificado
+ Verificar almacén de claves
+ Verificación exitosa
+ Error en la verificación
+ Ruta del archivo
+ Permisos requeridos
+ Permitir que la aplicación acceda al estado de Wi-Fi
+ Permitir que la aplicación escriba en el almacenamiento externo
+ Permitir que la aplicación lea desde el almacenamiento externo
+ Permitir que la aplicación lea archivos de imágenes del almacenamiento compartido
+ Permitir que la aplicación lea archivos de video del almacenamiento compartido
+ Permitir que la aplicación lea archivos de audio del almacenamiento compartido
+ Permitir que la aplicación gestione todos los archivos en el almacenamiento externo
+ Permitir que la aplicación envíe una intención para cerrar los cuadros de diálogo del sistema
+ Permitir que la aplicación acceda a toda la información del estado del teléfono
+ Permitir que la aplicación instale accesos directos
+ Permitir que la aplicación desinstale accesos directos
+ Permitir que la aplicación solicite la instalación de otras aplicaciones
+ Evitar que el teléfono entre en modo de suspensión
+ Permitir que la aplicación programe alarmas exactas o recordatorios de eventos
+ Permitir que la aplicación programe operaciones precisas por tiempo
+ Permitir que la aplicación controle la vibración del teléfono
+ Permitir que la aplicación acceda a Internet
+ Permitir que la aplicación vea el estado de la conexión de red
+ Permitir que la aplicación se muestre encima de otras aplicaciones
+ Permitir que la aplicación reordene tareas en ejecución
+ Permitir que la aplicación solicite ignorar optimizaciones de batería
+ Permitir que la aplicación reciba la notificación cuando el dispositivo termine de arrancar
+ Permitir que la aplicación envíe notificaciones
+ Permitir que la aplicación ejecute servicios en primer plano
+ Permitir que la aplicación use grabación de pantalla y proyección de medios en servicios en primer plano
+ Permitir que la aplicación realice operaciones especiales en servicios en primer plano
+ Permitir que la aplicación muestre notificaciones de Toast ilimitadas
+ Permitir que la aplicación capture salida de video
+ Permitir que la aplicación volcar información del sistema
+ Permitir que la aplicación consulte todos los paquetes
+ Permitir que la aplicación acceda a las estadísticas de uso de otras aplicaciones
+ Permitir que la aplicación modifique los ajustes del sistema
+ Permitir que la aplicación lea/escriba configuraciones del sistema seguras
+ Permitir que la aplicación administre usuarios
+ Permitir que la aplicación interactúe entre usuarios
+ Permitir que la aplicación acceda a la ubicación aproximada a través de la red
+ Permitir que la aplicación acceda a la ubicación precisa a través de GPS
+ Permitir que la aplicación grabe audio
+ Específico para la aplicación Termux, permitir ejecutar comandos
+ Permitir que la aplicación lea el estado y la identidad del teléfono
+ Permitir que la aplicación lea mensajes SMS
+ Permitir que la aplicación interactúe con el sistema mediante el servicio Shizuku para obtener permisos elevados
Cambiar ventana
Config de símbolos
Desarrollador a medida
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
index 393fa051..60beed07 100644
--- a/app/src/main/res/values-fr/strings.xml
+++ b/app/src/main/res/values-fr/strings.xml
@@ -758,6 +758,88 @@
ABIs (32-bit)
ABIs (64-bit)
Bibliothèques
+ Configuration de la signature
+ Magasin de clés
+ Magasin de clés par défaut
+ Gérer le magasin de clés
+ Schéma de signature
+ Générer une clé de signature
+ Mot de passe du magasin de clés
+ Alias
+ Mot de passe de l\'alias
+ Algorithme de signature
+ Validité (années)
+ Nom complet
+ Nom de l\'organisation
+ Unité organisationnelle
+ Code du pays (XX)
+ État ou province
+ Ville ou localité
+ Rue
+ Le nom de fichier ne peut pas être vide
+ Le nom du fichier ne peut pas contenir les caractères suivants : \\ / : * ? " < > |
+ Le nom du fichier est trop long
+ Le mot de passe doit contenir au moins %d caractères
+ L\'alias ne peut pas être vide
+ Les années de validité ne peuvent pas être vides
+ Les années de validité ne peuvent pas être égales à zéro
+ Le code du pays doit être composé de deux lettres majuscules
+ Au moins un champ parmi « Nom complet, Nom de l\'organisation, Unité organisationnelle, Code du pays, État ou province, Ville ou localité, Rue » doit être renseigné
+ Clé de signature créée avec succès
+ Échec de la création de la clé de signature
+ Le magasin de clés n\'a pas été vérifié
+ Le magasin de clés a été vérifié
+ Vérifier
+ Tout supprimer
+ Non vérifié
+ Vérifié
+ Vérifier le magasin de clés
+ Vérification réussie
+ Échec de la vérification
+ Chemin du fichier
+ Autorisations nécessaires
+ Autoriser l\'application à accéder à l\'état du Wi-Fi
+ Autoriser l\'application à écrire dans le stockage externe
+ Autoriser l\'application à lire à partir du stockage externe
+ Autoriser l\'application à lire les fichiers image depuis le stockage partagé
+ Autoriser l\'application à lire les fichiers vidéo depuis le stockage partagé
+ Autoriser l\'application à lire les fichiers audio depuis le stockage partagé
+ Autoriser l\'application à gérer tous les fichiers dans le stockage externe
+ Autoriser l\'application à envoyer une intention pour fermer les boîtes de dialogue système
+ Autoriser l\'application à accéder à toutes les informations de l\'état du téléphone
+ Autoriser l\'application à installer des raccourcis
+ Autoriser l\'application à désinstaller des raccourcis
+ Autoriser l\'application à demander l\'installation d\'autres applications
+ Empêcher le téléphone de passer en mode veille
+ Autoriser l\'application à programmer des alarmes exactes ou des rappels d\'événements
+ Autoriser l\'application à programmer des opérations précises dans le temps
+ Autoriser l\'application à contrôler la vibration du téléphone
+ Autoriser l\'application à accéder à Internet
+ Autoriser l\'application à consulter l\'état de la connexion réseau
+ Autoriser l\'application à s\'afficher au-dessus d\'autres applications
+ Autoriser l\'application à réorganiser les tâches en cours d\'exécution
+ Autoriser l\'application à demander l\'ignorance des optimisations de la batterie
+ Autoriser l\'application à recevoir une notification lorsque le démarrage du périphérique est terminé
+ Autoriser l\'application à afficher des notifications
+ Autoriser l\'application à exécuter des services en premier plan
+ Autoriser l\'application à utiliser l\'enregistrement d\'écran et la projection multimédia dans des services en premier plan
+ Autoriser l\'application à effectuer des opérations spéciales dans des services en premier plan
+ Autoriser l\'application à afficher un nombre illimité de notifications Toast
+ Autoriser l\'application à capturer la sortie vidéo
+ Autoriser l\'application à vider les informations système
+ Autoriser l\'application à interroger tous les packages
+ Autoriser l\'application à accéder aux statistiques d\'utilisation d\'autres applications
+ Autoriser l\'application à modifier les paramètres système
+ Autoriser l\'application à lire/écrire les paramètres sécurisés du système
+ Autoriser l\'application à gérer les utilisateurs
+ Autoriser l\'application à interagir entre utilisateurs
+ Autoriser l\'application à accéder à une localisation approximative via le réseau
+ Autoriser l\'application à accéder à une localisation précise via le GPS
+ Autoriser l\'application à enregistrer de l\'audio
+ Spécifique à l\'application Termux, autoriser l\'exécution de commandes
+ Autoriser l\'application à lire l\'état du téléphone et l\'identité
+ Autoriser l\'application à lire les SMS
+ Autoriser l\'application à interagir avec le système via le service Shizuku pour obtenir des autorisations élevées
Changer de fenêtre
Param des symboles
Développeur sur mesure
diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml
index 3da880e4..026e03d0 100644
--- a/app/src/main/res/values-ja/strings.xml
+++ b/app/src/main/res/values-ja/strings.xml
@@ -758,6 +758,88 @@
ABI (32ビット)
ABI (64ビット)
ライブラリー
+ 署名設定
+ キーストア
+ デフォルトのキーストア
+ キーストアを管理
+ 署名スキーム
+ 署名キーを生成
+ キーストアのパスワード
+ エイリアス
+ エイリアスのパスワード
+ 署名アルゴリズム
+ 有効期限(年)
+ 氏名
+ 組織名
+ 組織単位
+ 国コード(XX)
+ 州または省
+ 市区町村
+ 街
+ ファイル名は空にできません
+ ファイル名に以下の文字を含めることはできません:\\ / : * ? " < > |
+ ファイル名が長すぎます
+ パスワードは少なくとも%d文字以上である必要があります
+ エイリアスは空にできません
+ 有効期限の年数は空にできません
+ 有効期限の年数は0にできません
+ 国コードは2文字の大文字でなければなりません
+ 「氏名、組織名、組織単位、国コード、州または省、市区町村、街」のいずれかの項目を入力する必要があります
+ 署名キーが正常に作成されました
+ 署名キーの作成に失敗しました
+ キーストアは確認されていません
+ キーストアは確認されました
+ 確認
+ すべて削除
+ 未確認
+ 確認済み
+ キーストアを確認
+ 確認成功
+ 確認失敗
+ ファイルパス
+ 必要な権限
+ アプリにWi-Fiの状態へのアクセスを許可する
+ アプリに外部ストレージへの書き込みを許可する
+ アプリに外部ストレージからの読み取りを許可する
+ アプリに共有ストレージから画像ファイルの読み取りを許可する
+ アプリに共有ストレージから動画ファイルの読み取りを許可する
+ アプリに共有ストレージから音声ファイルの読み取りを許可する
+ アプリに外部ストレージ内のすべてのファイルを管理する権限を与える
+ アプリにシステムダイアログを閉じるためのインテントを送信する権限を与える
+ アプリにすべての電話状態情報にアクセスする権限を与える
+ アプリにショートカットのインストールを許可する
+ アプリにショートカットのアンインストールを許可する
+ アプリに他のアプリのインストールを要求する権限を与える
+ アプリに電話をスリープ状態にしないようにする権限を与える
+ アプリに正確なアラームやイベントリマインダーを設定する権限を与える
+ アプリに時間精度のある操作をスケジュールする権限を与える
+ アプリに電話の振動を制御する権限を与える
+ アプリにインターネットにアクセスする権限を与える
+ アプリにネットワーク接続状態を確認する権限を与える
+ アプリに他のアプリの上に表示する権限を与える
+ アプリに実行中のタスクを再配置する権限を与える
+ アプリにバッテリー最適化を無視するように要求する権限を与える
+ アプリにデバイス起動完了の通知を受け取る権限を与える
+ アプリに通知を表示する権限を与える
+ アプリにフォアグラウンドサービスを実行する権限を与える
+ アプリにフォアグラウンドサービス内で画面録画とメディアプロジェクションを使用する権限を与える
+ アプリにフォアグラウンドサービス内で特別な操作を行う権限を与える
+ アプリに無制限のToast通知を表示する権限を与える
+ アプリにビデオ出力をキャプチャする権限を与える
+ アプリにシステム情報をダンプする権限を与える
+ アプリにすべてのパッケージをクエリする権限を与える
+ アプリに他のアプリの使用統計情報にアクセスする権限を与える
+ アプリにシステム設定を変更する権限を与える
+ アプリにシステムのセキュア設定を読み書きする権限を与える
+ アプリにユーザー管理を行う権限を与える
+ アプリにユーザー間でのインタラクションを許可する
+ アプリにネットワークを使用して粗い位置情報にアクセスする権限を与える
+ アプリにGPSを使用して正確な位置情報にアクセスする権限を与える
+ アプリに音声を録音する権限を与える
+ Termuxアプリに特化したコマンド実行権限を与える
+ アプリに電話の状態とIDを読み取る権限を与える
+ アプリにSMSを読み取る権限を与える
+ アプリにShizukuサービスを通じてシステムと高い権限でインタラクションする権限を与える
ウィンドウの切り替え
シンボル設定
テーラーメイド開発者
diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml
index 46b66fa9..abeb5d40 100644
--- a/app/src/main/res/values-ko/strings.xml
+++ b/app/src/main/res/values-ko/strings.xml
@@ -759,6 +759,88 @@
ABI (32 비트)
ABI (64 비트)
라이브러리
+ 서명 설정
+ 키 저장소
+ 기본 키 저장소
+ 키 저장소 관리
+ 서명 스킴
+ 서명 키 생성
+ 키 저장소 비밀번호
+ 별칭
+ 별칭 비밀번호
+ 서명 알고리즘
+ 유효 기간 (년)
+ 성명
+ 조직명
+ 조직 단위
+ 국가 코드 (XX)
+ 주 또는 도
+ 시 또는 지역
+ 거리
+ 파일 이름은 비워둘 수 없습니다
+ 파일 이름에 다음 문자를 포함할 수 없습니다: \\ / : * ? " < > |
+ 파일 이름이 너무 깁니다
+ 비밀번호는 최소 %d자 이상이어야 합니다
+ 별칭은 비워둘 수 없습니다
+ 유효 기간을 입력해야 합니다
+ 유효 기간은 0일 수 없습니다
+ 국가 코드는 두 개의 대문자로 입력해야 합니다
+ "성명, 조직명, 조직 단위, 국가 코드, 주 또는 도, 시 또는 지역, 거리" 중 하나 이상을 입력해야 합니다
+ 서명 키가 성공적으로 생성되었습니다
+ 서명 키 생성에 실패했습니다
+ 키 저장소가 확인되지 않았습니다
+ 키 저장소가 확인되었습니다
+ 확인
+ 모두 삭제
+ 미확인
+ 확인됨
+ 키 저장소 확인
+ 확인 성공
+ 확인 실패
+ 파일 경로
+ 필요한 권한
+ 앱이 Wi-Fi 상태에 접근할 수 있도록 허용
+ 앱이 외부 저장소에 쓸 수 있도록 허용
+ 앱이 외부 저장소를 읽을 수 있도록 허용
+ 앱이 공유 저장소의 이미지 파일을 읽을 수 있도록 허용
+ 앱이 공유 저장소의 비디오 파일을 읽을 수 있도록 허용
+ 앱이 공유 저장소의 오디오 파일을 읽을 수 있도록 허용
+ 앱이 외부 저장소의 모든 파일을 관리할 수 있도록 허용
+ 앱이 시스템 대화상자를 닫기 위한 인텐트를 보낼 수 있도록 허용
+ 앱이 모든 전화 상태 정보를 읽을 수 있도록 허용
+ 앱이 바로가기를 설치할 수 있도록 허용
+ 앱이 바로가기를 삭제할 수 있도록 허용
+ 앱이 다른 앱 설치를 요청할 수 있도록 허용
+ 앱이 전화기를 절전 모드로 진입하지 않도록 방지
+ 앱이 정확한 알람이나 이벤트 알림을 설정할 수 있도록 허용
+ 앱이 시간 정확한 작업을 예약할 수 있도록 허용
+ 앱이 전화기 진동을 제어할 수 있도록 허용
+ 앱이 인터넷에 접근할 수 있도록 허용
+ 앱이 네트워크 상태를 확인할 수 있도록 허용
+ 앱이 다른 앱 위에 표시될 수 있도록 허용
+ 앱이 실행 중인 작업을 재배치할 수 있도록 허용
+ 앱이 배터리 최적화를 무시하도록 요청할 수 있도록 허용
+ 앱이 장치 부팅 완료 시 브로드캐스트를 받을 수 있도록 허용
+ 앱이 알림을 게시할 수 있도록 허용
+ 앱이 포그라운드 서비스를 실행할 수 있도록 허용
+ 앱이 포그라운드 서비스에서 화면 캡처와 미디어 투영 기능을 사용할 수 있도록 허용
+ 앱이 포그라운드 서비스에서 특별한 작업을 수행할 수 있도록 허용
+ 앱이 무제한으로 Toast 알림을 표시할 수 있도록 허용
+ 앱이 비디오 출력을 캡처할 수 있도록 허용
+ 앱이 시스템 정보를 덤프할 수 있도록 허용
+ 앱이 모든 패키지를 쿼리할 수 있도록 허용
+ 앱이 다른 앱의 사용 통계를 볼 수 있도록 허용
+ 앱이 시스템 설정을 수정할 수 있도록 허용
+ 앱이 시스템 보안 설정을 읽고 쓸 수 있도록 허용
+ 앱이 사용자 관리를 할 수 있도록 허용
+ 앱이 사용자 간 상호작용을 할 수 있도록 허용
+ 앱이 네트워크를 통해 대략적인 위치에 접근할 수 있도록 허용
+ 앱이 GPS를 통해 정확한 위치에 접근할 수 있도록 허용
+ 앱이 오디오를 녹음할 수 있도록 허용
+ Termux 앱에 대해 명령을 실행할 수 있도록 허용
+ 앱이 전화 상태 및 신원을 읽을 수 있도록 허용
+ 앱이 SMS를 읽을 수 있도록 허용
+ 앱이 Shizuku 서비스를 통해 시스템과 상호작용할 수 있도록 허용
창 전환
기호 설정
맞춤형 개발자
diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
index 98605f99..5c70619e 100644
--- a/app/src/main/res/values-ru/strings.xml
+++ b/app/src/main/res/values-ru/strings.xml
@@ -758,6 +758,88 @@
ABIs (32-bit)
ABIs (64-bit)
Библиотеки
+ Конфигурация подписи
+ Хранилище ключей
+ По умолчанию хранилище ключей
+ Управление хранилищем ключей
+ Схема подписи
+ Создать ключ подписи
+ Пароль хранилища ключей
+ Псевдоним
+ Пароль псевдонима
+ Алгоритм подписи
+ Срок действия (годы)
+ Полное имя
+ Название организации
+ Организационная единица
+ Код страны (XX)
+ Штат или провинция
+ Город или населенный пункт
+ Улица
+ Имя файла не может быть пустым
+ Имя файла не может содержать следующие символы: \\ / : * ? " < > |
+ Имя файла слишком длинное
+ Пароль должен содержать не менее %d символов
+ Псевдоним не может быть пустым
+ Срок действия не может быть пустым
+ Срок действия не может быть равен нулю
+ Код страны должен состоять из двух заглавных букв
+ Необходимо заполнить хотя бы одно поле из: «Полное имя, Название организации, Организационная единица, Код страны, Штат или провинция, Город или населенный пункт, Улица»
+ Ключ подписи успешно создан
+ Не удалось создать ключ подписи
+ Хранилище ключей не подтверждено
+ Хранилище ключей подтверждено
+ Проверить
+ Удалить все
+ Не подтверждено
+ Подтверждено
+ Проверить хранилище ключей
+ Проверка успешна
+ Ошибка проверки
+ Путь к файлу
+ Требуемые разрешения
+ Разрешить приложению доступ к состоянию Wi-Fi
+ Разрешить приложению записывать на внешнее хранилище
+ Разрешить приложению читать с внешнего хранилища
+ Разрешить приложению читать изображения из общего хранилища
+ Разрешить приложению читать видеофайлы из общего хранилища
+ Разрешить приложению читать аудиофайлы из общего хранилища
+ Разрешить приложению управлять всеми файлами на внешнем хранилище
+ Разрешить приложению отправить намерение для закрытия системных диалогов
+ Разрешить приложению доступ ко всем данным состояния телефона
+ Разрешить приложению устанавливать ярлыки
+ Разрешить приложению удалять ярлыки
+ Разрешить приложению запрашивать установку других приложений
+ Предотвратить переход устройства в спящий режим
+ Разрешить приложению устанавливать точные будильники или напоминания
+ Разрешить приложению планировать точные операции по времени
+ Разрешить приложению управлять вибрацией устройства
+ Разрешить приложению доступ в Интернет
+ Разрешить приложению проверять состояние сетевого подключения
+ Разрешить приложению отображаться поверх других приложений
+ Разрешить приложению изменять порядок работающих задач
+ Разрешить приложению запросить игнорирование оптимизаций батареи
+ Разрешить приложению получать уведомления при завершении загрузки устройства
+ Разрешить приложению отображать уведомления
+ Разрешить приложению запускать сервисы в фоновом режиме
+ Разрешить приложению использовать запись экрана и медиапроекцию в фоновом сервисе
+ Разрешить приложению выполнять специальные операции в фоновом сервисе
+ Разрешить приложению показывать неограниченное количество уведомлений Toast
+ Разрешить приложению захватывать видеовыход
+ Разрешить приложению выгружать системные данные
+ Разрешить приложению запрашивать все пакеты
+ Разрешить приложению доступ к статистике использования других приложений
+ Разрешить приложению изменять настройки системы
+ Разрешить приложению читать/записывать защищенные настройки системы
+ Разрешить приложению управлять пользователями
+ Разрешить приложению взаимодействовать между пользователями
+ Разрешить приложению доступ к неточной геопозиции через сеть
+ Разрешить приложению доступ к точной геопозиции через GPS
+ Разрешить приложению записывать звук
+ Для приложения Termux разрешить выполнение команд
+ Разрешить приложению читать состояние телефона и его идентификатор
+ Разрешить приложению читать SMS-сообщения
+ Разрешить приложению взаимодействовать с системой через сервис Shizuku для получения повышенных прав
Переключить окно
Настройки символов
Индивидуальный разработчик
diff --git a/app/src/main/res/values-zh-rHK/strings.xml b/app/src/main/res/values-zh-rHK/strings.xml
index 61975e3f..22a91a6c 100644
--- a/app/src/main/res/values-zh-rHK/strings.xml
+++ b/app/src/main/res/values-zh-rHK/strings.xml
@@ -757,6 +757,88 @@
支持 ABI (32 位)
支持 ABI (64 位)
支援庫
+ 簽名配置
+ 密鑰庫
+ 預設密鑰庫
+ 管理密鑰庫
+ 簽名方案
+ 生成簽名密鑰
+ 密鑰庫密碼
+ 別名
+ 別名密碼
+ 簽名算法
+ 有效期 (年)
+ 姓名
+ 組織名稱
+ 組織單位
+ 國家代碼 (XX)
+ 州或省份
+ 城市或區域
+ 街道
+ 文件名不能為空
+ 文件名不能包含以下字符: \\ / : * ? " < > |
+ 文件名太長
+ 密碼至少需要%d位字符
+ 別名不能為空
+ 有效年份不能為空
+ 有效年份不能為0
+ 國家代碼必須為兩個大寫字母
+ “姓名、組織名稱、組織單位、國家代碼、州或省份、城市或區域、街道”至少填寫一個
+ 成功生成簽名密鑰
+ 生成簽名密鑰失敗
+ 密鑰尚未驗證
+ 密鑰已驗證
+ 驗證
+ 刪除全部
+ 未驗證
+ 已驗證
+ 驗證簽名密鑰
+ 驗證成功
+ 驗證失敗
+ 文件路徑
+ 所需的權限
+ 允許應用程式訪問Wi-Fi狀態
+ 允許應用程式寫入外部儲存
+ 允許應用程式讀取外部儲存
+ 允許應用程式讀取共享儲存中的圖片檔案
+ 允許應用程式讀取共享儲存中的視頻檔案
+ 允許應用程式讀取共享儲存中的音頻檔案
+ 允許應用程式管理外部儲存中的所有檔案
+ 允許應用程式發送意圖來關閉系統對話框
+ 允許應用程式訪問所有電話狀態資訊
+ 允許應用程式安裝快捷方式
+ 允許應用程式卸載快捷方式
+ 允許應用程式請求安裝其他應用程式
+ 防止手機進入休眠狀態
+ 允許應用程式安排精確的鬧鐘或事件提醒
+ 允許應用程式安排時間精確的操作
+ 控制手機震動
+ 允許應用程式訪問網路
+ 允許應用程式查看網路連接狀態
+ 允許應用程式在其他應用程式上面顯示
+ 允許應用程式對正在運行的應用程式重新排序
+ 允許應用程式請求忽略電池優化
+ 允許應用程式在設備啟動時接收廣播
+ 允許應用程式顯示通知
+ 允許應用程式運行前景服務
+ 允許應用程式在前景服務中使用螢幕錄製和媒體投影功能
+ 允許應用程式在前景服務中執行一些特殊操作
+ 允許應用程式顯示無限數量的Toast通知
+ 允許應用程式捕獲視頻輸出
+ 允許應用程式轉儲系統資訊
+ 允許應用程式查詢所有軟件包
+ 允許應用程式訪問其他應用程式的使用統計數據
+ 允許應用程式修改系統設定
+ 允許應用程式讀寫系統敏感設定
+ 允許應用程式管理使用者
+ 允許應用程式跨使用者互動
+ 允許應用程式通過網路訪問粗略的位置信息
+ 允許應用程式通過GPS獲取精確的位置信息
+ 允許應用程式錄音
+ 特定於Termux應用程式,允許執行命令
+ 允許應用程式讀取手機狀態和身份
+ 允許應用程式讀取簡訊
+ 允許應用程式通過Shizuku服務與系統進行更高權限的交互
切換窗口
符號設置
二次開發者
diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml
index 1e12745a..7506ea93 100644
--- a/app/src/main/res/values-zh-rTW/strings.xml
+++ b/app/src/main/res/values-zh-rTW/strings.xml
@@ -757,6 +757,88 @@
支援 ABI (32 位)
支援 ABI (64 位)
支援庫
+ 簽名設定
+ 密鑰庫
+ 預設密鑰庫
+ 管理密鑰庫
+ 簽名方案
+ 生成簽名密鑰
+ 密鑰庫密碼
+ 別名
+ 別名密碼
+ 簽名演算法
+ 有效期 (年)
+ 姓名
+ 組織名稱
+ 組織單位
+ 國家代碼 (XX)
+ 州或省份
+ 城市或區域
+ 街道
+ 檔案名稱不能為空
+ 檔案名稱不能包含以下字符: \\ / : * ? " < > |
+ 檔案名稱太長
+ 密碼至少需要%d個字元
+ 別名不能為空
+ 有效年份不能為空
+ 有效年份不能為0
+ 國家代碼必須是兩個大寫字母
+ “姓名、組織名稱、組織單位、國家代碼、州或省份、城市或區域、街道”至少填寫一個
+ 成功生成簽名密鑰
+ 生成簽名密鑰失敗
+ 密鑰尚未驗證
+ 密鑰已驗證
+ 驗證
+ 刪除全部
+ 未驗證
+ 已驗證
+ 驗證簽名密鑰
+ 驗證成功
+ 驗證失敗
+ 檔案路徑
+ 所需的權限
+ 允許應用程式訪問Wi-Fi狀態
+ 允許應用程式寫入外部儲存
+ 允許應用程式讀取外部儲存
+ 允許應用程式讀取共享儲存中的圖片檔案
+ 允許應用程式讀取共享儲存中的影片檔案
+ 允許應用程式讀取共享儲存中的音頻檔案
+ 允許應用程式管理外部儲存中的所有檔案
+ 允許應用程式發送意圖來關閉系統對話框
+ 允許應用程式訪問所有電話狀態資訊
+ 允許應用程式安裝快捷方式
+ 允許應用程式卸載快捷方式
+ 允許應用程式請求安裝其他應用程式
+ 防止手機進入休眠狀態
+ 允許應用程式安排精確的鬧鐘或事件提醒
+ 允許應用程式安排時間精確的操作
+ 控制手機震動
+ 允許應用程式訪問網路
+ 允許應用程式查看網路連接狀態
+ 允許應用程式顯示在其他應用程式上層
+ 允許應用程式對正在運行的應用程式重新排序
+ 允許應用程式請求忽略電池優化
+ 允許應用程式在設備啟動時接收廣播
+ 允許應用程式顯示通知
+ 允許應用程式運行前景服務
+ 允許應用程式在前景服務中使用螢幕錄製和媒體投影功能
+ 允許應用程式在前景服務中執行一些特殊操作
+ 允許應用程式顯示無限數量的Toast通知
+ 允許應用程式捕獲視頻輸出
+ 允許應用程式轉儲系統資訊
+ 允許應用程式查詢所有軟件包
+ 允許應用程式訪問其他應用程式的使用統計數據
+ 允許應用程式修改系統設定
+ 允許應用程式讀寫系統敏感設定
+ 允許應用程式管理使用者
+ 允許應用程式跨使用者互動
+ 允許應用程式通過網路訪問粗略的位置信息
+ 允許應用程式通過GPS獲取精確的位置信息
+ 允許應用程式錄音
+ 特定於Termux應用程式,允許執行命令
+ 允許應用程式讀取手機狀態和身份
+ 允許應用程式讀取簡訊
+ 允許應用程式通過Shizuku服務與系統進行更高權限的交互
切換視窗
符號設定
二次開發者
diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml
index 0e5239ab..98ade9a5 100644
--- a/app/src/main/res/values-zh/strings.xml
+++ b/app/src/main/res/values-zh/strings.xml
@@ -753,6 +753,88 @@
支持 ABI (32 位)
支持 ABI (64 位)
支持库
+ 签名配置
+ 密钥库
+ 默认密钥库
+ 管理密钥库
+ 签名方案
+ 生成签名密钥
+ 密钥库密码
+ 别名
+ 别名密码
+ 签名算法
+ 有效期 (年)
+ 姓名
+ 组织名称
+ 组织单位
+ 国家代码 (XX)
+ 州或省份
+ 城市或区域
+ 街道
+ 文件名不能为空
+ 文件名不能包含下列任何字符: \\ / : * ? " < > |
+ 文件名太长
+ 密码至少需要%d位字符
+ 别名不能为空
+ 有效年份不能为空
+ 有效年份不能为0
+ 国家代码必须为两个大写字母
+ “姓名、组织名称、组织单位、国家代码、州或省份、城市或区域、街道”至少填写一个
+ 成功生成签名密钥
+ 生成签名密钥失败
+ 密钥尚未验证
+ 密钥已验证
+ 验证
+ 删除全部
+ 未验证
+ 已验证
+ 验证签名密钥
+ 验证成功
+ 验证失败
+ 文件路径
+ 需要的权限
+ 允许应用访问Wi-Fi状态
+ 允许应用写入外部存储
+ 允许应用读取外部存储
+ 允许应用读取共享存储中的图片文件
+ 允许应用读取共享存储中的视频文件
+ 允许应用读取共享存储中的音频文件
+ 允许应用管理外部存储中的所有文件
+ 允许应用发送一个意图来关闭系统对话框
+ 允许应用访问所有电话状态信息
+ 允许应用安装快捷方式
+ 允许应用卸载快捷方式
+ 允许应用请求安装其他应用
+ 防止手机进入休眠状态
+ 允许应用安排精确的闹钟或事件提醒
+ 允许应用安排时间精确的操作
+ 控制手机振动
+ 允许应用访问网络
+ 允许应用查看网络连接状态
+ 允许应用显示在其他应用的上层
+ 允许应用对正在运行的应用重新排序
+ 允许应用请求忽略电池优化
+ 允许应用在设备启动时接收广播
+ 允许应用显示通知
+ 允许应用运行前台服务
+ 允许应用在前台服务中使用屏幕录制和媒体投影功能
+ 允许应用在前台服务中执行一些特殊操作
+ 允许应用显示无限数量的Toast通知
+ 允许应用捕获视频输出
+ 允许应用转储系统信息
+ 允许应用查询所有软件包
+ 允许应用访问其他应用的使用统计数据
+ 允许应用修改系统设置
+ 允许应用读写系统敏感设置
+ 允许应用管理用户
+ 允许应用跨用户交互
+ 允许应用通过网络访问粗略的位置信息
+ 允许应用通过GPS获取精确的位置信息
+ 允许应用录音
+ 特定于Termux应用,允许运行命令
+ 允许应用读取手机状态和身份
+ 允许应用读取短信
+ 允许应用通过 Shizuku 服务与系统进行更高权限的交互
切换窗口
符号设置
二次开发者
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 89065012..d1635944 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -966,6 +966,94 @@
ABIs (32-bit)
ABIs (64-bit)
Libraries
+ JKS
+ BKS
+ key0
+ 25
+ CN
+ %s: %s
+ Signature Configuration
+ Keystore
+ Default Keystore
+ Manage Keystore
+ Signature Scheme
+ New Key Store
+ Key Store Password
+ Alias
+ Alias Password
+ Signature Algorithm
+ Validity (Years)
+ Full Name
+ Organization Name
+ Organizational Unit
+ Country Code (XX)
+ State or Province
+ City or Locality
+ Street
+ Filename cannot be empty
+ Filename cannot contain the following characters: \\ / : * ? " < > |
+ Filename is too long
+ Password must be at least %d characters long
+ Alias cannot be empty
+ Validity years cannot be empty
+ Validity years cannot be zero
+ Country code must be two capital letters
+ At least one field from \"Full Name, Organization Name, Organizational Unit, Country Code, State or Province, City or Locality, Street\" must be filled
+ Successfully created signature key
+ Failed to create signature key
+ Keystore has not been verified
+ Keystore has been verified
+ Verify
+ Delete All
+ Unverified
+ Verified
+ Verify Keystore
+ Verification successful
+ Verification failed
+ File Path
+ Permissions
+ Allow the app to access Wi-Fi state
+ Allow the app to write to external storage
+ Allow the app to read from external storage
+ Allow the app to read image files from shared storage
+ Allow the app to read video files from shared storage
+ Allow the app to read audio files from shared storage
+ Allow the app to manage all files in external storage
+ Allow the app to send an intent to close system dialogs
+ Allow the app to access all phone state information
+ Allow the app to install shortcuts
+ Allow the app to uninstall shortcuts
+ Allow the app to request installation of other apps
+ Prevent the phone from going to sleep
+ Allow the app to schedule exact alarms or event reminders
+ Allow the app to schedule time-precise operations
+ Allow the app to control phone vibration
+ Allow the app to access the internet
+ Allow the app to view network connection status
+ Allow the app to display over other apps
+ Allow the app to reorder running tasks
+ Allow the app to request ignoring battery optimizations
+ Allow the app to receive broadcast when device boot completes
+ Allow the app to post notifications
+ Allow the app to run foreground services
+ Allow the app to use screen recording and media projection in foreground services
+ Allow the app to perform special operations in foreground services
+ Allow the app to show unlimited Toast notifications
+ Allow the app to capture video output
+ Allow the app to dump system information
+ Allow the app to query all packages
+ Allow the app to access usage statistics of other apps
+ Allow the app to modify system settings
+ Allow the app to read/write secure system settings
+ Allow the app to manage users
+ Allow the app to interact across users
+ Allow the app to access approximate location via network
+ Allow the app to access precise location via GPS
+ Allow the app to record audio
+ Specific to Termux app, allow running commands
+ Allow the app to read phone state and identity
+ Allow the app to read SMS
+ Allow the app to interact with the system with elevated permissions via Shizuku service
Switch window
Symbols settings
Tailor-made developer