diff --git a/.gitignore b/.gitignore index aa724b77..23732d09 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ .externalNativeBuild .cxx local.properties +.kotlin diff --git a/app/build.gradle.kts b/app/build.gradle.kts index a4d4ac87..ec160c69 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -86,4 +86,8 @@ dependencies { implementation(libs.zoomable) implementation(libs.androidx.media3.exoplayer) implementation(libs.androidx.media3.ui) + + // APKEditor + implementation(files("libs/APKEditor.jar")) + implementation(libs.apksig) } \ No newline at end of file diff --git a/app/libs/APKEditor.jar b/app/libs/APKEditor.jar new file mode 100644 index 00000000..57b56885 Binary files /dev/null and b/app/libs/APKEditor.jar differ diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 25ab1172..bb3c58bf 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -22,4 +22,7 @@ -keep class com.raival.compose.file.explorer.** { *; } -keep class org.eclipse.tm4e.** { *; } --keep class org.joni.** { *; } \ No newline at end of file +-keep class org.joni.** { *; } +-keep class android.content.** { *; } + +-keepnames interface * { *; } \ No newline at end of file diff --git a/app/src/main/assets/keystore/testkey.pk8 b/app/src/main/assets/keystore/testkey.pk8 new file mode 100644 index 00000000..586c1bd5 Binary files /dev/null and b/app/src/main/assets/keystore/testkey.pk8 differ diff --git a/app/src/main/assets/keystore/testkey.x509.pem b/app/src/main/assets/keystore/testkey.x509.pem new file mode 100644 index 00000000..e242d83e --- /dev/null +++ b/app/src/main/assets/keystore/testkey.x509.pem @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE----- +MIIEqDCCA5CgAwIBAgIJAJNurL4H8gHfMA0GCSqGSIb3DQEBBQUAMIGUMQswCQYD +VQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4g +VmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UE +AxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTAe +Fw0wODAyMjkwMTMzNDZaFw0zNTA3MTcwMTMzNDZaMIGUMQswCQYDVQQGEwJVUzET +MBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4G +A1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9p +ZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTCCASAwDQYJKoZI +hvcNAQEBBQADggENADCCAQgCggEBANaTGQTexgskse3HYuDZ2CU+Ps1s6x3i/waM +qOi8qM1r03hupwqnbOYOuw+ZNVn/2T53qUPn6D1LZLjk/qLT5lbx4meoG7+yMLV4 +wgRDvkxyGLhG9SEVhvA4oU6Jwr44f46+z4/Kw9oe4zDJ6pPQp8PcSvNQIg1QCAcy +4ICXF+5qBTNZ5qaU7Cyz8oSgpGbIepTYOzEJOmc3Li9kEsBubULxWBjf/gOBzAzU +RNps3cO4JFgZSAGzJWQTT7/emMkod0jb9WdqVA2BVMi7yge54kdVMxHEa5r3b97s +zI5p58ii0I54JiCUP5lyfTwE/nKZHZnfm644oLIXf6MdW2r+6R8CAQOjgfwwgfkw +HQYDVR0OBBYEFEhZAFY9JyxGrhGGBaR0GawJyowRMIHJBgNVHSMEgcEwgb6AFEhZ +AFY9JyxGrhGGBaR0GawJyowRoYGapIGXMIGUMQswCQYDVQQGEwJVUzETMBEGA1UE +CBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMH +QW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAG +CSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbYIJAJNurL4H8gHfMAwGA1Ud +EwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAHqvlozrUMRBBVEY0NqrrwFbinZa +J6cVosK0TyIUFf/azgMJWr+kLfcHCHJsIGnlw27drgQAvilFLAhLwn62oX6snb4Y +LCBOsVMR9FXYJLZW2+TcIkCRLXWG/oiVHQGo/rWuWkJgU134NDEFJCJGjDbiLCpe ++ZTWHdcwauTJ9pUbo8EvHRkU3cYfGmLaLfgn9gP+pWA7LFQNvXwBnDa6sppCccEX +31I828XzgXpJ4O+mDL1/dBd+ek8ZPUP0IgdyZm5MTYPhvVqGCHzzTy3sIeJFymwr +sBbmg2OAUNLEMO6nwmocSdN2ClirfxqCzJOLSDE4QyS9BAH6EhY6UFcOaE0= +-----END CERTIFICATE----- diff --git a/app/src/main/java/com/raival/compose/file/explorer/screen/main/tab/files/FilesTab.kt b/app/src/main/java/com/raival/compose/file/explorer/screen/main/tab/files/FilesTab.kt index 30f416da..ec1e6b89 100644 --- a/app/src/main/java/com/raival/compose/file/explorer/screen/main/tab/files/FilesTab.kt +++ b/app/src/main/java/com/raival/compose/file/explorer/screen/main/tab/files/FilesTab.kt @@ -5,6 +5,7 @@ import android.content.Context import android.content.Intent import android.content.pm.ShortcutInfo import android.content.pm.ShortcutManager +import android.content.res.AssetManager import android.net.Uri import android.os.Environment import androidx.compose.foundation.lazy.LazyListState @@ -86,6 +87,8 @@ class FilesTab( val currentPathSegments = mutableStateListOf() val currentPathSegmentsListState = LazyListState() + val assetManager: AssetManager = globalClass.assets + val selectedFiles = linkedMapOf() var lastSelectedFileIndex = -1 @@ -219,7 +222,7 @@ class FilesTab( } fun openFile(context: Context, item: DocumentHolder) { - if (item.isApk) { + if (item.isApk || item.isApks) { ApkDialog.show(item) } else { item.openFile(context, anonymous = false, skipSupportedExtensions = false) @@ -533,10 +536,13 @@ class FilesTab( private set var apkFile: DocumentHolder? = null private set + var ApksArchive = false + private set fun show(file: DocumentHolder) { apkFile = file showApkDialog = true + ApksArchive = file.isApks } fun hide() { diff --git a/app/src/main/java/com/raival/compose/file/explorer/screen/main/tab/files/coil/DocumentFileMapper.kt b/app/src/main/java/com/raival/compose/file/explorer/screen/main/tab/files/coil/DocumentFileMapper.kt index 5dd1b285..6c0dd2ba 100644 --- a/app/src/main/java/com/raival/compose/file/explorer/screen/main/tab/files/coil/DocumentFileMapper.kt +++ b/app/src/main/java/com/raival/compose/file/explorer/screen/main/tab/files/coil/DocumentFileMapper.kt @@ -5,6 +5,7 @@ import coil3.request.Options import com.raival.compose.file.explorer.R import com.raival.compose.file.explorer.screen.main.tab.files.modal.DocumentHolder import com.raival.compose.file.explorer.screen.main.tab.files.modal.DocumentHolder.Companion.FILE_TYPE_AI +import com.raival.compose.file.explorer.screen.main.tab.files.modal.DocumentHolder.Companion.FILE_TYPE_APK import com.raival.compose.file.explorer.screen.main.tab.files.modal.DocumentHolder.Companion.FILE_TYPE_ARCHIVE import com.raival.compose.file.explorer.screen.main.tab.files.modal.DocumentHolder.Companion.FILE_TYPE_AUDIO import com.raival.compose.file.explorer.screen.main.tab.files.modal.DocumentHolder.Companion.FILE_TYPE_CODE @@ -46,6 +47,7 @@ class DocumentFileMapper : Mapper { data.getFileIconType() == FILE_TYPE_CODE -> R.drawable.css_file_extension data.getFileIconType() == FILE_TYPE_TEXT -> R.drawable.txt_file_extension data.getFileIconType() == FILE_TYPE_ARCHIVE -> R.drawable.zip_file_extension + data.getFileIconResource() == FILE_TYPE_APK -> R.drawable.apk_file_extension canUseCoil(data) -> data.uri else -> R.drawable.unknown_file_extension } diff --git a/app/src/main/java/com/raival/compose/file/explorer/screen/main/tab/files/compose/ApkPreviewDialog.kt b/app/src/main/java/com/raival/compose/file/explorer/screen/main/tab/files/compose/ApkPreviewDialog.kt index 282ea372..47d2ac25 100644 --- a/app/src/main/java/com/raival/compose/file/explorer/screen/main/tab/files/compose/ApkPreviewDialog.kt +++ b/app/src/main/java/com/raival/compose/file/explorer/screen/main/tab/files/compose/ApkPreviewDialog.kt @@ -39,62 +39,63 @@ import com.raival.compose.file.explorer.common.compose.Space import com.raival.compose.file.explorer.common.extension.emptyString import com.raival.compose.file.explorer.common.extension.toFormattedSize import com.raival.compose.file.explorer.screen.main.tab.files.FilesTab +import com.raival.compose.file.explorer.screen.preferences.PreferencesManager +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch @Composable fun ApkPreviewDialog(tab: FilesTab) { val apkDialog = tab.apkDialog + val isApksArchive: Boolean = apkDialog.ApksArchive if (tab.apkDialog.showApkDialog && apkDialog.apkFile != null) { - val packageManager = globalClass.packageManager val context = LocalContext.current val apkFile = apkDialog.apkFile!! - val apkInfo by remember { - mutableStateOf(packageManager.getPackageArchiveInfo(apkFile.path, 0)) - } + var icon by remember { mutableStateOf(null) } + val details = remember { mutableStateListOf>() } + var appName by remember { mutableStateOf(emptyString) } - var icon by remember { - mutableStateOf(null) - } + val doSign = PreferencesManager.GeneralPrefs.signApk - val details = remember { - mutableStateListOf>() - } - - var appName by remember { - mutableStateOf(emptyString) - } + if (!isApksArchive) { + val packageManager = globalClass.packageManager + val apkInfo = + remember { mutableStateOf(packageManager.getPackageArchiveInfo(apkFile.path, 0)) } - LaunchedEffect(Unit) { - apkInfo?.let { - it.applicationInfo?.sourceDir = apkFile.path - it.applicationInfo?.publicSourceDir = apkFile.path + LaunchedEffect(Unit) { + apkInfo.value?.let { info -> + info.applicationInfo?.sourceDir = apkFile.path + info.applicationInfo?.publicSourceDir = apkFile.path - icon = it.applicationInfo?.loadIcon(packageManager) - appName = it.applicationInfo?.loadLabel(packageManager).toString() + icon = info.applicationInfo?.loadIcon(packageManager) + appName = info.applicationInfo?.loadLabel(packageManager).toString() - details.add(Pair(globalClass.getString(R.string.package_name), it.packageName)) - it.versionName?.let { - details.add(Pair(globalClass.getString(R.string.version_name), it)) - } - details.add( - Pair( - globalClass.getString(R.string.version_code), - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) it.longVersionCode.toString() else it.versionCode.toString() + details.add( + Pair( + globalClass.getString(R.string.package_name), + info.packageName + ) ) - ) - details.add( - Pair( - globalClass.getString(R.string.size), - apkFile.fileSize.toFormattedSize() + info.versionName?.let { + details.add(Pair(globalClass.getString(R.string.version_name), it)) + } + details.add( + Pair( + globalClass.getString(R.string.version_code), + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) info.longVersionCode.toString() else info.versionCode.toString() + ) ) - ) + } } } + details.add( + Pair(globalClass.getString(R.string.size), apkFile.fileSize.toFormattedSize()) + ) - BottomSheetDialog( - onDismissRequest = { apkDialog.hide() } - ) { + BottomSheetDialog(onDismissRequest = { apkDialog.hide() }) { Column( modifier = Modifier .fillMaxWidth() @@ -122,11 +123,7 @@ fun ApkPreviewDialog(tab: FilesTab) { modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center ) { - Text( - text = appName, - fontSize = 18.sp, - fontWeight = FontWeight.Bold - ) + Text(text = appName, fontSize = 18.sp, fontWeight = FontWeight.Bold) } Space(size = 8.dp) @@ -138,7 +135,8 @@ fun ApkPreviewDialog(tab: FilesTab) { Text( modifier = Modifier.alpha(0.5f), text = it.second, - maxLines = 1 + softWrap = true, + maxLines = 2 ) } } @@ -147,31 +145,57 @@ fun ApkPreviewDialog(tab: FilesTab) { Space(size = 8.dp) Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End) { - TextButton( - onClick = { - apkDialog.hide() - apkFile.openFile( - context = context, - anonymous = false, - skipSupportedExtensions = true, - customMimeType = "application/zip" - ) - } - ) { + TextButton(onClick = { + apkDialog.hide() + apkFile.openFile( + context = context, + anonymous = false, + skipSupportedExtensions = true, + customMimeType = "application/zip" + ) + }) { Text(text = stringResource(R.string.explore)) } - TextButton( - onClick = { + if (isApksArchive) { + TextButton(onClick = { + apkDialog.hide() + val mergeHandler = MergeHandler(context) + mergeHandler.mergeApks( + tab, apkFile, doSign, + onSuccess = { + CoroutineScope(Dispatchers.Main).launch { + tab.taskDialog.taskDialogInfo = + context.getString(R.string.merge_successful) + tab.taskDialog.taskDialogSubtitle = + context.getString(R.string.merge_completed) + tab.taskDialog.taskDialogProgress = 1f + delay(500) + tab.taskDialog.showTaskDialog = false + } + }, + onError = { errorMessage -> + CoroutineScope(Dispatchers.Main).launch { + tab.taskDialog.taskDialogInfo = errorMessage + tab.taskDialog.taskDialogSubtitle = + context.getString(R.string.failed) + } + } + ) + }) { + Text(text = stringResource(R.string.merge)) + } + } else { + TextButton(onClick = { apkDialog.hide() apkFile.openFile( - context = context, + context, anonymous = false, skipSupportedExtensions = true ) + }) { + Text(text = stringResource(R.string.install)) } - ) { - Text(text = stringResource(R.string.install)) } } } diff --git a/app/src/main/java/com/raival/compose/file/explorer/screen/main/tab/files/compose/MergeHandler.kt b/app/src/main/java/com/raival/compose/file/explorer/screen/main/tab/files/compose/MergeHandler.kt new file mode 100644 index 00000000..a1aa2e9e --- /dev/null +++ b/app/src/main/java/com/raival/compose/file/explorer/screen/main/tab/files/compose/MergeHandler.kt @@ -0,0 +1,141 @@ +package com.raival.compose.file.explorer.screen.main.tab.files.compose + +import android.content.Context +import com.android.apksig.ApkSigner +import com.android.apksig.ApkSigner.SignerConfig +import com.android.apksig.KeyConfig +import com.raival.compose.file.explorer.R +import com.raival.compose.file.explorer.screen.main.tab.files.FilesTab +import com.raival.compose.file.explorer.screen.main.tab.files.modal.DocumentHolder +import com.reandroid.apkeditor.merge.Merger +import com.reandroid.apkeditor.merge.MergerOptions +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import java.io.File +import java.io.IOException +import java.io.InputStream +import java.security.KeyFactory +import java.security.NoSuchAlgorithmException +import java.security.SignatureException +import java.security.cert.CertificateFactory +import java.security.cert.X509Certificate +import java.security.spec.InvalidKeySpecException +import java.security.spec.PKCS8EncodedKeySpec + +class MergeHandler(private val context: Context) { + + @Throws(IOException::class) + fun executeMerge(inputFilePath: String, outputFilePath: String, onProgressUpdate: (Float, String) -> Unit) { + val options = MergerOptions().apply { + inputFile = File(inputFilePath) + outputFile = File(outputFilePath) + } + val merger = Merger(options) + val tasks = listOf( + context.getString(R.string.initializing_merge) to 0.1f, + context.getString(R.string.extracting_files) to 0.3f, + context.getString(R.string.merging_modules) to 0.6f, + context.getString(R.string.finalizing_merge) to 0.8f, + ) + for ((message, progress) in tasks) { + Thread.sleep(500) + onProgressUpdate(progress, message) + } + merger.runCommand() + } + + @Throws(IOException::class, NoSuchAlgorithmException::class, InvalidKeySpecException::class, SignatureException::class) + private fun signApk(apkFilePath: String, outFilePath: String, keyInputStream: InputStream, certInputStream: InputStream) { + val privateKey = keyInputStream.use { inputStream -> + val keyBytes = inputStream.readBytes() + val keySpec = PKCS8EncodedKeySpec(keyBytes) + val keyFactory = KeyFactory.getInstance("RSA") + keyFactory.generatePrivate(keySpec) + } + + val certificate = certInputStream.use { inputStream -> + val certificateFactory = CertificateFactory.getInstance("X.509") + certificateFactory.generateCertificate(inputStream) as X509Certificate + } + + val keyConfig = KeyConfig.Jca(privateKey) + + val signerConfig = SignerConfig.Builder( + "Android", + keyConfig, + listOf(certificate) + ).build() + + val apkSigner = ApkSigner.Builder(listOf(signerConfig)) + .setInputApk(File(apkFilePath)) + .setOutputApk(File(outFilePath)) + .setV1SigningEnabled(true) + .setV2SigningEnabled(true) + .build() + + apkSigner.sign() + } + + fun mergeApks(tab: FilesTab, apkFile: DocumentHolder, doSign: Boolean, onSuccess: () -> Unit, onError: (String) -> Unit) { + CoroutineScope(Dispatchers.IO).launch { + try { + val fileExtension = apkFile.fileExtension + val fileName = apkFile.getName().removeSuffix(".$fileExtension") + val filePath = apkFile.path + val fileDir = apkFile.parent?.path + val outputFilePath = "$fileDir/$fileName.apk" + val mergeHandler = MergeHandler(context) + withContext(Dispatchers.Main) { + tab.taskDialog.showTaskDialog = true + tab.taskDialog.taskDialogInfo = context.getString(R.string.merging_apks) + tab.taskDialog.taskDialogTitle = context.getString(R.string.merging) + tab.taskDialog.taskDialogSubtitle = context.getString(R.string.merging_apks) + tab.taskDialog.showTaskDialogProgressbar = true + tab.taskDialog.taskDialogProgress = 0f + } + mergeHandler.executeMerge(filePath, outputFilePath) { progress, message -> + launch(Dispatchers.Main) { + tab.taskDialog.taskDialogProgress = progress + tab.taskDialog.taskDialogSubtitle = message + } + } + val outputFile = File(outputFilePath) + if (!outputFile.exists()) { + withContext(Dispatchers.Main) { onError(context.getString(R.string.merge_failed)) } + return@launch + } + + if (doSign) { + // TODO: Custom KeyStore + val keyFilePath = tab.assetManager.open("keystore/testkey.pk8") + val certFilePath = tab.assetManager.open("keystore/testkey.x509.pem") + val signedApkPath = outputFilePath.replace(".apk", "-signed.apk") + + tab.taskDialog.taskDialogSubtitle = context.getString(R.string.signing_apk) + tab.taskDialog.taskDialogProgress = 0.9f + signApk(outputFilePath, signedApkPath, keyFilePath, certFilePath) + + if (!File(signedApkPath).exists()) { + withContext(Dispatchers.Main) { onError(context.getString(R.string.signing_failed)) } + return@launch + } + if (File(outputFilePath).exists()) { + File(outputFilePath).delete() + } + } + + withContext(Dispatchers.Main) { onSuccess() } + } catch (e: Exception) { + e.printStackTrace() + withContext(Dispatchers.Main) { + onError(context.getString(R.string.merge_failed_with_error, e.message)) + delay(500) + tab.taskDialog.showTaskDialog = false + } + } + } + } +} diff --git a/app/src/main/java/com/raival/compose/file/explorer/screen/main/tab/files/misc/FileMimeType.kt b/app/src/main/java/com/raival/compose/file/explorer/screen/main/tab/files/misc/FileMimeType.kt index 2d175883..c754817c 100644 --- a/app/src/main/java/com/raival/compose/file/explorer/screen/main/tab/files/misc/FileMimeType.kt +++ b/app/src/main/java/com/raival/compose/file/explorer/screen/main/tab/files/misc/FileMimeType.kt @@ -40,7 +40,6 @@ object FileMimeType { "jar", "gz", "xz", - "xapk", "obb", "rar", "iso", @@ -48,7 +47,7 @@ object FileMimeType { "tgz", "tbz2", "lz", - "lzma" + "lzma", ) @JvmField @@ -164,4 +163,7 @@ object FileMimeType { "opus" ) + @JvmField + val apkBundleFileType = arrayOf("apks", "xapk", "apkm") + } \ No newline at end of file diff --git a/app/src/main/java/com/raival/compose/file/explorer/screen/main/tab/files/modal/DocumentHolder.kt b/app/src/main/java/com/raival/compose/file/explorer/screen/main/tab/files/modal/DocumentHolder.kt index ade1968f..ca7a848d 100644 --- a/app/src/main/java/com/raival/compose/file/explorer/screen/main/tab/files/modal/DocumentHolder.kt +++ b/app/src/main/java/com/raival/compose/file/explorer/screen/main/tab/files/modal/DocumentHolder.kt @@ -33,6 +33,7 @@ import com.raival.compose.file.explorer.common.extension.toFormattedSize import com.raival.compose.file.explorer.screen.main.tab.files.misc.FileMimeType import com.raival.compose.file.explorer.screen.main.tab.files.misc.FileMimeType.aiFileType import com.raival.compose.file.explorer.screen.main.tab.files.misc.FileMimeType.anyFileType +import com.raival.compose.file.explorer.screen.main.tab.files.misc.FileMimeType.apkBundleFileType import com.raival.compose.file.explorer.screen.main.tab.files.misc.FileMimeType.apkFileType import com.raival.compose.file.explorer.screen.main.tab.files.misc.FileMimeType.archiveFileType import com.raival.compose.file.explorer.screen.main.tab.files.misc.FileMimeType.audioFileType @@ -92,6 +93,8 @@ data class DocumentHolder(val documentFile: DocumentFile) : ContentHolder() { val isApk by lazy { isFile && fileExtension == apkFileType } + val isApks by lazy { isFile && apkBundleFileType.contains(fileExtension) } + val isImage by lazy { isFile && imageFileType.contains(fileExtension) } val isVideo by lazy { isFile && videoFileType.contains(fileExtension) } @@ -479,7 +482,7 @@ data class DocumentHolder(val documentFile: DocumentFile) : ContentHolder() { return FILE_TYPE_FONT } else if (vectorFileType.contains(fileExtension)) { return FILE_TYPE_VECTOR - } else if (archiveFileType.contains(fileExtension)) { + } else if (archiveFileType.contains(fileExtension) || apkBundleFileType.contains(fileExtension)) { return FILE_TYPE_ARCHIVE } else if (videoFileType.contains(fileExtension)) { return FILE_TYPE_VIDEO diff --git a/app/src/main/java/com/raival/compose/file/explorer/screen/preferences/PreferencesManager.kt b/app/src/main/java/com/raival/compose/file/explorer/screen/preferences/PreferencesManager.kt index c63f02c3..ac9e31f7 100644 --- a/app/src/main/java/com/raival/compose/file/explorer/screen/preferences/PreferencesManager.kt +++ b/app/src/main/java/com/raival/compose/file/explorer/screen/preferences/PreferencesManager.kt @@ -173,6 +173,11 @@ class PreferencesManager { defaultValue = true, getPreferencesKey = { booleanPreferencesKey(it) } ) + var signApk by prefMutableState( + keyName = "signApk", + defaultValue = false, + getPreferencesKey = { booleanPreferencesKey(it) } + ) } object FilesSortingPrefs { diff --git a/app/src/main/java/com/raival/compose/file/explorer/screen/preferences/compose/GeneralContainer.kt b/app/src/main/java/com/raival/compose/file/explorer/screen/preferences/compose/GeneralContainer.kt index ca8e7a64..e6c2bc6a 100644 --- a/app/src/main/java/com/raival/compose/file/explorer/screen/preferences/compose/GeneralContainer.kt +++ b/app/src/main/java/com/raival/compose/file/explorer/screen/preferences/compose/GeneralContainer.kt @@ -2,6 +2,7 @@ package com.raival.compose.file.explorer.screen.preferences.compose import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.rounded.ManageSearch +import androidx.compose.material.icons.automirrored.rounded.Note import androidx.compose.runtime.Composable import androidx.compose.ui.res.stringResource import com.raival.compose.file.explorer.App.Companion.globalClass @@ -39,5 +40,22 @@ fun GeneralContainer() { ) } ) + PreferenceItem( + label = stringResource(R.string.sign_apk), + // Couldn't find a suitable ImageVector for it, please change + icon = Icons.AutoMirrored.Rounded.Note, + supportingText = if (preferences.signApk) "True" else "False", + onClick = { + manager.singleChoiceDialog.show( + title = globalClass.getString(R.string.sign_apk), + description = globalClass.getString(R.string.sign_apk_desc), + choices = listOf("True", "False"), + selectedChoice = if (preferences.signApk) 0 else 1, + onSelect = { + preferences.signApk = it == 0 + } + ) + } + ) } } \ No newline at end of file diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 425f3e23..5e85e7b0 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -196,4 +196,20 @@ Usuário Sistema Mais + To APK + Sign APKS + Sign APK after APK Operations like Anti-Split(merge) etc. + Failed + Merge Successful! + Merge Completed + Initializing merge… + Extracting files… + Merging modules… + Finalizing merge… + Merging… + Merging APKs… + Merge wasn\'t successful! + Signing APK… + Signing failed! + Merge failed: %1$s diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 3badc9ac..64d1a114 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -196,4 +196,20 @@ Пользователь Система Ещё + To APK + Sign APKS + Sign APK after APK Operations like Anti-Split(merge) etc. + Failed + Merge Successful! + Merge Completed + Initializing merge… + Extracting files… + Merging modules… + Finalizing merge… + Merging… + Merging APKs… + Merge wasn\'t successful! + Signing APK… + Signing failed! + Merge failed: %1$s \ No newline at end of file diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index b7219bbb..f3cb0c66 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -197,4 +197,20 @@ 用户 系统 更多 + To APK + Sign APKS + Sign APK after APK Operations like Anti-Split(merge) etc. + Failed + Merge Successful! + Merge Completed + Initializing merge… + Extracting files… + Merging modules… + Finalizing merge… + Merging… + Merging APKs… + Merge wasn\'t successful! + Signing APK… + Signing failed! + Merge failed: %1$s \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ee8e9daa..701d65ec 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -196,4 +196,20 @@ User System More + To APK + Sign APKS + Sign APK after APK Operations like Anti-Split(merge) etc. + Failed + Merge Successful! + Merge Completed + Initializing merge… + Extracting files… + Merging modules… + Finalizing merge… + Merging… + Merging APKs… + Merge wasn\'t successful! + Signing APK… + Signing failed! + Merge failed: %1$s \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 1635e15a..cb63245b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -27,6 +27,7 @@ benchmarkMacroJunit4 = "1.3.3" baselineprofile = "1.3.3" profileinstaller = "1.4.1" reorderable = "2.4.3" +apksig = "8.8.0" [libraries] androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } @@ -63,6 +64,7 @@ androidx-uiautomator = { group = "androidx.test.uiautomator", name = "uiautomato androidx-benchmark-macro-junit4 = { group = "androidx.benchmark", name = "benchmark-macro-junit4", version.ref = "benchmarkMacroJunit4" } androidx-profileinstaller = { group = "androidx.profileinstaller", name = "profileinstaller", version.ref = "profileinstaller" } reorderable = { module = "sh.calvin.reorderable:reorderable", version.ref = "reorderable" } +apksig = { module = "com.android.tools.build:apksig", version.ref = "apksig"} [plugins] android-application = { id = "com.android.application", version.ref = "agp" }