新增打包签名配置和密钥库管理

- 打包功能新增签名配置功能,支持设置签名密钥和签名方案(V1、V1 + V2等)
- 新增密钥库管理,支持创建和删除自定义签名密钥库等
This commit is contained in:
luckyloogn
2024-12-31 19:52:24 +08:00
parent 09c1069d16
commit 925f22ccc8
37 changed files with 2276 additions and 1 deletions

View File

@@ -193,6 +193,14 @@ dependencies /* Unclassified */ {
// Toaster // Toaster
implementation("com.github.getActivity:Toaster:12.6") implementation("com.github.getActivity:Toaster:12.6")
implementation("com.github.getActivity:EasyWindow:10.3") 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 */ { dependencies /* MIME */ {

View File

@@ -256,6 +256,9 @@
<activity android:name="org.autojs.autojs.ui.project.ProjectConfigActivity" /> <activity android:name="org.autojs.autojs.ui.project.ProjectConfigActivity" />
<activity
android:name="org.autojs.autojs.ui.keystore.ManageKeyStoreActivity" />
<activity <activity
android:name="org.autojs.autojs.ui.log.LogActivity" android:name="org.autojs.autojs.ui.log.LogActivity"
android:exported="true" android:exported="true"

Binary file not shown.

View File

@@ -8,7 +8,10 @@ import android.graphics.Bitmap
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import android.os.Build import android.os.Build
import android.util.Log import android.util.Log
import com.mcal.apksigner.ApkSigner
import com.reandroid.arsc.chunk.TableBlock import com.reandroid.arsc.chunk.TableBlock
import org.apache.commons.io.FileUtils.copyFile
import org.apache.commons.io.FileUtils.copyInputStreamToFile
import org.autojs.autojs.app.GlobalAppContext import org.autojs.autojs.app.GlobalAppContext
import org.autojs.autojs.engine.encryption.AdvancedEncryptionStandard import org.autojs.autojs.engine.encryption.AdvancedEncryptionStandard
import org.autojs.autojs.pio.PFiles import org.autojs.autojs.pio.PFiles
@@ -16,6 +19,7 @@ import org.autojs.autojs.project.BuildInfo
import org.autojs.autojs.project.ProjectConfig import org.autojs.autojs.project.ProjectConfig
import org.autojs.autojs.script.EncryptedScriptFileHeader.writeHeader import org.autojs.autojs.script.EncryptedScriptFileHeader.writeHeader
import org.autojs.autojs.script.JavaScriptFileSource import org.autojs.autojs.script.JavaScriptFileSource
import org.autojs.autojs.apkbuilder.keystore.KeyStore
import org.autojs.autojs.util.FileUtils.TYPE.JAVASCRIPT import org.autojs.autojs.util.FileUtils.TYPE.JAVASCRIPT
import org.autojs.autojs.util.MD5Utils import org.autojs.autojs.util.MD5Utils
import org.autojs.autojs6.BuildConfig import org.autojs.autojs6.BuildConfig
@@ -269,6 +273,40 @@ open class ApkBuilder(apkInputStream: InputStream?, private val outApkFile: File
val fos = FileOutputStream(outApkFile) val fos = FileOutputStream(outApkFile)
TinySign.sign(File(workspacePath), fos) TinySign.sign(File(workspacePath), fos)
fos.close() fos.close()
val defaultKeyStoreFile = File(workspacePath, "default_key_store.bks")
val tmpOutputApk = File(workspacePath, "temp.apk")
copyInputStreamToFile(GlobalAppContext.get().assets.open("default_key_store.bks"), defaultKeyStoreFile)
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")
var keyStoreFile = defaultKeyStoreFile
var password = "AutoJs6"
var alias = "AutoJs6"
var aliasPassword = "AutoJs6"
mAppConfig.keyStore?.let {
keyStoreFile = File(it.absolutePath)
password = it.password
alias = it.alias
aliasPassword = it.aliasPassword
}
// 使用 ApkSigner 重新签名
if (!signer.signRelease(keyStoreFile, password, alias, aliasPassword)) {
throw java.lang.RuntimeException("Failed to re-sign using ApkSigner")
}
try {
copyFile(tmpOutputApk, outApkFile)
} catch (e: java.lang.Exception) {
throw java.lang.RuntimeException(e)
}
} }
fun cleanWorkspace() = also { fun cleanWorkspace() = also {
@@ -321,6 +359,10 @@ open class ApkBuilder(apkInputStream: InputStream?, private val outApkFile: File
private set private set
var libs: List<String> = emptyList() var libs: List<String> = emptyList()
private set private set
var keyStore: KeyStore? = null
private set
var signatureSchemes: String = "V1 + V2"
private set
fun ignoreDir(dir: File) = also { ignoredDirs.add(dir) } fun ignoreDir(dir: File) = also { ignoredDirs.add(dir) }
@@ -342,6 +384,10 @@ open class ApkBuilder(apkInputStream: InputStream?, private val outApkFile: File
fun setLibs(libs: List<String>) = also { this.libs = libs } 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 }
companion object { companion object {
@JvmStatic @JvmStatic
fun fromProjectConfig(projectDir: String?, projectConfig: ProjectConfig) = AppConfig() fun fromProjectConfig(projectDir: String?, projectConfig: ProjectConfig) = AppConfig()

View File

@@ -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
}

View File

@@ -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<KeyStore>
@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()
}

View File

@@ -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
}
}
}
}

View File

@@ -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<KeyStore> {
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()
}
}
}

View File

@@ -178,7 +178,7 @@ public class ConsoleView extends FrameLayout implements ConsoleImpl.LogListener
return /* default text size */ 14; return /* default text size */ 14;
} }
public void setTextColors(@NotNull Integer[] colors) { public void setTextColors(Integer[] colors) {
Adapter adapter = (Adapter) mLogListRecyclerView.getAdapter(); Adapter adapter = (Adapter) mLogListRecyclerView.getAdapter();
if (adapter != null) { if (adapter != null) {
adapter.setTextColors(colors); adapter.setTextColors(colors);

View File

@@ -172,6 +172,11 @@ object Pref {
@ScriptInterfaceCompatible @ScriptInterfaceCompatible
fun getScriptDirPath() = WorkingDirectoryUtils.path fun getScriptDirPath() = WorkingDirectoryUtils.path
@JvmStatic
fun getKeyStorePath(): String {
return getScriptDirPath() + "/.KeyStore/"
}
@JvmStatic @JvmStatic
fun registerOnSharedPreferenceChangeListener(listener: OnSharedPreferenceChangeListener) { fun registerOnSharedPreferenceChangeListener(listener: OnSharedPreferenceChangeListener) {
sPref.registerOnSharedPreferenceChangeListener(listener) sPref.registerOnSharedPreferenceChangeListener(listener)

View File

@@ -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<KeyStore, KeyStoreAdaptor.KeyStoreViewHolder>(KeyStoreDiffCallback()) {
class KeyStoreDiffCallback : DiffUtil.ItemCallback<KeyStore>() {
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)
}
}

View File

@@ -0,0 +1,266 @@
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.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 = configs.password,
alias = configs.alias,
aliasPassword = 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 = configs.password,
alias = configs.alias,
aliasPassword = 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()
}
}

View File

@@ -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)
}
}

View File

@@ -0,0 +1,133 @@
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.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(keyStore.password)
binding.alias.setText(keyStore.alias)
binding.aliasPassword.setText(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)
}
}

View File

@@ -11,12 +11,16 @@ import android.text.util.Linkify;
import android.util.Log; import android.util.Log;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.View; import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.EditText; import android.widget.EditText;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import android.widget.Spinner;
import android.widget.TextView; import android.widget.TextView;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.lifecycle.ViewModelProvider;
import com.afollestad.materialdialogs.MaterialDialog; import com.afollestad.materialdialogs.MaterialDialog;
import com.google.android.flexbox.FlexboxLayout; import com.google.android.flexbox.FlexboxLayout;
import com.google.android.material.textfield.TextInputLayout; import com.google.android.material.textfield.TextInputLayout;
@@ -34,6 +38,9 @@ import org.autojs.autojs.runtime.api.AppUtils.Companion.SimpleVersionInfo;
import org.autojs.autojs.ui.BaseActivity; import org.autojs.autojs.ui.BaseActivity;
import org.autojs.autojs.ui.common.NotAskAgainDialog; import org.autojs.autojs.ui.common.NotAskAgainDialog;
import org.autojs.autojs.ui.filechooser.FileChooserDialogBuilder; 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.shortcut.AppsIconSelectActivity;
import org.autojs.autojs.ui.widget.RoundCheckboxWithText; import org.autojs.autojs.ui.widget.RoundCheckboxWithText;
import org.autojs.autojs.util.AndroidUtils; import org.autojs.autojs.util.AndroidUtils;
@@ -109,6 +116,16 @@ public class BuildActivity extends BaseActivity implements ApkBuilder.ProgressCa
put(ApkBuilder.Constants.MLKIT_BARCODE, /* MLKit Barcode */ List.of("barcode", "mlkit-barcode", "mlkit_barcode")); put(ApkBuilder.Constants.MLKIT_BARCODE, /* MLKit Barcode */ List.of("barcode", "mlkit-barcode", "mlkit_barcode"));
}}; }};
private static final ArrayList<String> 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+)");
}};
EditText mSourcePath; EditText mSourcePath;
View mSourcePathContainer; View mSourcePathContainer;
EditText mOutputPath; EditText mOutputPath;
@@ -126,6 +143,8 @@ public class BuildActivity extends BaseActivity implements ApkBuilder.ProgressCa
private boolean mIsProjectLevelBuilding; private boolean mIsProjectLevelBuilding;
private FlexboxLayout mFlexboxAbis; private FlexboxLayout mFlexboxAbis;
private FlexboxLayout mFlexboxLibs; private FlexboxLayout mFlexboxLibs;
private Spinner mSignatureSchemes;
private Spinner mVerifiedKeyStores;
private final ArrayList<String> mInvalidAbis = new ArrayList<>(); private final ArrayList<String> mInvalidAbis = new ArrayList<>();
private final ArrayList<String> mUnavailableAbis = new ArrayList<>(); private final ArrayList<String> mUnavailableAbis = new ArrayList<>();
@@ -133,6 +152,8 @@ public class BuildActivity extends BaseActivity implements ApkBuilder.ProgressCa
private final ArrayList<String> mInvalidLibs = new ArrayList<>(); private final ArrayList<String> mInvalidLibs = new ArrayList<>();
private final ArrayList<String> mUnavailableLibs = new ArrayList<>(); private final ArrayList<String> mUnavailableLibs = new ArrayList<>();
private KeyStoreViewModel mKeyStoreViewModel;
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
@Override @Override
protected void onCreate(@Nullable Bundle savedInstanceState) { protected void onCreate(@Nullable Bundle savedInstanceState) {
@@ -195,6 +216,15 @@ public class BuildActivity extends BaseActivity implements ApkBuilder.ProgressCa
mFlexboxLibs = binding.flexboxLibraries; mFlexboxLibs = binding.flexboxLibraries;
initLibsChildren(); initLibsChildren();
mKeyStoreViewModel = new ViewModelProvider(this, new KeyStoreViewModel.Factory(getApplicationContext())).get(KeyStoreViewModel.class);
mKeyStoreViewModel.updateVerifiedKeyStores();
mSignatureSchemes = binding.spinnerSignatureSchemes;
initSignatureSchemeSpinner();
mVerifiedKeyStores = binding.spinnerVerifiedKeyStores;
initVerifiedKeyStoresSpinner();
binding.fab.setOnClickListener(v -> buildApk()); binding.fab.setOnClickListener(v -> buildApk());
binding.selectSource.setOnClickListener(v -> selectSourceFilePath()); binding.selectSource.setOnClickListener(v -> selectSourceFilePath());
binding.selectOutput.setOnClickListener(v -> selectOutputDirPath()); binding.selectOutput.setOnClickListener(v -> selectOutputDirPath());
@@ -204,6 +234,7 @@ public class BuildActivity extends BaseActivity implements ApkBuilder.ProgressCa
return true; return true;
}); });
binding.textLibs.setOnClickListener(v -> toggleAllFlexboxChildren(mFlexboxLibs)); binding.textLibs.setOnClickListener(v -> toggleAllFlexboxChildren(mFlexboxLibs));
binding.manageKeyStore.setOnClickListener(v -> ManageKeyStoreActivity.Companion.startActivity(this));
setToolbarAsBack(R.string.text_build_apk); setToolbarAsBack(R.string.text_build_apk);
mSource = getIntent().getStringExtra(EXTRA_SOURCE); mSource = getIntent().getStringExtra(EXTRA_SOURCE);
@@ -217,6 +248,12 @@ public class BuildActivity extends BaseActivity implements ApkBuilder.ProgressCa
showHintDialogIfNeeded(); showHintDialogIfNeeded();
} }
@Override
protected void onResume() {
super.onResume();
mKeyStoreViewModel.updateVerifiedKeyStores();
}
private void toggleAllFlexboxChildren(FlexboxLayout mFlexboxLibs) { private void toggleAllFlexboxChildren(FlexboxLayout mFlexboxLibs) {
boolean isAllChecked = true; boolean isAllChecked = true;
for (int i = 0; i < mFlexboxLibs.getChildCount(); i += 1) { for (int i = 0; i < mFlexboxLibs.getChildCount(); i += 1) {
@@ -341,6 +378,32 @@ public class BuildActivity extends BaseActivity implements ApkBuilder.ProgressCa
mInvalidLibs.addAll(candidates); mInvalidLibs.addAll(candidates);
} }
private void initSignatureSchemeSpinner() {
ArrayAdapter<String> 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<KeyStore> verifiedKeyStores = new ArrayList<>();
// 添加 默认密钥库 下拉选项
KeyStore defaultKeyStore = new KeyStore("", getString(R.string.text_default_key_store), "", "", "", false); // 仅用于显示下拉列表
verifiedKeyStores.add(defaultKeyStore);
ArrayAdapter<KeyStore> 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();
});
}
private boolean isAliasMatching(Map<String, List<String>> aliases, String aliasKey, List<String> candidates) { private boolean isAliasMatching(Map<String, List<String>> aliases, String aliasKey, List<String> candidates) {
AtomicBoolean result = new AtomicBoolean(false); AtomicBoolean result = new AtomicBoolean(false);
var aliasList = aliases.getOrDefault(aliasKey, Collections.emptyList()); var aliasList = aliases.getOrDefault(aliasKey, Collections.emptyList());
@@ -632,6 +695,12 @@ public class BuildActivity extends BaseActivity implements ApkBuilder.ProgressCa
appConfig.setAbis(abis); appConfig.setAbis(abis);
appConfig.setLibs(libs); appConfig.setLibs(libs);
appConfig.setSignatureSchemes(mSignatureSchemes.getSelectedItem().toString());
if (mVerifiedKeyStores.getSelectedItemPosition() > 0) {
appConfig.setKeyStore((KeyStore) mVerifiedKeyStores.getSelectedItem());
} else {
appConfig.setKeyStore(null);
}
return appConfig; return appConfig;
} }

View File

@@ -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<List<KeyStore>>()
val allKeyStores: LiveData<List<KeyStore>> get() = _allKeyStores
private val _verifiedKeyStores = MutableLiveData<List<KeyStore>>()
val verifiedKeyStores: LiveData<List<KeyStore>> get() = _verifiedKeyStores
init {
updateVerifiedKeyStores()
}
fun updateVerifiedKeyStores() {
viewModelScope.launch {
val keyStores = keyStoreRepository.getAllKeyStores()
val validKeyStores = mutableListOf<KeyStore>()
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<File>) {
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 <T : ViewModel> create(modelClass: Class<T>): T {
return KeyStoreViewModel(context) as T
}
}
}

View File

@@ -0,0 +1,19 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="48"
android:viewportHeight="48">
<path
android:fillColor="@android:color/white"
android:pathData="M16.9,25.2h0c-0.2,0.8 -0.6,2.1 -1.1,3.6h2.3c-0.6,-1.7 -1,-2.9 -1.2,-3.6Z" />
<path
android:fillColor="@android:color/white"
android:pathData="M32.1,5.5l-2.3,-2.3l-11.6,0l-2.3,2.3l-8.1,0l0,4.6l32.4,0l0,-4.6l-8.1,0z" />
<path
android:fillColor="@android:color/white"
android:pathData="M10.1,40.2c0,2.6 2.1,4.6 4.6,4.6h18.5c2.6,0 4.6,-2.1 4.6,-4.6V12.4H10.1v27.7ZM29.1,23.9h1.8v6.8h4.1l-0.2,1.5h-5.7v-8.3ZM22.1,23.9h1.8v6.8h4.1l-0.2,1.5h-5.7v-8.3ZM15.8,23.9h2.2l2.9,8.3h-1.9l-0.7,-1.9h-3l-0.6,1.9h-1.8l2.8,-8.3Z" />
</vector>

View File

@@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="48dp" android:tint="#999999" android:viewportHeight="24" android:viewportWidth="24" android:width="48dp">
<path android:fillColor="@android:color/white" android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:pathData="M512.2,32L947.2,155.8v261c0,253.3 -162.4,478.7 -404.9,565.1l-17,5.8 -13.2,4.3 -13.2,-4.3c-246.1,-79.9 -414.3,-301.3 -421.8,-553.3L76.8,416.8L76.8,155.8L512.2,32zM506,668.9a56.4,56.4 0,0 0,-41.2 15.6,51.6 51.6,0 0,0 -16.6,39.9c0,15.6 5.6,28.9 16.6,39.9a57,57 0,0 0,41.2 16.4c15.9,0 30.1,-5.5 41.2,-15.7a55.2,55.2 0,0 0,17.4 -40.6,51.6 51.6,0 0,0 -16.6,-39.9c-11.1,-10.9 -25.3,-15.6 -41.9,-15.6zM520.3,211.2c-58.6,0 -104.4,16.4 -137.7,50 -33.3,32.9 -49.9,77.4 -49.9,134.6h83.1c0,-34.4 7.1,-61 21.4,-79.8 15.8,-22.7 41.9,-33.7 77.5,-33.7 28.5,0 51.5,7.9 67.3,23.5 15,15.7 23,36.8 23,64.2 0,20.4 -7.9,39.1 -22.2,57.1l-13.4,14.8C520.3,485.1 490.2,517.1 480,539c-11.1,20.4 -15.8,45.4 -15.8,74.4v13.3h84.7v-13.3c0,-18.8 4,-36 12.7,-51.6 7,-14.1 18.2,-28.2 33.2,-40.7 37.1,-32.1 59.3,-52.4 66.4,-61 19.8,-25.1 30.1,-57.2 30.1,-95.5 0,-46.9 -15.8,-84.5 -47.5,-111.9 -31.6,-28.2 -72.8,-41.5 -123.4,-41.5z"
android:fillColor="#2D9AFF"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:pathData="M512.2,32L947.2,155.8v261c0,253.3 -162.4,478.7 -404.9,565.1l-17,5.8 -13.2,4.3 -13.2,-4.3c-246.1,-79.9 -414.3,-301.3 -421.8,-553.3L76.8,416.8V155.8L512.2,32zM726.4,320L459.8,582 304,428.9l-60.8,62.2L459.8,704 787.2,382.3 726.4,320z"
android:fillColor="#00C25A"/>
</vector>

View File

@@ -315,6 +315,88 @@
</LinearLayout> </LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingHorizontal="0dp"
android:paddingBottom="16dp">
<TextView
android:id="@+id/text_apk_sign_configs"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="20dp"
android:paddingHorizontal="16dp"
android:text="@string/text_signature_configuration"
android:textAppearance="@style/TextAppearance.AppCompat.Body2"
android:textColor="?android:textColorSecondary" />
<GridLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:columnCount="2"
android:orientation="horizontal"
android:paddingStart="16dp"
android:paddingEnd="0dp"
android:rowCount="3">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_row="0"
android:layout_column="0"
android:text="@string/text_signature_scheme" />
<Spinner
android:id="@+id/spinner_signature_schemes"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_row="0"
android:layout_column="1"
android:layout_columnWeight="1"
android:paddingVertical="4dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_row="1"
android:layout_column="0"
android:text="@string/text_key_store" />
<Spinner
android:id="@+id/spinner_verified_key_stores"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_row="1"
android:layout_column="1"
android:layout_columnWeight="1"
android:paddingVertical="4dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_row="2"
android:layout_column="0"
android:text="@string/text_others" />
<TextView
android:id="@+id/manage_key_store"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_row="2"
android:layout_column="1"
android:layout_columnWeight="1"
android:background="?selectableItemBackground"
android:paddingHorizontal="8dp"
android:paddingVertical="4dp"
android:text="@string/text_manage_key_store"
android:textColor="@color/colorPrimary"
android:textSize="16sp"
android:textStyle="bold" />
</GridLayout>
</LinearLayout>
<View <View
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="72dp" android:layout_height="72dp"

View File

@@ -0,0 +1,58 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay">
<org.autojs.autojs.theme.widget.ThemeColorToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/ToolBarStyle" />
</com.google.android.material.appbar.AppBarLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="8dp">
<org.autojs.autojs.theme.widget.ThemeColorSwipeRefreshLayout
android:id="@+id/swipe_refresh_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</ScrollView>
</org.autojs.autojs.theme.widget.ThemeColorSwipeRefreshLayout>
<org.autojs.autojs.theme.widget.ThemeColorFloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|bottom"
android:layout_margin="16dp"
android:alpha="0.9"
android:src="@drawable/ic_add_white_48dp"
android:tint="@color/toolbar_text"
app:backgroundTint="?attr/colorPrimary"
app:layout_anchor="@id/viewpager"
app:layout_anchorGravity="bottom|end"
app:layout_behavior="org.autojs.autojs.ui.widget.ScrollAwareFABBehavior" />
</FrameLayout>
</LinearLayout>

View File

@@ -0,0 +1,317 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="16dp"
android:orientation="vertical">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="top"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
android:layout_toStartOf="@+id/type_radio_group"
android:text="@string/text_new_key_store"
android:textColor="@color/text_color_primary"
android:textSize="24sp" />
<RadioGroup
android:id="@+id/type_radio_group"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:layout_marginEnd="4dp"
android:orientation="horizontal"
tools:ignore="RelativeOverlap">
<RadioButton
android:id="@+id/type_jks"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="true"
android:text="@string/text_jks"
android:textSize="16sp" />
<RadioButton
android:id="@+id/type_bks"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:text="@string/text_bks"
android:textSize="16sp" />
</RadioGroup>
</RelativeLayout>
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="8dp"
android:layout_weight="1">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:orientation="vertical">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/filename_text_input_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/text_file_name">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/filename"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/text_color_primary" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/password_text_input_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/text_key_store_password"
app:endIconMode="password_toggle">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPassword"
android:textColor="@color/text_color_primary" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/alias_text_input_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/text_key_alias">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/alias"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/text_default_alias"
android:textColor="@color/text_color_primary" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/alias_password_text_input_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/text_alias_password"
app:endIconMode="password_toggle">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/alias_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPassword"
android:textColor="@color/text_color_primary" />
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/more_options"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:layout_marginBottom="8dp"
android:gravity="center_vertical|start"
android:text="@string/text_more"
android:textAlignment="viewStart" />
<LinearLayout
android:id="@+id/more_options_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:orientation="vertical"
android:visibility="gone"
tools:visibility="visible">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_marginBottom="8dp"
android:text="@string/text_signature_algorithm"
android:textSize="11sp" />
<androidx.appcompat.widget.AppCompatSpinner
android:id="@+id/signature_algorithms"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="-4dp"
android:textAlignment="viewStart"
android:textColor="@color/text_color_primary"
android:textSize="16sp" />
</LinearLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/validity_years_text_input_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/text_validity_years">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/validity_years"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="number"
android:text="@string/text_default_validity_years"
android:textColor="@color/text_color_primary" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/first_and_last_name_text_input_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/text_first_and_last_name">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/first_and_last_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/text_color_primary" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/organization_text_input_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/text_organization">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/organization"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/text_color_primary" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/organizational_unit_text_input_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/text_organizational_unit">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/organizational_unit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/text_color_primary" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/country_code_text_input_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/text_country_code">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/country_code"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/text_default_country_code"
android:textColor="@color/text_color_primary" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/state_or_province_text_input_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/text_state_or_province">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/state_or_province"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/text_color_primary" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/city_or_locality_text_input_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/text_city_or_locality">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/city_or_locality"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/text_color_primary" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/street_text_input_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/text_street">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/street"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/text_color_primary" />
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
</LinearLayout>
</ScrollView>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:gravity="end"
android:orientation="horizontal">
<Button
android:id="@+id/cancel"
style="@style/Base.Widget.AppCompat.Button.Borderless.Colored"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_marginTop="8dp"
android:elegantTextHeight="true"
android:text="@string/text_cancel" />
<Button
android:id="@+id/confirm"
style="@style/Base.Widget.AppCompat.Button.Borderless.Colored"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_marginTop="8dp"
android:elegantTextHeight="true"
android:text="@string/text_ok" />
</LinearLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>

View File

@@ -0,0 +1,163 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="16dp"
android:orientation="vertical">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="top"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
android:layout_toStartOf="@+id/container_verify_state"
android:text="@string/text_verify_key_store"
android:textColor="@color/text_color_primary"
android:textSize="24sp" />
<LinearLayout
android:id="@+id/container_verify_state"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:gravity="center_horizontal"
android:orientation="vertical"
tools:ignore="RelativeOverlap,UseCompoundDrawables">
<ImageView
android:id="@+id/img_verify_state"
android:layout_width="32dp"
android:layout_height="32dp"
android:background="?selectableItemBackgroundBorderless"
android:contentDescription="@string/text_verify_key_store"
android:padding="4dp"
android:src="@drawable/ic_key_store_unverified"
tools:src="@drawable/ic_key_store_verified" />
<TextView
android:id="@+id/text_verify_state"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/text_unverified"
android:textColor="@color/text_color_primary"
android:textSize="12sp" />
</LinearLayout>
</RelativeLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="8dp"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_marginBottom="8dp"
android:text="@string/text_file_path"
android:textSize="11sp" />
<TextView
android:id="@+id/file_path"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:textSize="16sp"
tools:text="/storage/emulated/0/脚本/.KeyStore/default_key_store.jks" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginVertical="16dp"
android:orientation="vertical">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/password_text_input_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/text_key_store_password"
app:endIconMode="password_toggle">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPassword"
android:textColor="@color/text_color_primary" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/alias_text_input_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/text_key_alias">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/alias"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/text_default_alias"
android:textColor="@color/text_color_primary" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/alias_password_text_input_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/text_alias_password"
app:endIconMode="password_toggle">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/alias_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPassword"
android:textColor="@color/text_color_primary" />
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:gravity="end"
android:orientation="horizontal">
<Button
android:id="@+id/cancel"
style="@style/Base.Widget.AppCompat.Button.Borderless.Colored"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_marginTop="8dp"
android:elegantTextHeight="true"
android:text="@string/text_cancel" />
<Button
android:id="@+id/verify"
style="@style/Base.Widget.AppCompat.Button.Borderless.Colored"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_marginTop="8dp"
android:elegantTextHeight="true"
android:text="@string/text_verify" />
</LinearLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>

View File

@@ -0,0 +1,81 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="6dp"
android:layout_marginTop="3dp"
android:layout_marginEnd="6dp"
android:layout_marginBottom="3dp"
android:clickable="true"
android:focusable="true"
android:foreground="?attr/selectableItemBackground"
app:cardCornerRadius="4dp"
app:cardElevation="1dp"
app:cardUseCompatPadding="true">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="8dp">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
android:orientation="vertical"
tools:ignore="RelativeOverlap">
<TextView
android:id="@+id/filename"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/text_color_primary"
tools:text="filename" />
<TextView
android:id="@+id/alias"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/text_color_primary"
tools:text="filename" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:gravity="center_vertical"
android:orientation="horizontal">
<ImageView
android:id="@+id/verify"
android:layout_width="32dp"
android:layout_height="32dp"
android:background="?selectableItemBackgroundBorderless"
android:clickable="true"
android:contentDescription="@string/text_verify_key_store"
android:focusable="true"
android:padding="6dp"
android:src="@drawable/ic_key_store_unverified"
tools:src="@drawable/ic_key_store_verified" />
<ImageView
android:id="@+id/delete"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginStart="8dp"
android:background="?selectableItemBackgroundBorderless"
android:clickable="true"
android:contentDescription="@string/text_delete"
android:focusable="true"
android:padding="4dp"
android:src="@drawable/ic_key_store_delete"
app:tint="@color/md_red_500" />
</LinearLayout>
</RelativeLayout>
</androidx.cardview.widget.CardView>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_delete_all"
android:icon="@drawable/ic_delete_all"
android:title="@string/text_delete_all"
app:showAsAction="always" />
</menu>

View File

@@ -759,6 +759,45 @@
<string name="text_supported_abis_short_32bit">ABIs (32 بت)</string> <string name="text_supported_abis_short_32bit">ABIs (32 بت)</string>
<string name="text_supported_abis_short_64bit">ABIs (64 بت)</string> <string name="text_supported_abis_short_64bit">ABIs (64 بت)</string>
<string name="text_supported_libraries_short">المكتبات</string> <string name="text_supported_libraries_short">المكتبات</string>
<string name="text_signature_configuration">تكوين التوقيع</string>
<string name="text_key_store">مخزن المفاتيح</string>
<string name="text_default_key_store">مخزن المفاتيح الافتراضي</string>
<string name="text_manage_key_store">إدارة مخزن المفاتيح</string>
<string name="text_signature_scheme">خطة التوقيع</string>
<string name="text_new_key_store">إنشاء مفتاح التوقيع</string>
<string name="text_key_store_password">كلمة مرور مخزن المفاتيح</string>
<string name="text_key_alias">الاسم المستعار</string>
<string name="text_alias_password">كلمة مرور الاسم المستعار</string>
<string name="text_signature_algorithm">خوارزمية التوقيع</string>
<string name="text_validity_years">المدة (سنوات)</string>
<string name="text_first_and_last_name">الاسم</string>
<string name="text_organization">اسم المنظمة</string>
<string name="text_organizational_unit">وحدة التنظيم</string>
<string name="text_country_code">رمز الدولة (XX)</string>
<string name="text_state_or_province">الولاية أو المقاطعة</string>
<string name="text_city_or_locality">المدينة أو المنطقة</string>
<string name="text_street">الشارع</string>
<string name="text_filename_cannot_be_empty">اسم الملف لا يمكن أن يكون فارغًا</string>
<string name="text_filename_cannot_contain_invalid_character">اسم الملف لا يمكن أن يحتوي على أي من الأحرف التالية: \\ / : * ? &quot; &lt; &gt; |</string>
<string name="text_filename_is_too_long">اسم الملف طويل جدًا</string>
<string name="text_password_requires_at_least_n_characters">يجب أن يحتوي كلمة المرور على %d على الأقل من الأحرف</string>
<string name="text_alias_cannot_be_empty">الاسم المستعار لا يمكن أن يكون فارغًا</string>
<string name="text_validity_years_cannot_be_empty">مدة الصلاحية لا يمكن أن تكون فارغة</string>
<string name="text_validity_years_cannot_be_zero">مدة الصلاحية لا يمكن أن تكون صفرًا</string>
<string name="text_country_code_must_be_two_capital_letters">رمز الدولة يجب أن يكون حرفين كبيرين</string>
<string name="text_at_least_one_certificate_issuer_field_is_not_empty">يجب ملء حقل واحد على الأقل من "الاسم، اسم المنظمة، وحدة التنظيم، رمز الدولة، الولاية أو المقاطعة، المدينة أو المنطقة، الشارع"</string>
<string name="text_successfully_created_key_store">تم إنشاء مفتاح التوقيع بنجاح</string>
<string name="text_failed_to_create_key_store">فشل في إنشاء مفتاح التوقيع</string>
<string name="text_key_store_has_not_been_verified">لم يتم التحقق من المخزن</string>
<string name="text_key_store_has_been_verified">تم التحقق من المخزن</string>
<string name="text_verify">التحقق</string>
<string name="text_delete_all">حذف الكل</string>
<string name="text_unverified">لم يتم التحقق</string>
<string name="text_verified">تم التحقق</string>
<string name="text_verify_key_store">التحقق من مخزن المفاتيح</string>
<string name="text_verify_success">تم التحقق بنجاح</string>
<string name="text_verify_failed">فشل التحقق</string>
<string name="text_file_path">مسار الملف</string>
<string name="text_switch_window">تبديل النافذة</string> <string name="text_switch_window">تبديل النافذة</string>
<string name="text_symbols_settings">إعدادات الرموز</string> <string name="text_symbols_settings">إعدادات الرموز</string>
<string name="text_tailor_made_developer">مطور مصممة خصيصا</string> <string name="text_tailor_made_developer">مطور مصممة خصيصا</string>

View File

@@ -755,6 +755,45 @@
<string name="text_supported_abis_short_32bit">ABIs (32-bit)</string> <string name="text_supported_abis_short_32bit">ABIs (32-bit)</string>
<string name="text_supported_abis_short_64bit">ABIs (64-bit)</string> <string name="text_supported_abis_short_64bit">ABIs (64-bit)</string>
<string name="text_supported_libraries_short">Libraries</string> <string name="text_supported_libraries_short">Libraries</string>
<string name="text_signature_configuration">Signature Configuration</string>
<string name="text_key_store">Keystore</string>
<string name="text_default_key_store">Default Keystore</string>
<string name="text_manage_key_store">Manage Keystore</string>
<string name="text_signature_scheme">Signature Scheme</string>
<string name="text_new_key_store">New Key Store</string>
<string name="text_key_store_password">Key Store Password</string>
<string name="text_key_alias">Alias</string>
<string name="text_alias_password">Alias Password</string>
<string name="text_signature_algorithm">Signature Algorithm</string>
<string name="text_validity_years">Validity (Years)</string>
<string name="text_first_and_last_name">Full Name</string>
<string name="text_organization">Organization Name</string>
<string name="text_organizational_unit">Organizational Unit</string>
<string name="text_country_code">Country Code (XX)</string>
<string name="text_state_or_province">State or Province</string>
<string name="text_city_or_locality">City or Locality</string>
<string name="text_street">Street</string>
<string name="text_filename_cannot_be_empty">Filename cannot be empty</string>
<string name="text_filename_cannot_contain_invalid_character">Filename cannot contain the following characters: \\ / : * ? &quot; &lt; &gt; |</string>
<string name="text_filename_is_too_long">Filename is too long</string>
<string name="text_password_requires_at_least_n_characters">Password must be at least %d characters long</string>
<string name="text_alias_cannot_be_empty">Alias cannot be empty</string>
<string name="text_validity_years_cannot_be_empty">Validity years cannot be empty</string>
<string name="text_validity_years_cannot_be_zero">Validity years cannot be zero</string>
<string name="text_country_code_must_be_two_capital_letters">Country code must be two capital letters</string>
<string name="text_at_least_one_certificate_issuer_field_is_not_empty">At least one field from \"Full Name, Organization Name, Organizational Unit, Country Code, State or Province, City or Locality, Street\" must be filled</string>
<string name="text_successfully_created_key_store">Successfully created signature key</string>
<string name="text_failed_to_create_key_store">Failed to create signature key</string>
<string name="text_key_store_has_not_been_verified">Keystore has not been verified</string>
<string name="text_key_store_has_been_verified">Keystore has been verified</string>
<string name="text_verify">Verify</string>
<string name="text_delete_all">Delete All</string>
<string name="text_unverified">Unverified</string>
<string name="text_verified">Verified</string>
<string name="text_verify_key_store">Verify Keystore</string>
<string name="text_verify_success">Verification successful</string>
<string name="text_verify_failed">Verification failed</string>
<string name="text_file_path">File Path</string>
<string name="text_switch_window">Switch window</string> <string name="text_switch_window">Switch window</string>
<string name="text_symbols_settings">Symbols settings</string> <string name="text_symbols_settings">Symbols settings</string>
<string name="text_tailor_made_developer">Tailor-made developer</string> <string name="text_tailor_made_developer">Tailor-made developer</string>

View File

@@ -758,6 +758,45 @@
<string name="text_supported_abis_short_32bit">ABIs (32 bits)</string> <string name="text_supported_abis_short_32bit">ABIs (32 bits)</string>
<string name="text_supported_abis_short_64bit">ABIs (64 bits)</string> <string name="text_supported_abis_short_64bit">ABIs (64 bits)</string>
<string name="text_supported_libraries_short">Bibliotecas</string> <string name="text_supported_libraries_short">Bibliotecas</string>
<string name="text_signature_configuration">Configuración de firma</string>
<string name="text_key_store">Almacenamiento de claves</string>
<string name="text_default_key_store">Almacenamiento de claves predeterminado</string>
<string name="text_manage_key_store">Gestionar almacenamiento de claves</string>
<string name="text_signature_scheme">Esquema de firma</string>
<string name="text_new_key_store">Generar clave de firma</string>
<string name="text_key_store_password">Contraseña del almacén de claves</string>
<string name="text_key_alias">Alias</string>
<string name="text_alias_password">Contraseña del alias</string>
<string name="text_signature_algorithm">Algoritmo de firma</string>
<string name="text_validity_years">Validez (años)</string>
<string name="text_first_and_last_name">Nombre completo</string>
<string name="text_organization">Nombre de la organización</string>
<string name="text_organizational_unit">Unidad organizacional</string>
<string name="text_country_code">Código de país (XX)</string>
<string name="text_state_or_province">Estado o provincia</string>
<string name="text_city_or_locality">Ciudad o localidad</string>
<string name="text_street">Calle</string>
<string name="text_filename_cannot_be_empty">El nombre de archivo no puede estar vacío</string>
<string name="text_filename_cannot_contain_invalid_character">El nombre del archivo no puede contener ninguno de los siguientes caracteres: \\ / : * ? &quot; &lt; &gt; |</string>
<string name="text_filename_is_too_long">El nombre del archivo es demasiado largo</string>
<string name="text_password_requires_at_least_n_characters">La contraseña debe tener al menos %d caracteres</string>
<string name="text_alias_cannot_be_empty">El alias no puede estar vacío</string>
<string name="text_validity_years_cannot_be_empty">El número de años de validez no puede estar vacío</string>
<string name="text_validity_years_cannot_be_zero">El número de años de validez no puede ser cero</string>
<string name="text_country_code_must_be_two_capital_letters">El código del país debe ser de dos letras mayúsculas</string>
<string name="text_at_least_one_certificate_issuer_field_is_not_empty">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</string>
<string name="text_successfully_created_key_store">Clave de firma creada con éxito</string>
<string name="text_failed_to_create_key_store">Error al crear la clave de firma</string>
<string name="text_key_store_has_not_been_verified">El almacén de claves no ha sido verificado</string>
<string name="text_key_store_has_been_verified">El almacén de claves ha sido verificado</string>
<string name="text_verify">Verificar</string>
<string name="text_delete_all">Eliminar todo</string>
<string name="text_unverified">No verificado</string>
<string name="text_verified">Verificado</string>
<string name="text_verify_key_store">Verificar almacén de claves</string>
<string name="text_verify_success">Verificación exitosa</string>
<string name="text_verify_failed">Error en la verificación</string>
<string name="text_file_path">Ruta del archivo</string>
<string name="text_switch_window">Cambiar ventana</string> <string name="text_switch_window">Cambiar ventana</string>
<string name="text_symbols_settings">Config de símbolos</string> <string name="text_symbols_settings">Config de símbolos</string>
<string name="text_tailor_made_developer">Desarrollador a medida</string> <string name="text_tailor_made_developer">Desarrollador a medida</string>

View File

@@ -758,6 +758,45 @@
<string name="text_supported_abis_short_32bit">ABIs (32-bit)</string> <string name="text_supported_abis_short_32bit">ABIs (32-bit)</string>
<string name="text_supported_abis_short_64bit">ABIs (64-bit)</string> <string name="text_supported_abis_short_64bit">ABIs (64-bit)</string>
<string name="text_supported_libraries_short">Bibliothèques</string> <string name="text_supported_libraries_short">Bibliothèques</string>
<string name="text_signature_configuration">Configuration de la signature</string>
<string name="text_key_store">Magasin de clés</string>
<string name="text_default_key_store">Magasin de clés par défaut</string>
<string name="text_manage_key_store">Gérer le magasin de clés</string>
<string name="text_signature_scheme">Schéma de signature</string>
<string name="text_new_key_store">Générer une clé de signature</string>
<string name="text_key_store_password">Mot de passe du magasin de clés</string>
<string name="text_key_alias">Alias</string>
<string name="text_alias_password">Mot de passe de l\'alias</string>
<string name="text_signature_algorithm">Algorithme de signature</string>
<string name="text_validity_years">Validité (années)</string>
<string name="text_first_and_last_name">Nom complet</string>
<string name="text_organization">Nom de l\'organisation</string>
<string name="text_organizational_unit">Unité organisationnelle</string>
<string name="text_country_code">Code du pays (XX)</string>
<string name="text_state_or_province">État ou province</string>
<string name="text_city_or_locality">Ville ou localité</string>
<string name="text_street">Rue</string>
<string name="text_filename_cannot_be_empty">Le nom de fichier ne peut pas être vide</string>
<string name="text_filename_cannot_contain_invalid_character">Le nom du fichier ne peut pas contenir les caractères suivants : \\ / : * ? &quot; &lt; &gt; |</string>
<string name="text_filename_is_too_long">Le nom du fichier est trop long</string>
<string name="text_password_requires_at_least_n_characters">Le mot de passe doit contenir au moins %d caractères</string>
<string name="text_alias_cannot_be_empty">L\'alias ne peut pas être vide</string>
<string name="text_validity_years_cannot_be_empty">Les années de validité ne peuvent pas être vides</string>
<string name="text_validity_years_cannot_be_zero">Les années de validité ne peuvent pas être égales à zéro</string>
<string name="text_country_code_must_be_two_capital_letters">Le code du pays doit être composé de deux lettres majuscules</string>
<string name="text_at_least_one_certificate_issuer_field_is_not_empty">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é</string>
<string name="text_successfully_created_key_store">Clé de signature créée avec succès</string>
<string name="text_failed_to_create_key_store">Échec de la création de la clé de signature</string>
<string name="text_key_store_has_not_been_verified">Le magasin de clés n\'a pas été vérifié</string>
<string name="text_key_store_has_been_verified">Le magasin de clés a été vérifié</string>
<string name="text_verify">Vérifier</string>
<string name="text_delete_all">Tout supprimer</string>
<string name="text_unverified">Non vérifié</string>
<string name="text_verified">Vérifié</string>
<string name="text_verify_key_store">Vérifier le magasin de clés</string>
<string name="text_verify_success">Vérification réussie</string>
<string name="text_verify_failed">Échec de la vérification</string>
<string name="text_file_path">Chemin du fichier</string>
<string name="text_switch_window">Changer de fenêtre</string> <string name="text_switch_window">Changer de fenêtre</string>
<string name="text_symbols_settings">Param des symboles</string> <string name="text_symbols_settings">Param des symboles</string>
<string name="text_tailor_made_developer">Développeur sur mesure</string> <string name="text_tailor_made_developer">Développeur sur mesure</string>

View File

@@ -758,6 +758,45 @@
<string name="text_supported_abis_short_32bit">ABI (32ビット)</string> <string name="text_supported_abis_short_32bit">ABI (32ビット)</string>
<string name="text_supported_abis_short_64bit">ABI (64ビット)</string> <string name="text_supported_abis_short_64bit">ABI (64ビット)</string>
<string name="text_supported_libraries_short">ライブラリー</string> <string name="text_supported_libraries_short">ライブラリー</string>
<string name="text_signature_configuration">署名設定</string>
<string name="text_key_store">キーストア</string>
<string name="text_default_key_store">デフォルトのキーストア</string>
<string name="text_manage_key_store">キーストアを管理</string>
<string name="text_signature_scheme">署名スキーム</string>
<string name="text_new_key_store">署名キーを生成</string>
<string name="text_key_store_password">キーストアのパスワード</string>
<string name="text_key_alias">エイリアス</string>
<string name="text_alias_password">エイリアスのパスワード</string>
<string name="text_signature_algorithm">署名アルゴリズム</string>
<string name="text_validity_years">有効期限(年)</string>
<string name="text_first_and_last_name">氏名</string>
<string name="text_organization">組織名</string>
<string name="text_organizational_unit">組織単位</string>
<string name="text_country_code">国コード(XX)</string>
<string name="text_state_or_province">州または省</string>
<string name="text_city_or_locality">市区町村</string>
<string name="text_street">街</string>
<string name="text_filename_cannot_be_empty">ファイル名は空にできません</string>
<string name="text_filename_cannot_contain_invalid_character">ファイル名に以下の文字を含めることはできません:\\ / : * ? &quot; &lt; &gt; |</string>
<string name="text_filename_is_too_long">ファイル名が長すぎます</string>
<string name="text_password_requires_at_least_n_characters">パスワードは少なくとも%d文字以上である必要があります</string>
<string name="text_alias_cannot_be_empty">エイリアスは空にできません</string>
<string name="text_validity_years_cannot_be_empty">有効期限の年数は空にできません</string>
<string name="text_validity_years_cannot_be_zero">有効期限の年数は0にできません</string>
<string name="text_country_code_must_be_two_capital_letters">国コードは2文字の大文字でなければなりません</string>
<string name="text_at_least_one_certificate_issuer_field_is_not_empty">「氏名、組織名、組織単位、国コード、州または省、市区町村、街」のいずれかの項目を入力する必要があります</string>
<string name="text_successfully_created_key_store">署名キーが正常に作成されました</string>
<string name="text_failed_to_create_key_store">署名キーの作成に失敗しました</string>
<string name="text_key_store_has_not_been_verified">キーストアは確認されていません</string>
<string name="text_key_store_has_been_verified">キーストアは確認されました</string>
<string name="text_verify">確認</string>
<string name="text_delete_all">すべて削除</string>
<string name="text_unverified">未確認</string>
<string name="text_verified">確認済み</string>
<string name="text_verify_key_store">キーストアを確認</string>
<string name="text_verify_success">確認成功</string>
<string name="text_verify_failed">確認失敗</string>
<string name="text_file_path">ファイルパス</string>
<string name="text_switch_window">ウィンドウの切り替え</string> <string name="text_switch_window">ウィンドウの切り替え</string>
<string name="text_symbols_settings">シンボル設定</string> <string name="text_symbols_settings">シンボル設定</string>
<string name="text_tailor_made_developer">テーラーメイド開発者</string> <string name="text_tailor_made_developer">テーラーメイド開発者</string>

View File

@@ -759,6 +759,45 @@
<string name="text_supported_abis_short_32bit">ABI (32 비트)</string> <string name="text_supported_abis_short_32bit">ABI (32 비트)</string>
<string name="text_supported_abis_short_64bit">ABI (64 비트)</string> <string name="text_supported_abis_short_64bit">ABI (64 비트)</string>
<string name="text_supported_libraries_short">라이브러리</string> <string name="text_supported_libraries_short">라이브러리</string>
<string name="text_signature_configuration">서명 설정</string>
<string name="text_key_store">키 저장소</string>
<string name="text_default_key_store">기본 키 저장소</string>
<string name="text_manage_key_store">키 저장소 관리</string>
<string name="text_signature_scheme">서명 스킴</string>
<string name="text_new_key_store">서명 키 생성</string>
<string name="text_key_store_password">키 저장소 비밀번호</string>
<string name="text_key_alias">별칭</string>
<string name="text_alias_password">별칭 비밀번호</string>
<string name="text_signature_algorithm">서명 알고리즘</string>
<string name="text_validity_years">유효 기간 (년)</string>
<string name="text_first_and_last_name">성명</string>
<string name="text_organization">조직명</string>
<string name="text_organizational_unit">조직 단위</string>
<string name="text_country_code">국가 코드 (XX)</string>
<string name="text_state_or_province">주 또는 도</string>
<string name="text_city_or_locality">시 또는 지역</string>
<string name="text_street">거리</string>
<string name="text_filename_cannot_be_empty">파일 이름은 비워둘 수 없습니다</string>
<string name="text_filename_cannot_contain_invalid_character">파일 이름에 다음 문자를 포함할 수 없습니다: \\ / : * ? &quot; &lt; &gt; |</string>
<string name="text_filename_is_too_long">파일 이름이 너무 깁니다</string>
<string name="text_password_requires_at_least_n_characters">비밀번호는 최소 %d자 이상이어야 합니다</string>
<string name="text_alias_cannot_be_empty">별칭은 비워둘 수 없습니다</string>
<string name="text_validity_years_cannot_be_empty">유효 기간을 입력해야 합니다</string>
<string name="text_validity_years_cannot_be_zero">유효 기간은 0일 수 없습니다</string>
<string name="text_country_code_must_be_two_capital_letters">국가 코드는 두 개의 대문자로 입력해야 합니다</string>
<string name="text_at_least_one_certificate_issuer_field_is_not_empty">"성명, 조직명, 조직 단위, 국가 코드, 주 또는 도, 시 또는 지역, 거리" 중 하나 이상을 입력해야 합니다</string>
<string name="text_successfully_created_key_store">서명 키가 성공적으로 생성되었습니다</string>
<string name="text_failed_to_create_key_store">서명 키 생성에 실패했습니다</string>
<string name="text_key_store_has_not_been_verified">키 저장소가 확인되지 않았습니다</string>
<string name="text_key_store_has_been_verified">키 저장소가 확인되었습니다</string>
<string name="text_verify">확인</string>
<string name="text_delete_all">모두 삭제</string>
<string name="text_unverified">미확인</string>
<string name="text_verified">확인됨</string>
<string name="text_verify_key_store">키 저장소 확인</string>
<string name="text_verify_success">확인 성공</string>
<string name="text_verify_failed">확인 실패</string>
<string name="text_file_path">파일 경로</string>
<string name="text_switch_window">창 전환</string> <string name="text_switch_window">창 전환</string>
<string name="text_symbols_settings">기호 설정</string> <string name="text_symbols_settings">기호 설정</string>
<string name="text_tailor_made_developer">맞춤형 개발자</string> <string name="text_tailor_made_developer">맞춤형 개발자</string>

View File

@@ -758,6 +758,45 @@
<string name="text_supported_abis_short_32bit">ABIs (32-bit)</string> <string name="text_supported_abis_short_32bit">ABIs (32-bit)</string>
<string name="text_supported_abis_short_64bit">ABIs (64-bit)</string> <string name="text_supported_abis_short_64bit">ABIs (64-bit)</string>
<string name="text_supported_libraries_short">Библиотеки</string> <string name="text_supported_libraries_short">Библиотеки</string>
<string name="text_signature_configuration">Конфигурация подписи</string>
<string name="text_key_store">Хранилище ключей</string>
<string name="text_default_key_store">По умолчанию хранилище ключей</string>
<string name="text_manage_key_store">Управление хранилищем ключей</string>
<string name="text_signature_scheme">Схема подписи</string>
<string name="text_new_key_store">Создать ключ подписи</string>
<string name="text_key_store_password">Пароль хранилища ключей</string>
<string name="text_key_alias">Псевдоним</string>
<string name="text_alias_password">Пароль псевдонима</string>
<string name="text_signature_algorithm">Алгоритм подписи</string>
<string name="text_validity_years">Срок действия (годы)</string>
<string name="text_first_and_last_name">Полное имя</string>
<string name="text_organization">Название организации</string>
<string name="text_organizational_unit">Организационная единица</string>
<string name="text_country_code">Код страны (XX)</string>
<string name="text_state_or_province">Штат или провинция</string>
<string name="text_city_or_locality">Город или населенный пункт</string>
<string name="text_street">Улица</string>
<string name="text_filename_cannot_be_empty">Имя файла не может быть пустым</string>
<string name="text_filename_cannot_contain_invalid_character">Имя файла не может содержать следующие символы: \\ / : * ? &quot; &lt; &gt; |</string>
<string name="text_filename_is_too_long">Имя файла слишком длинное</string>
<string name="text_password_requires_at_least_n_characters">Пароль должен содержать не менее %d символов</string>
<string name="text_alias_cannot_be_empty">Псевдоним не может быть пустым</string>
<string name="text_validity_years_cannot_be_empty">Срок действия не может быть пустым</string>
<string name="text_validity_years_cannot_be_zero">Срок действия не может быть равен нулю</string>
<string name="text_country_code_must_be_two_capital_letters">Код страны должен состоять из двух заглавных букв</string>
<string name="text_at_least_one_certificate_issuer_field_is_not_empty">Необходимо заполнить хотя бы одно поле из: «Полное имя, Название организации, Организационная единица, Код страны, Штат или провинция, Город или населенный пункт, Улица»</string>
<string name="text_successfully_created_key_store">Ключ подписи успешно создан</string>
<string name="text_failed_to_create_key_store">Не удалось создать ключ подписи</string>
<string name="text_key_store_has_not_been_verified">Хранилище ключей не подтверждено</string>
<string name="text_key_store_has_been_verified">Хранилище ключей подтверждено</string>
<string name="text_verify">Проверить</string>
<string name="text_delete_all">Удалить все</string>
<string name="text_unverified">Не подтверждено</string>
<string name="text_verified">Подтверждено</string>
<string name="text_verify_key_store">Проверить хранилище ключей</string>
<string name="text_verify_success">Проверка успешна</string>
<string name="text_verify_failed">Ошибка проверки</string>
<string name="text_file_path">Путь к файлу</string>
<string name="text_switch_window">Переключить окно</string> <string name="text_switch_window">Переключить окно</string>
<string name="text_symbols_settings">Настройки символов</string> <string name="text_symbols_settings">Настройки символов</string>
<string name="text_tailor_made_developer">Индивидуальный разработчик</string> <string name="text_tailor_made_developer">Индивидуальный разработчик</string>

View File

@@ -757,6 +757,45 @@
<string name="text_supported_abis_short_32bit">支持 ABI (32 位)</string> <string name="text_supported_abis_short_32bit">支持 ABI (32 位)</string>
<string name="text_supported_abis_short_64bit">支持 ABI (64 位)</string> <string name="text_supported_abis_short_64bit">支持 ABI (64 位)</string>
<string name="text_supported_libraries_short">支援庫</string> <string name="text_supported_libraries_short">支援庫</string>
<string name="text_signature_configuration">簽名配置</string>
<string name="text_key_store">密鑰庫</string>
<string name="text_default_key_store">預設密鑰庫</string>
<string name="text_manage_key_store">管理密鑰庫</string>
<string name="text_signature_scheme">簽名方案</string>
<string name="text_new_key_store">生成簽名密鑰</string>
<string name="text_key_store_password">密鑰庫密碼</string>
<string name="text_key_alias">別名</string>
<string name="text_alias_password">別名密碼</string>
<string name="text_signature_algorithm">簽名算法</string>
<string name="text_validity_years">有效期 (年)</string>
<string name="text_first_and_last_name">姓名</string>
<string name="text_organization">組織名稱</string>
<string name="text_organizational_unit">組織單位</string>
<string name="text_country_code">國家代碼 (XX)</string>
<string name="text_state_or_province">州或省份</string>
<string name="text_city_or_locality">城市或區域</string>
<string name="text_street">街道</string>
<string name="text_filename_cannot_be_empty">文件名不能為空</string>
<string name="text_filename_cannot_contain_invalid_character">文件名不能包含以下字符: \\ / : * ? &quot; &lt; &gt; |</string>
<string name="text_filename_is_too_long">文件名太長</string>
<string name="text_password_requires_at_least_n_characters">密碼至少需要%d位字符</string>
<string name="text_alias_cannot_be_empty">別名不能為空</string>
<string name="text_validity_years_cannot_be_empty">有效年份不能為空</string>
<string name="text_validity_years_cannot_be_zero">有效年份不能為0</string>
<string name="text_country_code_must_be_two_capital_letters">國家代碼必須為兩個大寫字母</string>
<string name="text_at_least_one_certificate_issuer_field_is_not_empty">“姓名、組織名稱、組織單位、國家代碼、州或省份、城市或區域、街道”至少填寫一個</string>
<string name="text_successfully_created_key_store">成功生成簽名密鑰</string>
<string name="text_failed_to_create_key_store">生成簽名密鑰失敗</string>
<string name="text_key_store_has_not_been_verified">密鑰尚未驗證</string>
<string name="text_key_store_has_been_verified">密鑰已驗證</string>
<string name="text_verify">驗證</string>
<string name="text_delete_all">刪除全部</string>
<string name="text_unverified">未驗證</string>
<string name="text_verified">已驗證</string>
<string name="text_verify_key_store">驗證簽名密鑰</string>
<string name="text_verify_success">驗證成功</string>
<string name="text_verify_failed">驗證失敗</string>
<string name="text_file_path">文件路徑</string>
<string name="text_switch_window">切換窗口</string> <string name="text_switch_window">切換窗口</string>
<string name="text_symbols_settings">符號設置</string> <string name="text_symbols_settings">符號設置</string>
<string name="text_tailor_made_developer">二次開發者</string> <string name="text_tailor_made_developer">二次開發者</string>

View File

@@ -757,6 +757,45 @@
<string name="text_supported_abis_short_32bit">支援 ABI (32 位)</string> <string name="text_supported_abis_short_32bit">支援 ABI (32 位)</string>
<string name="text_supported_abis_short_64bit">支援 ABI (64 位)</string> <string name="text_supported_abis_short_64bit">支援 ABI (64 位)</string>
<string name="text_supported_libraries_short">支援庫</string> <string name="text_supported_libraries_short">支援庫</string>
<string name="text_signature_configuration">簽名設定</string>
<string name="text_key_store">密鑰庫</string>
<string name="text_default_key_store">預設密鑰庫</string>
<string name="text_manage_key_store">管理密鑰庫</string>
<string name="text_signature_scheme">簽名方案</string>
<string name="text_new_key_store">生成簽名密鑰</string>
<string name="text_key_store_password">密鑰庫密碼</string>
<string name="text_key_alias">別名</string>
<string name="text_alias_password">別名密碼</string>
<string name="text_signature_algorithm">簽名演算法</string>
<string name="text_validity_years">有效期 (年)</string>
<string name="text_first_and_last_name">姓名</string>
<string name="text_organization">組織名稱</string>
<string name="text_organizational_unit">組織單位</string>
<string name="text_country_code">國家代碼 (XX)</string>
<string name="text_state_or_province">州或省份</string>
<string name="text_city_or_locality">城市或區域</string>
<string name="text_street">街道</string>
<string name="text_filename_cannot_be_empty">檔案名稱不能為空</string>
<string name="text_filename_cannot_contain_invalid_character">檔案名稱不能包含以下字符: \\ / : * ? &quot; &lt; &gt; |</string>
<string name="text_filename_is_too_long">檔案名稱太長</string>
<string name="text_password_requires_at_least_n_characters">密碼至少需要%d個字元</string>
<string name="text_alias_cannot_be_empty">別名不能為空</string>
<string name="text_validity_years_cannot_be_empty">有效年份不能為空</string>
<string name="text_validity_years_cannot_be_zero">有效年份不能為0</string>
<string name="text_country_code_must_be_two_capital_letters">國家代碼必須是兩個大寫字母</string>
<string name="text_at_least_one_certificate_issuer_field_is_not_empty">“姓名、組織名稱、組織單位、國家代碼、州或省份、城市或區域、街道”至少填寫一個</string>
<string name="text_successfully_created_key_store">成功生成簽名密鑰</string>
<string name="text_failed_to_create_key_store">生成簽名密鑰失敗</string>
<string name="text_key_store_has_not_been_verified">密鑰尚未驗證</string>
<string name="text_key_store_has_been_verified">密鑰已驗證</string>
<string name="text_verify">驗證</string>
<string name="text_delete_all">刪除全部</string>
<string name="text_unverified">未驗證</string>
<string name="text_verified">已驗證</string>
<string name="text_verify_key_store">驗證簽名密鑰</string>
<string name="text_verify_success">驗證成功</string>
<string name="text_verify_failed">驗證失敗</string>
<string name="text_file_path">檔案路徑</string>
<string name="text_switch_window">切換視窗</string> <string name="text_switch_window">切換視窗</string>
<string name="text_symbols_settings">符號設定</string> <string name="text_symbols_settings">符號設定</string>
<string name="text_tailor_made_developer">二次開發者</string> <string name="text_tailor_made_developer">二次開發者</string>

View File

@@ -753,6 +753,45 @@
<string name="text_supported_abis_short_32bit">支持 ABI (32 位)</string> <string name="text_supported_abis_short_32bit">支持 ABI (32 位)</string>
<string name="text_supported_abis_short_64bit">支持 ABI (64 位)</string> <string name="text_supported_abis_short_64bit">支持 ABI (64 位)</string>
<string name="text_supported_libraries_short">支持库</string> <string name="text_supported_libraries_short">支持库</string>
<string name="text_signature_configuration">签名配置</string>
<string name="text_key_store">密钥库</string>
<string name="text_default_key_store">默认密钥库</string>
<string name="text_manage_key_store">管理密钥库</string>
<string name="text_signature_scheme">签名方案</string>
<string name="text_new_key_store">生成签名密钥</string>
<string name="text_key_store_password">密钥库密码</string>
<string name="text_key_alias">别名</string>
<string name="text_alias_password">别名密码</string>
<string name="text_signature_algorithm">签名算法</string>
<string name="text_validity_years">有效期 (年)</string>
<string name="text_first_and_last_name">姓名</string>
<string name="text_organization">组织名称</string>
<string name="text_organizational_unit">组织单位</string>
<string name="text_country_code">国家代码 (XX)</string>
<string name="text_state_or_province">州或省份</string>
<string name="text_city_or_locality">城市或区域</string>
<string name="text_street">街道</string>
<string name="text_filename_cannot_be_empty">文件名不能为空</string>
<string name="text_filename_cannot_contain_invalid_character">文件名不能包含下列任何字符: \\ / : * ? &quot; &lt; &gt; |</string>
<string name="text_filename_is_too_long">文件名太长</string>
<string name="text_password_requires_at_least_n_characters">密码至少需要%d位字符</string>
<string name="text_alias_cannot_be_empty">别名不能为空</string>
<string name="text_validity_years_cannot_be_empty">有效年份不能为空</string>
<string name="text_validity_years_cannot_be_zero">有效年份不能为0</string>
<string name="text_country_code_must_be_two_capital_letters">国家代码必须为两个大写字母</string>
<string name="text_at_least_one_certificate_issuer_field_is_not_empty">“姓名、组织名称、组织单位、国家代码、州或省份、城市或区域、街道”至少填写一个</string>
<string name="text_successfully_created_key_store">成功生成签名密钥</string>
<string name="text_failed_to_create_key_store">生成签名密钥失败</string>
<string name="text_key_store_has_not_been_verified">密钥尚未验证</string>
<string name="text_key_store_has_been_verified">密钥已验证</string>
<string name="text_verify">验证</string>
<string name="text_delete_all">删除全部</string>
<string name="text_unverified">未验证</string>
<string name="text_verified">已验证</string>
<string name="text_verify_key_store">验证签名密钥</string>
<string name="text_verify_success">验证成功</string>
<string name="text_verify_failed">验证失败</string>
<string name="text_file_path">文件路径</string>
<string name="text_switch_window">切换窗口</string> <string name="text_switch_window">切换窗口</string>
<string name="text_symbols_settings">符号设置</string> <string name="text_symbols_settings">符号设置</string>
<string name="text_tailor_made_developer">二次开发者</string> <string name="text_tailor_made_developer">二次开发者</string>

View File

@@ -966,6 +966,51 @@
<string name="text_supported_abis_short_32bit">ABIs (32-bit)</string> <string name="text_supported_abis_short_32bit">ABIs (32-bit)</string>
<string name="text_supported_abis_short_64bit">ABIs (64-bit)</string> <string name="text_supported_abis_short_64bit">ABIs (64-bit)</string>
<string name="text_supported_libraries_short">Libraries</string> <string name="text_supported_libraries_short">Libraries</string>
<string name="text_jks" translatable="false">JKS</string>
<string name="text_bks" translatable="false">BKS</string>
<string name="text_default_alias" translatable="false">key0</string>
<string name="text_default_validity_years" translatable="false">25</string>
<string name="text_default_country_code" translatable="false">CN</string>
<string name="text_str_colon_space_str_formatter" translatable="false">%s: %s</string>
<string name="text_signature_configuration">Signature Configuration</string>
<string name="text_key_store">Keystore</string>
<string name="text_default_key_store">Default Keystore</string>
<string name="text_manage_key_store">Manage Keystore</string>
<string name="text_signature_scheme">Signature Scheme</string>
<string name="text_new_key_store">New Key Store</string>
<string name="text_key_store_password">Key Store Password</string>
<string name="text_key_alias">Alias</string>
<string name="text_alias_password">Alias Password</string>
<string name="text_signature_algorithm">Signature Algorithm</string>
<string name="text_validity_years">Validity (Years)</string>
<string name="text_first_and_last_name">Full Name</string>
<string name="text_organization">Organization Name</string>
<string name="text_organizational_unit">Organizational Unit</string>
<string name="text_country_code">Country Code (XX)</string>
<string name="text_state_or_province">State or Province</string>
<string name="text_city_or_locality">City or Locality</string>
<string name="text_street">Street</string>
<string name="text_filename_cannot_be_empty">Filename cannot be empty</string>
<string name="text_filename_cannot_contain_invalid_character">Filename cannot contain the following characters: \\ / : * ? &quot; &lt; &gt; |</string>
<string name="text_filename_is_too_long">Filename is too long</string>
<string name="text_password_requires_at_least_n_characters">Password must be at least %d characters long</string>
<string name="text_alias_cannot_be_empty">Alias cannot be empty</string>
<string name="text_validity_years_cannot_be_empty">Validity years cannot be empty</string>
<string name="text_validity_years_cannot_be_zero">Validity years cannot be zero</string>
<string name="text_country_code_must_be_two_capital_letters">Country code must be two capital letters</string>
<string name="text_at_least_one_certificate_issuer_field_is_not_empty">At least one field from \"Full Name, Organization Name, Organizational Unit, Country Code, State or Province, City or Locality, Street\" must be filled</string>
<string name="text_successfully_created_key_store">Successfully created signature key</string>
<string name="text_failed_to_create_key_store">Failed to create signature key</string>
<string name="text_key_store_has_not_been_verified">Keystore has not been verified</string>
<string name="text_key_store_has_been_verified">Keystore has been verified</string>
<string name="text_verify">Verify</string>
<string name="text_delete_all">Delete All</string>
<string name="text_unverified">Unverified</string>
<string name="text_verified">Verified</string>
<string name="text_verify_key_store">Verify Keystore</string>
<string name="text_verify_success">Verification successful</string>
<string name="text_verify_failed">Verification failed</string>
<string name="text_file_path">File Path</string>
<string name="text_switch_window">Switch window</string> <string name="text_switch_window">Switch window</string>
<string name="text_symbols_settings">Symbols settings</string> <string name="text_symbols_settings">Symbols settings</string>
<string name="text_tailor_made_developer">Tailor-made developer</string> <string name="text_tailor_made_developer">Tailor-made developer</string>