refactor: clean up usages of ~/Aliucord
This commit is contained in:
parent
0cdf2ee743
commit
8d295979be
4 changed files with 49 additions and 30 deletions
|
|
@ -6,7 +6,6 @@
|
||||||
package com.aliucord.manager
|
package com.aliucord.manager
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Environment
|
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.compose.BackHandler
|
import androidx.activity.compose.BackHandler
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
|
|
@ -27,9 +26,6 @@ import com.aliucord.manager.ui.widgets.updater.UpdaterDialog
|
||||||
import com.aliucord.manager.util.IS_CUSTOM_BUILD
|
import com.aliucord.manager.util.IS_CUSTOM_BUILD
|
||||||
import org.koin.android.ext.android.inject
|
import org.koin.android.ext.android.inject
|
||||||
|
|
||||||
// TODO: move to a path provider in DI
|
|
||||||
val aliucordDir = Environment.getExternalStorageDirectory().resolve("Aliucord")
|
|
||||||
|
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
private val preferences: PreferencesManager by inject()
|
private val preferences: PreferencesManager by inject()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,13 +5,15 @@
|
||||||
|
|
||||||
package com.aliucord.manager.installer.util
|
package com.aliucord.manager.installer.util
|
||||||
|
|
||||||
import com.aliucord.manager.aliucordDir
|
import com.aliucord.manager.manager.PathManager
|
||||||
import com.android.apksig.ApkSigner
|
import com.android.apksig.ApkSigner
|
||||||
import org.bouncycastle.asn1.x500.X500Name
|
import org.bouncycastle.asn1.x500.X500Name
|
||||||
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo
|
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo
|
||||||
import org.bouncycastle.cert.X509v3CertificateBuilder
|
import org.bouncycastle.cert.X509v3CertificateBuilder
|
||||||
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter
|
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter
|
||||||
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder
|
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder
|
||||||
|
import org.koin.core.component.KoinComponent
|
||||||
|
import org.koin.core.component.get
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.math.BigInteger
|
import java.math.BigInteger
|
||||||
import java.security.*
|
import java.security.*
|
||||||
|
|
@ -20,21 +22,22 @@ import java.security.cert.X509Certificate
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
object Signer {
|
object Signer : KoinComponent {
|
||||||
|
private val paths = get<PathManager>()
|
||||||
private val password = "password".toCharArray()
|
private val password = "password".toCharArray()
|
||||||
|
|
||||||
private val signerConfig: ApkSigner.SignerConfig by lazy {
|
private val signerConfig: ApkSigner.SignerConfig by lazy {
|
||||||
val keyStore = KeyStore.getInstance(KeyStore.getDefaultType())
|
val keyStore = KeyStore.getInstance(KeyStore.getDefaultType())
|
||||||
|
|
||||||
aliucordDir.resolve("ks.keystore").also {
|
// Create new keystore if it doesn't exist
|
||||||
if (!it.exists()) {
|
if (!paths.keystoreFile.exists()) {
|
||||||
aliucordDir.mkdir()
|
paths.aliucordDir.mkdirs()
|
||||||
newKeystore(it)
|
newKeystore(paths.keystoreFile)
|
||||||
}
|
|
||||||
}.inputStream().use { stream ->
|
|
||||||
keyStore.load(stream, null)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
paths.keystoreFile.inputStream()
|
||||||
|
.use { keyStore.load(it, /* password = */ null) }
|
||||||
|
|
||||||
val alias = keyStore.aliases().nextElement()
|
val alias = keyStore.aliases().nextElement()
|
||||||
val certificate = keyStore.getCertificate(alias) as X509Certificate
|
val certificate = keyStore.getCertificate(alias) as X509Certificate
|
||||||
|
|
||||||
|
|
@ -45,13 +48,13 @@ object Signer {
|
||||||
).build()
|
).build()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun newKeystore(out: File?) {
|
private fun newKeystore(out: File) {
|
||||||
val key = createKey()
|
val key = createKey()
|
||||||
|
|
||||||
with(KeyStore.getInstance(KeyStore.getDefaultType())) {
|
with(KeyStore.getInstance(KeyStore.getDefaultType())) {
|
||||||
load(null, password)
|
load(null, password)
|
||||||
setKeyEntry("alias", key.privateKey, password, arrayOf<Certificate>(key.publicKey))
|
setKeyEntry("alias", key.privateKey, password, arrayOf<Certificate>(key.publicKey))
|
||||||
store(out?.outputStream(), password)
|
store(out.outputStream(), password)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -81,18 +84,18 @@ object Signer {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun signApk(apkFile: File) {
|
fun signApk(apkFile: File) {
|
||||||
val outputApk = aliucordDir.resolve(apkFile.name)
|
val tmpApk = apkFile.resolveSibling(apkFile.name + ".tmp")
|
||||||
|
|
||||||
ApkSigner.Builder(listOf(signerConfig))
|
ApkSigner.Builder(listOf(signerConfig))
|
||||||
.setV1SigningEnabled(false) // TODO: enable so api <24 devices can work, however zip-alignment breaks
|
.setV1SigningEnabled(false) // TODO: enable so api <24 devices can work, however zip-alignment breaks
|
||||||
.setV2SigningEnabled(true)
|
.setV2SigningEnabled(true)
|
||||||
.setV3SigningEnabled(true)
|
.setV3SigningEnabled(true)
|
||||||
.setInputApk(apkFile)
|
.setInputApk(apkFile)
|
||||||
.setOutputApk(outputApk)
|
.setOutputApk(tmpApk)
|
||||||
.build()
|
.build()
|
||||||
.sign()
|
.sign()
|
||||||
|
|
||||||
outputApk.renameTo(apkFile)
|
tmpApk.renameTo(apkFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
private class KeySet(val publicKey: X509Certificate, val privateKey: PrivateKey)
|
private class KeySet(val publicKey: X509Certificate, val privateKey: PrivateKey)
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,34 @@
|
||||||
package com.aliucord.manager.manager
|
package com.aliucord.manager.manager
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.os.Environment
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A central place to provide all system paths that are used.
|
* A central place to provide all system paths that are used.
|
||||||
*/
|
*/
|
||||||
class PathManager(context: Context) {
|
class PathManager(context: Context) {
|
||||||
|
/**
|
||||||
|
* The Aliucord folder in which plugins/settings/themes are stored.
|
||||||
|
* Standard path: `~/Aliucord`
|
||||||
|
*/
|
||||||
|
val aliucordDir = Environment.getExternalStorageDirectory().resolve("Aliucord")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The directory in external storage in which plugins are stored by Aliucord.
|
||||||
|
*/
|
||||||
|
val pluginsDir = aliucordDir.resolve("plugins")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The settings file in which Aliucord's core uses.
|
||||||
|
*/
|
||||||
|
val coreSettingsFile = aliucordDir.resolve("settings/Aliucord.json")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Global keystore used for signing APKs.
|
||||||
|
*/
|
||||||
|
val keystoreFile = aliucordDir.resolve("ks.keystore")
|
||||||
|
|
||||||
private val externalCacheDir = context.externalCacheDir
|
private val externalCacheDir = context.externalCacheDir
|
||||||
?: throw Error("External cache directory isn't supported")
|
?: throw Error("External cache directory isn't supported")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,13 +5,15 @@ import androidx.compose.runtime.*
|
||||||
import cafe.adriel.voyager.core.model.ScreenModel
|
import cafe.adriel.voyager.core.model.ScreenModel
|
||||||
import cafe.adriel.voyager.core.model.screenModelScope
|
import cafe.adriel.voyager.core.model.screenModelScope
|
||||||
import com.aliucord.manager.BuildConfig
|
import com.aliucord.manager.BuildConfig
|
||||||
import com.aliucord.manager.aliucordDir
|
|
||||||
import com.aliucord.manager.domain.model.Plugin
|
import com.aliucord.manager.domain.model.Plugin
|
||||||
|
import com.aliucord.manager.manager.PathManager
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.serialization.ExperimentalSerializationApi
|
import kotlinx.serialization.ExperimentalSerializationApi
|
||||||
import kotlinx.serialization.json.*
|
import kotlinx.serialization.json.*
|
||||||
|
|
||||||
class PluginsModel : ScreenModel {
|
class PluginsModel(
|
||||||
|
private val paths: PathManager,
|
||||||
|
) : ScreenModel {
|
||||||
var showChangelogDialog by mutableStateOf(false)
|
var showChangelogDialog by mutableStateOf(false)
|
||||||
private set
|
private set
|
||||||
|
|
||||||
|
|
@ -57,8 +59,6 @@ class PluginsModel : ScreenModel {
|
||||||
plugins.remove(plugin)
|
plugins.remove(plugin)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val settingsFile = aliucordDir.resolve("settings/Aliucord.json")
|
|
||||||
|
|
||||||
fun setPluginEnabled(plugin: String, enable: Boolean) {
|
fun setPluginEnabled(plugin: String, enable: Boolean) {
|
||||||
screenModelScope.launch(Dispatchers.IO) {
|
screenModelScope.launch(Dispatchers.IO) {
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
|
|
@ -69,13 +69,13 @@ class PluginsModel : ScreenModel {
|
||||||
|
|
||||||
@OptIn(ExperimentalSerializationApi::class)
|
@OptIn(ExperimentalSerializationApi::class)
|
||||||
try {
|
try {
|
||||||
val settings = settingsFile.inputStream()
|
val settings = paths.coreSettingsFile.inputStream()
|
||||||
.use { Json.decodeFromStream<Map<String, JsonElement>>(it) }
|
.use { Json.decodeFromStream<Map<String, JsonElement>>(it) }
|
||||||
.toMutableMap()
|
.toMutableMap()
|
||||||
|
|
||||||
settings["AC_PM_$plugin"] = JsonPrimitive(enable)
|
settings["AC_PM_$plugin"] = JsonPrimitive(enable)
|
||||||
|
|
||||||
settingsFile.outputStream()
|
paths.coreSettingsFile.outputStream()
|
||||||
.use { Json.encodeToStream(settings, it) }
|
.use { Json.encodeToStream(settings, it) }
|
||||||
} catch (t: Throwable) {
|
} catch (t: Throwable) {
|
||||||
Log.e(BuildConfig.TAG, "Failed to write Aliucord.json: ${Log.getStackTraceString(t)}")
|
Log.e(BuildConfig.TAG, "Failed to write Aliucord.json: ${Log.getStackTraceString(t)}")
|
||||||
|
|
@ -85,11 +85,11 @@ class PluginsModel : ScreenModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun loadEnabled(): Map<String, Boolean> = withContext(Dispatchers.IO) {
|
private suspend fun loadEnabled(): Map<String, Boolean> = withContext(Dispatchers.IO) {
|
||||||
if (!settingsFile.exists()) return@withContext emptyMap()
|
if (!paths.coreSettingsFile.exists()) return@withContext emptyMap()
|
||||||
|
|
||||||
@OptIn(ExperimentalSerializationApi::class)
|
@OptIn(ExperimentalSerializationApi::class)
|
||||||
try {
|
try {
|
||||||
settingsFile.inputStream()
|
paths.coreSettingsFile.inputStream()
|
||||||
.use { Json.decodeFromStream<Map<String, JsonElement>>(it) }
|
.use { Json.decodeFromStream<Map<String, JsonElement>>(it) }
|
||||||
.filterKeys { it.startsWith("AC_PM_") }
|
.filterKeys { it.startsWith("AC_PM_") }
|
||||||
.mapKeys { it.key.substring("AC_PM_".length) }
|
.mapKeys { it.key.substring("AC_PM_".length) }
|
||||||
|
|
@ -107,14 +107,12 @@ class PluginsModel : ScreenModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
screenModelScope.launch(Dispatchers.IO) {
|
screenModelScope.launch(Dispatchers.IO) {
|
||||||
val pluginsDir = aliucordDir.resolve("plugins")
|
if (!paths.pluginsDir.exists() && !paths.pluginsDir.mkdirs()) {
|
||||||
|
|
||||||
if (!pluginsDir.exists() && !pluginsDir.mkdirs()) {
|
|
||||||
Log.e(BuildConfig.TAG, "Failed to create plugins dir. Missing Permissions?")
|
Log.e(BuildConfig.TAG, "Failed to create plugins dir. Missing Permissions?")
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
|
|
||||||
val files = pluginsDir.listFiles { file -> file.extension == "zip" } ?: return@launch
|
val files = paths.pluginsDir.listFiles { file -> file.extension == "zip" } ?: return@launch
|
||||||
val loadedPlugins = files.mapNotNull { file ->
|
val loadedPlugins = files.mapNotNull { file ->
|
||||||
try {
|
try {
|
||||||
Plugin(file)
|
Plugin(file)
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue