finish room and kotlin migration

SettingsActivity.java cannot be converted right now because AS crashes silently

Signed-off-by: androidacy-user <opensource@androidacy.com>
pull/89/head
androidacy-user 2 years ago
parent 0b05dfe29b
commit 743cd3f46d

@ -72,6 +72,10 @@ android {
"en"
)
)
// ksp room processor
room {
schemaLocationDir.set(file("roomSchemas"))
}
}
splits {
@ -525,9 +529,6 @@ dependencies {
// yes
implementation("com.github.fingerprintjs:fingerprint-android:2.0.0")
// encryption for room
implementation("net.zetetic:android-database-sqlcipher:4.5.4")
// room
implementation("androidx.room:room-runtime:2.5.1")

@ -0,0 +1,142 @@
{
"formatVersion": 1,
"database": {
"version": 1,
"identityHash": "2abfd2dad332c887d96cdda63d92a639",
"entities": [
{
"tableName": "modulelistcache",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`codename` TEXT NOT NULL, `version` TEXT NOT NULL, `versionCode` INTEGER NOT NULL, `author` TEXT NOT NULL, `description` TEXT NOT NULL, `minApi` INTEGER NOT NULL, `maxApi` INTEGER NOT NULL, `minMagisk` INTEGER NOT NULL, `needRamdisk` INTEGER NOT NULL, `support` TEXT NOT NULL, `donate` TEXT NOT NULL, `config` TEXT NOT NULL, `changeBoot` INTEGER NOT NULL, `mmtReborn` INTEGER NOT NULL, `repoId` TEXT NOT NULL, `lastUpdate` INTEGER NOT NULL, `name` TEXT NOT NULL, `safe` INTEGER NOT NULL, `stats` INTEGER NOT NULL, PRIMARY KEY(`codename`))",
"fields": [
{
"fieldPath": "codename",
"columnName": "codename",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "version",
"columnName": "version",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "versionCode",
"columnName": "versionCode",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "author",
"columnName": "author",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "description",
"columnName": "description",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "minApi",
"columnName": "minApi",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "maxApi",
"columnName": "maxApi",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "minMagisk",
"columnName": "minMagisk",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "needRamdisk",
"columnName": "needRamdisk",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "support",
"columnName": "support",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "donate",
"columnName": "donate",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "config",
"columnName": "config",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "changeBoot",
"columnName": "changeBoot",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "mmtReborn",
"columnName": "mmtReborn",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "repoId",
"columnName": "repoId",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "lastUpdate",
"columnName": "lastUpdate",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "safe",
"columnName": "safe",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "stats",
"columnName": "stats",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"codename"
]
},
"indices": [],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '2abfd2dad332c887d96cdda63d92a639')"
]
}
}

@ -0,0 +1,82 @@
{
"formatVersion": 1,
"database": {
"version": 1,
"identityHash": "5a217dbf3caa5a6b70e7eee98636866c",
"entities": [
{
"tableName": "ReposList",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `url` TEXT NOT NULL, `enabled` INTEGER NOT NULL, `donate` TEXT, `support` TEXT, `submitModule` TEXT, `lastUpdate` INTEGER NOT NULL, `name` TEXT NOT NULL, `website` TEXT, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "enabled",
"columnName": "enabled",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "donate",
"columnName": "donate",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "support",
"columnName": "support",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "submitModule",
"columnName": "submitModule",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "lastUpdate",
"columnName": "lastUpdate",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "website",
"columnName": "website",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '5a217dbf3caa5a6b70e7eee98636866c')"
]
}
}

Binary file not shown.

@ -28,7 +28,6 @@ import androidx.appcompat.widget.SearchView
import androidx.cardview.widget.CardView
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.room.Room
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener
import com.fox2code.foxcompat.app.FoxActivity
@ -55,7 +54,6 @@ import com.fox2code.mmm.utils.RuntimeUtils
import com.fox2code.mmm.utils.SyncManager
import com.fox2code.mmm.utils.io.net.Http.Companion.cleanDnsCache
import com.fox2code.mmm.utils.io.net.Http.Companion.hasWebView
import com.fox2code.mmm.utils.room.ReposListDatabase
import com.google.android.material.bottomnavigation.BottomNavigationView
import com.google.android.material.progressindicator.LinearProgressIndicator
import org.matomo.sdk.extra.TrackHelper
@ -102,25 +100,6 @@ class MainActivity : FoxActivity(), OnRefreshListener, SearchView.OnQueryTextLis
onMainActivityCreate(this)
super.onCreate(savedInstanceState)
TrackHelper.track().screen(this).with(MainApplication.INSTANCE!!.tracker)
// track enabled repos
val db = Room.databaseBuilder(
applicationContext,
ReposListDatabase::class.java,
"reposlist.db"
).build()
val repoDao = db.reposListDao()
val repos = repoDao.getAll()
val enabledRepos = StringBuilder()
for (repo in repos) {
if (repo.enabled) {
enabledRepos.append(repo.url).append(", ")
}
}
if (enabledRepos.isNotEmpty()) {
enabledRepos.delete(enabledRepos.length - 2, enabledRepos.length)
TrackHelper.track().event("Enabled Repos", enabledRepos.toString())
.with(MainApplication.INSTANCE!!.tracker)
}
// hide this behind a buildconfig flag for now, but crash the app if it's not an official build and not debug
if (BuildConfig.ENABLE_PROTECTION && !MainApplication.o && !BuildConfig.DEBUG) {
throw RuntimeException("This is not an official build of AMM")

@ -14,9 +14,6 @@ import android.content.pm.PackageManager
import android.content.res.Resources
import android.os.Build
import android.os.SystemClock
import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyProperties
import android.util.Base64
import android.util.Log
import androidx.annotation.StyleRes
import androidx.core.app.NotificationManagerCompat
@ -44,35 +41,16 @@ import io.noties.markwon.Markwon
import io.noties.markwon.html.HtmlPlugin
import io.noties.markwon.image.ImagesPlugin
import io.noties.markwon.image.network.OkHttpNetworkSchemeHandler
import io.realm.Realm
import org.matomo.sdk.Matomo
import org.matomo.sdk.Tracker
import org.matomo.sdk.TrackerBuilder
import org.matomo.sdk.extra.TrackHelper
import timber.log.Timber
import java.io.File
import java.io.IOException
import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.security.InvalidAlgorithmParameterException
import java.security.InvalidKeyException
import java.security.KeyStore
import java.security.KeyStoreException
import java.security.NoSuchAlgorithmException
import java.security.NoSuchProviderException
import java.security.SecureRandom
import java.security.UnrecoverableKeyException
import java.security.cert.CertificateException
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Random
import javax.crypto.BadPaddingException
import javax.crypto.Cipher
import javax.crypto.IllegalBlockSizeException
import javax.crypto.KeyGenerator
import javax.crypto.NoSuchPaddingException
import javax.crypto.SecretKey
import javax.crypto.spec.IvParameterSpec
import kotlin.math.abs
import kotlin.math.max
@ -94,7 +72,7 @@ class MainApplication : FoxApplication(), Configuration.Provider {
@JvmField
var markwon: Markwon? = null
private var existingKey: ByteArray? = null
private var existingKey: CharArray? = null
@JvmField
var tracker: Tracker? = null
@ -106,6 +84,38 @@ class MainApplication : FoxApplication(), Configuration.Provider {
INSTANCE = this
}
// generates or retrieves a key for encrypted room databases
@SuppressLint("ApplySharedPref")
fun getKey(): CharArray {
// check if existing key is available
if (existingKey != null) {
return existingKey!!
}
// use android keystore to generate a key
val masterKey = MasterKey.Builder(this).setKeyScheme(MasterKey.KeyScheme.AES256_GCM).build()
val sharedPreferences = EncryptedSharedPreferences.create(
this,
"dbKey",
masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
val key = sharedPreferences.getString("dbKey", null)
if (key != null) {
existingKey = key.toCharArray()
return existingKey!!
}
// generate a new key
val newKey = CharArray(32)
val random = SecureRandom()
for (i in newKey.indices) {
newKey[i] = (random.nextInt(26) + 97).toChar()
}
sharedPreferences.edit().putString("dbKey", String(newKey)).commit()
existingKey = newKey
return existingKey!!
}
fun getMarkwon(): Markwon? {
if (isCrashHandler) return null
if (markwon != null) return markwon
@ -178,8 +188,7 @@ class MainApplication : FoxApplication(), Configuration.Provider {
@SuppressLint("NonConstantResourceId")
override fun isLightTheme(): Boolean {
return when (getSharedPreferences("mmm")!!
.getString("pref_theme", "system")) {
return when (getSharedPreferences("mmm")!!.getString("pref_theme", "system")) {
"system" -> isSystemLightTheme
"dark", "black" -> false
else -> true
@ -253,9 +262,6 @@ class MainApplication : FoxApplication(), Configuration.Provider {
Timber.d("Initializing AMM")
Timber.d("Started from background: %s", !isInForeground)
Timber.d("AMM is running in debug mode")
Timber.d("Initializing Realm")
Realm.init(this)
Timber.d("Initialized Realm")
// analytics
Timber.d("Initializing matomo")
getTracker()
@ -265,22 +271,18 @@ class MainApplication : FoxApplication(), Configuration.Provider {
} else {
tracker!!.isOptOut = false
}
if (getSharedPreferences("matomo")!!
.getBoolean("install_tracked", false)
) {
if (getSharedPreferences("matomo")!!.getBoolean("install_tracked", false)) {
TrackHelper.track().download().with(INSTANCE!!.getTracker())
Timber.d("Sent install event to matomo")
getSharedPreferences("matomo")!!
.edit().putBoolean("install_tracked", true).apply()
getSharedPreferences("matomo")!!.edit().putBoolean("install_tracked", true).apply()
} else {
Timber.d("Matomo already has install")
}
try {
@Suppress("DEPRECATION")
@SuppressLint("PackageManagerGetSignatures") val s = this.packageManager.getPackageInfo(
this.packageName,
PackageManager.GET_SIGNATURES
).signatures
@Suppress("DEPRECATION") @SuppressLint("PackageManagerGetSignatures") val s =
this.packageManager.getPackageInfo(
this.packageName, PackageManager.GET_SIGNATURES
).signatures
@Suppress("SpellCheckingInspection") val osh = arrayOf(
"7bec7c4462f4aac616612d9f56a023ee3046e83afa956463b5fab547fd0a0be6",
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
@ -322,8 +324,7 @@ class MainApplication : FoxApplication(), Configuration.Provider {
Timber.i("Emoji compat loaded!")
}, "Emoji compat init.").start()
}
@Suppress("KotlinConstantConditions")
if ((BuildConfig.ANDROIDACY_CLIENT_ID == "")) {
@Suppress("KotlinConstantConditions") if ((BuildConfig.ANDROIDACY_CLIENT_ID == "")) {
Timber.w("Androidacy client id is empty! Please set it in androidacy.properties. Will not enable Androidacy.")
val editor = sharedPreferences!!.edit()
editor.putBoolean("pref_androidacy_repo_enabled", false)
@ -371,8 +372,7 @@ class MainApplication : FoxApplication(), Configuration.Provider {
if (!dataDir.exists()) {
if (!dataDir.mkdirs()) {
if (BuildConfig.DEBUG) Timber.w(
"Failed to create directory %s",
dataDir
"Failed to create directory %s", dataDir
)
}
}
@ -413,9 +413,7 @@ class MainApplication : FoxApplication(), Configuration.Provider {
val packageName = this.packageName
for (appProcess in appProcesses) {
Timber.d(
"Process: %s, Importance: %d",
appProcess.processName,
appProcess.importance
"Process: %s, Importance: %d", appProcess.processName, appProcess.importance
)
if (appProcess.importance == RunningAppProcessInfo.IMPORTANCE_FOREGROUND && appProcess.processName == packageName) {
return true
@ -435,237 +433,6 @@ class MainApplication : FoxApplication(), Configuration.Provider {
}
}// sleep until the key is made
// attempt to read the existingKey property
// check if we have a key already
// open a connection to the android keystore
// create a securely generated random asymmetric RSA key
// create a cipher that uses AES encryption -- we'll use this to encrypt our key
// generate secret key
// access the generated key in the android keystore, then
// use the cipher to create an encrypted version of the key
// keep the encrypted key in shared preferences
// to persist it across application runs
// pass to a realm configuration via encryptionKey()
// Create a key to encrypt a realm and save it securely in the keystore
val key: ByteArray
get() {
if (makingNewKey) {
// sleep until the key is made
while (makingNewKey) try {
Thread.sleep(100)
} catch (ignored: InterruptedException) {
Thread.currentThread().interrupt()
}
}
// attempt to read the existingKey property
if (existingKey != null && existingKey!!.isNotEmpty()) {
return existingKey as ByteArray
}
// check if we have a key already
val sharedPreferences = getSharedPreferences("realm_key")
makingNewKey = if (sharedPreferences!!.contains("iv_and_encrypted_key")) {
return getExistingKey()
} else {
true
}
// open a connection to the android keystore
val keyStore: KeyStore
try {
keyStore = KeyStore.getInstance("AndroidKeyStore")
keyStore.load(null)
} catch (e: KeyStoreException) {
Timber.v("Failed to open the keystore.")
throw RuntimeException(e)
} catch (e: NoSuchAlgorithmException) {
Timber.v("Failed to open the keystore.")
throw RuntimeException(e)
} catch (e: CertificateException) {
Timber.v("Failed to open the keystore.")
throw RuntimeException(e)
} catch (e: IOException) {
Timber.v("Failed to open the keystore.")
throw RuntimeException(e)
}
// create a securely generated random asymmetric RSA key
val realmKey = ByteArray(Realm.ENCRYPTION_KEY_LENGTH)
do {
SecureRandom().nextBytes(realmKey)
} while (realmKey[0].toInt() == 0)
// create a cipher that uses AES encryption -- we'll use this to encrypt our key
val cipher: Cipher = try {
Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_CBC + "/" + KeyProperties.ENCRYPTION_PADDING_PKCS7)
} catch (e: NoSuchAlgorithmException) {
Timber.e("Failed to create a cipher.")
throw RuntimeException(e)
} catch (e: NoSuchPaddingException) {
Timber.e("Failed to create a cipher.")
throw RuntimeException(e)
}
Timber.v("Cipher created.")
// generate secret key
val keyGenerator: KeyGenerator = try {
KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore")
} catch (e: NoSuchAlgorithmException) {
Timber.e("Failed to access the key generator.")
throw RuntimeException(e)
} catch (e: NoSuchProviderException) {
Timber.e("Failed to access the key generator.")
throw RuntimeException(e)
}
val keySpec = KeyGenParameterSpec.Builder(
"realm_key",
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
).setBlockModes(KeyProperties.BLOCK_MODE_CBC)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7).build()
try {
keyGenerator.init(keySpec)
} catch (e: InvalidAlgorithmParameterException) {
Timber.e("Failed to generate a secret key.")
throw RuntimeException(e)
}
Timber.v("Secret key generated.")
keyGenerator.generateKey()
Timber.v("Secret key stored in the keystore.")
// access the generated key in the android keystore, then
// use the cipher to create an encrypted version of the key
val initializationVector: ByteArray
val encryptedKeyForRealm: ByteArray
try {
val secretKey = keyStore.getKey("realm_key", null) as SecretKey
cipher.init(Cipher.ENCRYPT_MODE, secretKey)
encryptedKeyForRealm = cipher.doFinal(realmKey)
initializationVector = cipher.iv
} catch (e: InvalidKeyException) {
Timber.e("Failed encrypting the key with the secret key.")
throw RuntimeException(e)
} catch (e: UnrecoverableKeyException) {
Timber.e("Failed encrypting the key with the secret key.")
throw RuntimeException(e)
} catch (e: NoSuchAlgorithmException) {
Timber.e("Failed encrypting the key with the secret key.")
throw RuntimeException(e)
} catch (e: KeyStoreException) {
Timber.e("Failed encrypting the key with the secret key.")
throw RuntimeException(e)
} catch (e: BadPaddingException) {
Timber.e("Failed encrypting the key with the secret key.")
throw RuntimeException(e)
} catch (e: IllegalBlockSizeException) {
Timber.e("Failed encrypting the key with the secret key.")
throw RuntimeException(e)
}
// keep the encrypted key in shared preferences
// to persist it across application runs
val initializationVectorAndEncryptedKey =
ByteArray(Integer.BYTES + initializationVector.size + encryptedKeyForRealm.size)
val buffer = ByteBuffer.wrap(initializationVectorAndEncryptedKey)
buffer.order(ByteOrder.BIG_ENDIAN)
buffer.putInt(initializationVector.size)
buffer.put(initializationVector)
buffer.put(encryptedKeyForRealm)
Timber.d("Created all keys successfully.")
getSharedPreferences("realm_key")!!
.edit().putString(
"iv_and_encrypted_key",
Base64.encodeToString(initializationVectorAndEncryptedKey, Base64.NO_WRAP)
).apply()
Timber.d("Saved the encrypted key in shared preferences.")
makingNewKey = false
Timber.d("Returning the new key. Key length: %d", realmKey.size)
return realmKey // pass to a realm configuration via encryptionKey()
}
// Access the encrypted key in the keystore, decrypt it with the secret,
// and use it to open and read from the realm again
fun getExistingKey(): ByteArray {
Timber.v("Getting existing key.")
// attempt to read the existingKey property
if (existingKey != null) {
Timber.v("Existing key found in memory.")
return existingKey as ByteArray
}
// open a connection to the android keystore
val keyStore: KeyStore
try {
keyStore = KeyStore.getInstance("AndroidKeyStore")
keyStore.load(null)
} catch (e: KeyStoreException) {
Timber.e("Failed to open the keystore.")
throw RuntimeException(e)
} catch (e: NoSuchAlgorithmException) {
Timber.e("Failed to open the keystore.")
throw RuntimeException(e)
} catch (e: CertificateException) {
Timber.e("Failed to open the keystore.")
throw RuntimeException(e)
} catch (e: IOException) {
Timber.e("Failed to open the keystore.")
throw RuntimeException(e)
}
Timber.v("Keystore opened.")
// access the encrypted key that's stored in shared preferences
val initializationVectorAndEncryptedKey = Base64.decode(
getSharedPreferences("realm_key")!!
.getString("iv_and_encrypted_key", null), Base64.DEFAULT
)
Timber.d(
"Retrieved the encrypted key from shared preferences. Key length: %d",
initializationVectorAndEncryptedKey.size
)
val buffer = ByteBuffer.wrap(initializationVectorAndEncryptedKey)
buffer.order(ByteOrder.BIG_ENDIAN)
// extract the length of the initialization vector from the buffer
val initializationVectorLength = buffer.int
// extract the initialization vector based on that length
val initializationVector = ByteArray(initializationVectorLength)
buffer[initializationVector]
// extract the encrypted key
val encryptedKey =
ByteArray(initializationVectorAndEncryptedKey.size - Integer.BYTES - initializationVectorLength)
buffer[encryptedKey]
Timber.d("Got key from shared preferences.")
// create a cipher that uses AES encryption to decrypt our key
val cipher: Cipher = try {
Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_CBC + "/" + KeyProperties.ENCRYPTION_PADDING_PKCS7)
} catch (e: NoSuchAlgorithmException) {
Timber.e("Failed to create cipher.")
throw RuntimeException(e)
} catch (e: NoSuchPaddingException) {
Timber.e("Failed to create cipher.")
throw RuntimeException(e)
}
// decrypt the encrypted key with the secret key stored in the keystore
val decryptedKey: ByteArray = try {
val secretKey = keyStore.getKey("realm_key", null) as SecretKey
val initializationVectorSpec = IvParameterSpec(initializationVector)
cipher.init(Cipher.DECRYPT_MODE, secretKey, initializationVectorSpec)
cipher.doFinal(encryptedKey)
} catch (e: InvalidKeyException) {
Timber.e("Failed to decrypt. Invalid key.")
throw RuntimeException(e)
} catch (e: UnrecoverableKeyException) {
Timber.e("Failed to decrypt the encrypted realm key with the secret key.")
throw RuntimeException(e)
} catch (e: NoSuchAlgorithmException) {
Timber.e("Failed to decrypt the encrypted realm key with the secret key.")
throw RuntimeException(e)
} catch (e: BadPaddingException) {
Timber.e("Failed to decrypt the encrypted realm key with the secret key.")
throw RuntimeException(e)
} catch (e: KeyStoreException) {
Timber.e("Failed to decrypt the encrypted realm key with the secret key.")
throw RuntimeException(e)
} catch (e: IllegalBlockSizeException) {
Timber.e("Failed to decrypt the encrypted realm key with the secret key.")
throw RuntimeException(e)
} catch (e: InvalidAlgorithmParameterException) {
Timber.e("Failed to decrypt the encrypted realm key with the secret key.")
throw RuntimeException(e)
}
// set property on MainApplication to indicate that the key has been accessed
existingKey = decryptedKey
return decryptedKey // pass to a realm configuration via encryptionKey()
}
fun resetUpdateModule() {
modulesHaveUpdates = false
@ -728,12 +495,11 @@ class MainApplication : FoxApplication(), Configuration.Provider {
var updateCheckBg: String? = null
init {
Shell.setDefaultBuilder(
Shell.Builder.create()
.setFlags(Shell.FLAG_REDIRECT_STDERR or Shell.FLAG_MOUNT_MASTER).setTimeout(15)
.setInitializers(
InstallerInitializer::class.java
).also { shellBuilder = it })
Shell.setDefaultBuilder(Shell.Builder.create()
.setFlags(Shell.FLAG_REDIRECT_STDERR or Shell.FLAG_MOUNT_MASTER).setTimeout(15)
.setInitializers(
InstallerInitializer::class.java
).also { shellBuilder = it })
val random = Random()
do {
secret = random.nextLong()
@ -749,8 +515,7 @@ class MainApplication : FoxApplication(), Configuration.Provider {
val packageName = componentName?.packageName ?: (intent.getPackage())!!
require(
!(!BuildConfig.APPLICATION_ID.equals(
packageName,
ignoreCase = true
packageName, ignoreCase = true
) && relPackageName != packageName)
) {
// Code safeguard, we should never reach here.
@ -772,7 +537,8 @@ class MainApplication : FoxApplication(), Configuration.Provider {
}
/*
this part is only here because with added encryption, parts of code that were previously calling this over and over again or on each invocation of a method are causing performance issues.
*/if (BuildConfig.DEBUG) {
*/
if (BuildConfig.DEBUG) {
// get file, function, and line number
val stackTrace = Thread.currentThread().stackTrace
// get the caller of this method
@ -787,8 +553,7 @@ class MainApplication : FoxApplication(), Configuration.Provider {
// add the caller to an array. if the last 3 callers are the same, then we are in a loop, log at error level
callers.add(name + ":" + caller.lineNumber + ":" + caller.methodName)
// get the last 3 callers
val last3: List<String> =
callers.subList(max(callers.size - 3, 0), callers.size)
val last3: List<String> = callers.subList(max(callers.size - 3, 0), callers.size)
// if the last 3 callers are the same, then we are in a loop, log at error level
if (((last3.size == 3) && last3[0] == last3[1]) && last3[1] == last3[2]) {
Timber.e(
@ -834,50 +599,49 @@ class MainApplication : FoxApplication(), Configuration.Provider {
// convert from String to boolean
return java.lang.Boolean.parseBoolean(SHOWCASE_MODE_TRUE)
}
val showcaseMode = getSharedPreferences("mmm")!!
.getBoolean("pref_showcase_mode", false)
val showcaseMode =
getSharedPreferences("mmm")!!.getBoolean("pref_showcase_mode", false)
SHOWCASE_MODE_TRUE = showcaseMode.toString()
return showcaseMode
}
fun shouldPreventReboot(): Boolean {
return getSharedPreferences("mmm")!!
.getBoolean("pref_prevent_reboot", true)
return getSharedPreferences("mmm")!!.getBoolean("pref_prevent_reboot", true)
}
val isShowIncompatibleModules: Boolean
get() = getSharedPreferences("mmm")!!
.getBoolean("pref_show_incompatible", false)
get() = getSharedPreferences("mmm")!!.getBoolean("pref_show_incompatible", false)
val isForceDarkTerminal: Boolean
get() = getSharedPreferences("mmm")!!
.getBoolean("pref_force_dark_terminal", false)
get() = getSharedPreferences("mmm")!!.getBoolean("pref_force_dark_terminal", false)
val isTextWrapEnabled: Boolean
get() = getSharedPreferences("mmm")!!
.getBoolean("pref_wrap_text", false)
get() = getSharedPreferences("mmm")!!.getBoolean("pref_wrap_text", false)
val isDohEnabled: Boolean
get() = getSharedPreferences("mmm")!!
.getBoolean("pref_dns_over_https", true)
get() = getSharedPreferences("mmm")!!.getBoolean("pref_dns_over_https", true)
val isMonetEnabled: Boolean
get() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && getSharedPreferences("mmm")!!
.getBoolean("pref_enable_monet", true)
get() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && getSharedPreferences("mmm")!!.getBoolean(
"pref_enable_monet",
true
)
val isBlurEnabled: Boolean
get() = getSharedPreferences("mmm")!!
.getBoolean("pref_enable_blur", false)
get() = getSharedPreferences("mmm")!!.getBoolean("pref_enable_blur", false)
@JvmStatic
val isDeveloper: Boolean
get() {
return if (BuildConfig.DEBUG) true else getSharedPreferences("mmm")!!
.getBoolean("developer", false)
return if (BuildConfig.DEBUG) true else getSharedPreferences("mmm")!!.getBoolean(
"developer",
false
)
}
val isDisableLowQualityModuleFilter: Boolean
get() = getSharedPreferences("mmm")!!
.getBoolean("pref_disable_low_quality_module_filter", false) && isDeveloper
get() = getSharedPreferences("mmm")!!.getBoolean(
"pref_disable_low_quality_module_filter",
false
) && isDeveloper
val isUsingMagiskCommand: Boolean
get() = (peekMagiskVersion() >= Constants.MAGISK_VER_CODE_INSTALL_COMMAND) && getSharedPreferences(
"mmm"
)!!
.getBoolean("pref_use_magisk_install_command", false) && isDeveloper
)!!.getBoolean("pref_use_magisk_install_command", false) && isDeveloper
@JvmStatic
val isBackgroundUpdateCheckEnabled: Boolean
@ -886,23 +650,28 @@ class MainApplication : FoxApplication(), Configuration.Provider {
return java.lang.Boolean.parseBoolean(updateCheckBg)
}
val wrapped = isWrapped
val updateCheckBgTemp = !wrapped && getSharedPreferences("mmm")!!
.getBoolean("pref_background_update_check", true)
val updateCheckBgTemp = !wrapped && getSharedPreferences("mmm")!!.getBoolean(
"pref_background_update_check",
true
)
updateCheckBg = updateCheckBgTemp.toString()
return java.lang.Boolean.parseBoolean(updateCheckBg)
}
val isAndroidacyTestMode: Boolean
get() = isDeveloper && getSharedPreferences("mmm")!!
.getBoolean("pref_androidacy_test_mode", false)
get() = isDeveloper && getSharedPreferences("mmm")!!.getBoolean(
"pref_androidacy_test_mode",
false
)
fun setHasGottenRootAccess(bool: Boolean) {
getSharedPreferences("mmm")!!
.edit().putBoolean("has_root_access", bool).apply()
getSharedPreferences("mmm")!!.edit().putBoolean("has_root_access", bool).apply()
}
val isCrashReportingEnabled: Boolean
get() = SentryMain.IS_SENTRY_INSTALLED && getSharedPreferences("mmm")!!
.getBoolean("pref_crash_reporting", BuildConfig.DEFAULT_ENABLE_CRASH_REPORTING)
get() = SentryMain.IS_SENTRY_INSTALLED && getSharedPreferences("mmm")!!.getBoolean(
"pref_crash_reporting",
BuildConfig.DEFAULT_ENABLE_CRASH_REPORTING
)
val bootSharedPreferences: SharedPreferences?
get() = getSharedPreferences("mmm_boot")
@ -918,8 +687,10 @@ class MainApplication : FoxApplication(), Configuration.Provider {
@JvmStatic
fun isMatomoAllowed(): Boolean {
return getSharedPreferences("mmm")!!
.getBoolean("pref_analytics_enabled", BuildConfig.DEFAULT_ENABLE_ANALYTICS)
return getSharedPreferences("mmm")!!.getBoolean(
"pref_analytics_enabled",
BuildConfig.DEFAULT_ENABLE_ANALYTICS
)
}
}
}

@ -21,8 +21,11 @@ import androidx.fragment.app.FragmentActivity
import androidx.room.Room
import com.fox2code.foxcompat.app.FoxActivity
import com.fox2code.mmm.databinding.ActivitySetupBinding
import com.fox2code.mmm.repo.RepoManager
import com.fox2code.mmm.utils.IntentHelper
import com.fox2code.mmm.utils.room.ModuleListCache
import com.fox2code.mmm.utils.room.ModuleListCacheDatabase
import com.fox2code.mmm.utils.room.ReposList
import com.fox2code.mmm.utils.room.ReposListDatabase
import com.fox2code.rosettax.LanguageActivity
import com.fox2code.rosettax.LanguageSwitcher
@ -227,10 +230,11 @@ class SetupActivity : FoxActivity(), LanguageActivity {
val db = Room.databaseBuilder(
applicationContext,
ReposListDatabase::class.java, "ReposList.db"
).build()
).allowMainThreadQueries().build()
val androidacyRepoRoom = andRepoView.isChecked
val magiskAltRepoRoom = magiskAltRepoView.isChecked
val reposListDao = db.reposListDao()
Timber.d(reposListDao.getAll().toString())
val androidacyRepoRoomObj = reposListDao.getById("androidacy_repo")
val magiskAltRepoRoomObj = reposListDao.getById("magisk_alt_repo")
reposListDao.setEnabled(androidacyRepoRoomObj.id, androidacyRepoRoom)
@ -239,12 +243,6 @@ class SetupActivity : FoxActivity(), LanguageActivity {
editor.putString("last_shown_setup", "v3")
// Commit the changes
editor.commit()
// sleep to allow the realm transaction to finish
try {
Thread.sleep(250)
} catch (e: InterruptedException) {
Thread.currentThread().interrupt()
}
// Log the changes
Timber.d("Setup finished. Preferences: %s", prefs.all)
Timber.d("Androidacy repo: %s", androidacyRepoRoom)
@ -329,18 +327,111 @@ class SetupActivity : FoxActivity(), LanguageActivity {
// creates the room database
private fun createDatabases() {
val startTime = System.currentTimeMillis()
val appContext = MainApplication.INSTANCE!!.applicationContext
Room.databaseBuilder(appContext, ReposListDatabase::class.java, "reposlist.db")
.createFromAsset("assets/reposlist.db")
.fallbackToDestructiveMigration()
.build()
// same for modulelistcache
Room.databaseBuilder(appContext, ModuleListCacheDatabase::class.java, "modulelistcache.db")
.createFromAsset("assets/modulelistcache.db")
.fallbackToDestructiveMigration()
.build()
Timber.d("Databases created in %s ms", System.currentTimeMillis() - startTime)
val thread = Thread {
Timber.d("Creating databases")
val startTime = System.currentTimeMillis()
val appContext = MainApplication.INSTANCE!!.applicationContext
val db = Room.databaseBuilder(appContext, ReposListDatabase::class.java, "ReposList.db")
.fallbackToDestructiveMigration().build()
// same for modulelistcache
val db2 = Room.databaseBuilder(
appContext,
ModuleListCacheDatabase::class.java,
"ModuleListCache.db"
)
.fallbackToDestructiveMigration()
.allowMainThreadQueries().build()
val reposListDao = db.reposListDao()
val moduleListCacheDao = db2.moduleListCacheDao()
// create the androidacy repo
val androidacyRepo = ReposList(
"androidacy_repo",
RepoManager.ANDROIDACY_MAGISK_REPO_ENDPOINT,
true,
"https://www.androidacy.com/membership-join/",
"https://t.me/androidacy",
"https://www.androidacy.com/module-repository-applications//",
0,
"Androidacy Modules Repo",
"https://www.androidacy.com/"
)
// create the magisk alt repo
val magiskAltRepo = ReposList(
"magisk_alt_repo",
RepoManager.MAGISK_ALT_REPO_JSDELIVR,
false,
"",
"",
RepoManager.MAGISK_ALT_REPO_HOMEPAGE + "/submission/",
0,
"Magisk Alt Modules Repo",
RepoManager.MAGISK_ALT_REPO_HOMEPAGE
)
// insert the repos into the database
reposListDao.insert(androidacyRepo)
reposListDao.insert(magiskAltRepo)
// create the modulelistcache
val moduleListCache = ModuleListCache(
codename = "androidacy_repo",
version = "",
versionCode = 0,
author = "",
description = "",
minApi = 0,
maxApi = 99999,
minMagisk = 0,
needRamdisk = false,
support = "",
donate = "",
config = "",
changeBoot = false,
mmtReborn = false,
repoId = "androidacy_repo",
lastUpdate = 0,
name = "",
safe = false,
stats = 0,
)
// insert the modulelistcache into the database
moduleListCacheDao.insert(moduleListCache)
// now make sure reposlist is updated with 2 entries and modulelistcache is updated with 1 entry
val reposList = reposListDao.getAll()
val moduleListCacheList = moduleListCacheDao.getAll()
// make sure reposlist is updated with 2 entries
if (reposList.size != 2) {
Timber.e("ReposList is not updated with 2 entries")
// show a toast
runOnUiThread {
Toast.makeText(
this,
R.string.error_creating_repos_database,
Toast.LENGTH_LONG
).show()
}
} else {
Timber.d("ReposList is updated with 2 entries")
}
// make sure modulelistcache is updated with 1 entry
if (moduleListCacheList.size != 1) {
Timber.e("ModuleListCache is not updated with 1 entry")
// show a toast
runOnUiThread {
Toast.makeText(
this,
R.string.error_creating_modulelistcache_database,
Toast.LENGTH_LONG
).show()
}
} else {
Timber.d("ModuleListCache is updated with 1 entry")
}
// close the databases
db.close()
db2.close()
Timber.d("Databases created in %s ms", System.currentTimeMillis() - startTime)
}
thread.start()
}
private fun createFiles() {

@ -66,12 +66,11 @@ class ModuleManager private constructor() : SyncManager() {
var moduleInfo = moduleInfos[module]
// next, merge the module info with a record from ModuleListCache room db if it exists
// initialize modulelistcache db
// DO NOT USE REALM ANYMORE
val db = Room.databaseBuilder(
MainApplication.INSTANCE!!,
ModuleListCacheDatabase::class.java,
"ModuleListCache"
).build()
).allowMainThreadQueries().build()
// get module info from cache
val moduleListCacheDao: ModuleListCacheDao = db.moduleListCacheDao()
Timber.d("Found cache for %s", module)

@ -1,376 +1,430 @@
/*
* Copyright (c) 2023 to present Androidacy and contributors. Names, logos, icons, and the Androidacy name are all trademarks of Androidacy and may not be used without license. See LICENSE for more information.
*/
package com.fox2code.mmm.module
package com.fox2code.mmm.module;
import android.annotation.SuppressLint
import android.content.DialogInterface
import android.net.Uri
import android.text.Spanned
import android.widget.TextView
import android.widget.Toast
import androidx.annotation.DrawableRes
import com.fox2code.foxcompat.app.FoxActivity
import com.fox2code.foxcompat.view.FoxDisplay
import com.fox2code.mmm.MainApplication.Companion.INSTANCE
import com.fox2code.mmm.MainApplication.Companion.isShowcaseMode
import com.fox2code.mmm.R
import com.fox2code.mmm.androidacy.AndroidacyUtil.Companion.isAndroidacyLink
import com.fox2code.mmm.installer.InstallerInitializer.Companion.peekMagiskPath
import com.fox2code.mmm.manager.ModuleInfo
import com.fox2code.mmm.manager.ModuleManager.Companion.instance
import com.fox2code.mmm.utils.ExternalHelper
import com.fox2code.mmm.utils.IntentHelper.Companion.openConfig
import com.fox2code.mmm.utils.IntentHelper.Companion.openCustomTab
import com.fox2code.mmm.utils.IntentHelper.Companion.openInstaller
import com.fox2code.mmm.utils.IntentHelper.Companion.openMarkdown
import com.fox2code.mmm.utils.IntentHelper.Companion.openUrl
import com.fox2code.mmm.utils.IntentHelper.Companion.openUrlAndroidacy
import com.google.android.material.chip.Chip
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import io.noties.markwon.Markwon
import org.matomo.sdk.extra.TrackHelper
import timber.log.Timber
import java.util.Objects
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.text.Spanned;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.DrawableRes;
import androidx.appcompat.app.AlertDialog;
import com.fox2code.foxcompat.app.FoxActivity;
import com.fox2code.foxcompat.view.FoxDisplay;
import com.fox2code.mmm.MainApplication;
import com.fox2code.mmm.R;
import com.fox2code.mmm.androidacy.AndroidacyUtil;
import com.fox2code.mmm.installer.InstallerInitializer;
import com.fox2code.mmm.manager.LocalModuleInfo;
import com.fox2code.mmm.manager.ModuleInfo;
import com.fox2code.mmm.manager.ModuleManager;
import com.fox2code.mmm.utils.ExternalHelper;
import com.fox2code.mmm.utils.IntentHelper;
import com.google.android.material.chip.Chip;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import org.matomo.sdk.extra.TrackHelper;
import java.util.Objects;
import io.noties.markwon.Markwon;
import timber.log.Timber;
@SuppressWarnings("ReplaceNullCheck")
@Suppress("SENSELESS_COMPARISON")
@SuppressLint("UseCompatLoadingForDrawables")
public enum ActionButtonType {
INFO() {
@Override
public void update(Chip button, ModuleHolder moduleHolder) {
button.setChipIcon(button.getContext().getDrawable(R.drawable.ic_baseline_info_24));
button.setText(R.string.description);
enum class ActionButtonType {
INFO {
override fun update(button: Chip, moduleHolder: ModuleHolder) {
button.chipIcon = button.context.getDrawable(R.drawable.ic_baseline_info_24)
button.setText(R.string.description)
}
@Override
public void doAction(Chip button, ModuleHolder moduleHolder) {
String name;
if (moduleHolder.moduleInfo != null) {
name = moduleHolder.moduleInfo.name;
override fun doAction(button: Chip, moduleHolder: ModuleHolder) {
val name: String? = if (moduleHolder.moduleInfo != null) {
moduleHolder.moduleInfo!!.name
} else {
name = moduleHolder.repoModule.moduleInfo.name;
moduleHolder.repoModule?.moduleInfo?.name
}
TrackHelper.track().event("view_notes", name).with(MainApplication.getINSTANCE().getTracker());
String notesUrl = moduleHolder.repoModule.notesUrl;
if (AndroidacyUtil.Companion.isAndroidacyLink(notesUrl)) {
IntentHelper.openUrlAndroidacy(button.getContext(), notesUrl, false, moduleHolder.repoModule.moduleInfo.name, moduleHolder.getMainModuleConfig());
TrackHelper.track().event("view_notes", name).with(INSTANCE!!.getTracker())
val notesUrl = moduleHolder.repoModule?.notesUrl
if (isAndroidacyLink(notesUrl)) {
openUrlAndroidacy(
button.context,
notesUrl,
false,
moduleHolder.repoModule?.moduleInfo?.name,
moduleHolder.mainModuleConfig
)
} else {
IntentHelper.openMarkdown(button.getContext(), notesUrl, moduleHolder.repoModule.moduleInfo.name, moduleHolder.getMainModuleConfig(), moduleHolder.repoModule.moduleInfo.changeBoot, moduleHolder.repoModule.moduleInfo.needRamdisk, moduleHolder.repoModule.moduleInfo.minMagisk, moduleHolder.repoModule.moduleInfo.minApi, moduleHolder.repoModule.moduleInfo.maxApi);
openMarkdown(
button.context,
notesUrl,
moduleHolder.repoModule?.moduleInfo?.name,
moduleHolder.mainModuleConfig,
moduleHolder.repoModule?.moduleInfo?.changeBoot,
moduleHolder.repoModule?.moduleInfo?.needRamdisk,
moduleHolder.repoModule?.moduleInfo?.minMagisk ?: 0,
moduleHolder.repoModule?.moduleInfo?.minApi ?: 0,
moduleHolder.repoModule?.moduleInfo?.maxApi ?: 9999
)
}
}
@Override
public boolean doActionLong(Chip button, ModuleHolder moduleHolder) {
Context context = button.getContext();
Toast.makeText(context, context.getString(R.string.module_id_prefix) + moduleHolder.moduleId, Toast.LENGTH_SHORT).show();
return true;
override fun doActionLong(button: Chip, moduleHolder: ModuleHolder): Boolean {
val context = button.context
Toast.makeText(
context,
context.getString(R.string.module_id_prefix) + moduleHolder.moduleId,
Toast.LENGTH_SHORT
).show()
return true
}
}, UPDATE_INSTALL() {
@Override
public void update(Chip button, ModuleHolder moduleHolder) {
int icon;
},
UPDATE_INSTALL {
override fun update(button: Chip, moduleHolder: ModuleHolder) {
val icon: Int
if (moduleHolder.hasUpdate()) {
icon = R.drawable.ic_baseline_update_24;
button.setText(R.string.update);
icon = R.drawable.ic_baseline_update_24
button.setText(R.string.update)
} else if (moduleHolder.moduleInfo != null) {
icon = R.drawable.ic_baseline_refresh_24;
button.setText(R.string.reinstall);
icon = R.drawable.ic_baseline_refresh_24
button.setText(R.string.reinstall)
} else {
icon = R.drawable.ic_baseline_system_update_24;
button.setText(R.string.install);
icon = R.drawable.ic_baseline_system_update_24
button.setText(R.string.install)
}
button.setChipIcon(button.getContext().getDrawable(icon));
button.chipIcon = button.context.getDrawable(icon)
}
@Override
public void doAction(Chip button, ModuleHolder moduleHolder) {
ModuleInfo moduleInfo = moduleHolder.getMainModuleInfo();
if (moduleInfo == null) return;
String name;
if (moduleHolder.moduleInfo != null) {
name = moduleHolder.moduleInfo.name;
override fun doAction(button: Chip, moduleHolder: ModuleHolder) {
// if mainmoduleinfo is null, we are in repo mode
val moduleInfo: ModuleInfo = if (moduleHolder.mainModuleInfo != null) {
moduleHolder.mainModuleInfo
} else {
name = moduleHolder.repoModule.moduleInfo.name;
moduleHolder.repoModule?.moduleInfo ?: return
}
TrackHelper.track().event("view_update_install", name).with(MainApplication.getINSTANCE().getTracker());
val name: String? = if (moduleHolder.moduleInfo != null) {
moduleHolder.moduleInfo!!.name
} else {
moduleHolder.repoModule?.moduleInfo?.name
}
TrackHelper.track().event("view_update_install", name).with(INSTANCE!!.getTracker())
// if icon is reinstall, we need to uninstall first - warn the user but don't proceed
if (moduleHolder.moduleInfo != null) {
// get icon of the button
Drawable icon = button.getChipIcon();
if (icon != null && icon.getConstantState() != null) {
Drawable reinstallIcon = button.getContext().getDrawable(R.drawable.ic_baseline_refresh_24);
if (reinstallIcon != null && reinstallIcon.getConstantState() != null) {
if (icon.getConstantState().equals(reinstallIcon.getConstantState())) {
new MaterialAlertDialogBuilder(button.getContext())
.setTitle(R.string.reinstall)
.setMessage(R.string.reinstall_warning)
.setPositiveButton(R.string.reinstall, null)
.show();
return;
val icon = button.chipIcon
if (icon != null && icon.constantState != null) {
val reinstallIcon =
button.context.getDrawable(R.drawable.ic_baseline_refresh_24)
if (reinstallIcon != null && reinstallIcon.constantState != null) {
if (icon.constantState == reinstallIcon.constantState) {
MaterialAlertDialogBuilder(button.context)
.setTitle(R.string.reinstall)
.setMessage(R.string.reinstall_warning)
.setPositiveButton(R.string.reinstall, null)
.show()
return
}
}
}
}
String updateZipUrl = moduleHolder.getUpdateZipUrl();
if (updateZipUrl == null) return;
val updateZipUrl = moduleHolder.updateZipUrl ?: return
// Androidacy manage the selection between download and install
if (AndroidacyUtil.Companion.isAndroidacyLink(updateZipUrl)) {
IntentHelper.openUrlAndroidacy(button.getContext(), updateZipUrl, true, moduleInfo.name, moduleInfo.config);
return;
if (isAndroidacyLink(updateZipUrl)) {
openUrlAndroidacy(
button.context,
updateZipUrl,
true,
moduleInfo.name,
moduleInfo.config
)
return
}
boolean hasRoot = InstallerInitializer.peekMagiskPath() != null && !MainApplication.Companion.isShowcaseMode();
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(button.getContext());
builder.setTitle(moduleInfo.name).setCancelable(true).setIcon(R.drawable.ic_baseline_extension_24);
CharSequence desc = moduleInfo.description;
Markwon markwon = null;
LocalModuleInfo localModuleInfo = moduleHolder.moduleInfo;
if (localModuleInfo != null && !localModuleInfo.updateChangeLog.isEmpty()) {
markwon = MainApplication.getINSTANCE().getMarkwon();
val hasRoot = peekMagiskPath() != null && !isShowcaseMode
val builder = MaterialAlertDialogBuilder(button.context)
builder.setTitle(moduleInfo.name).setCancelable(true)
.setIcon(R.drawable.ic_baseline_extension_24)
var desc: CharSequence? = moduleInfo.description
var markwon: Markwon? = null
val localModuleInfo = moduleHolder.moduleInfo
if (localModuleInfo != null && localModuleInfo.updateChangeLog.isNotEmpty()) {
markwon = INSTANCE!!.getMarkwon()
// Re-render each time in cse of config changes
desc = markwon.toMarkdown(localModuleInfo.updateChangeLog);
desc = markwon!!.toMarkdown(localModuleInfo.updateChangeLog)
}
if (desc == null || desc.length() == 0) {
builder.setMessage(R.string.no_desc_found);
if (desc.isNullOrEmpty()) {
builder.setMessage(R.string.no_desc_found)
} else {
builder.setMessage(desc);
builder.setMessage(desc)
}
Timber.i("URL: %s", updateZipUrl)
builder.setNegativeButton(R.string.download_module) { _: DialogInterface?, _: Int ->
openCustomTab(
button.context,
updateZipUrl
)
}
Timber.i("URL: %s", updateZipUrl);
builder.setNegativeButton(R.string.download_module, (x, y) -> IntentHelper.openCustomTab(button.getContext(), updateZipUrl));
if (hasRoot) {
builder.setPositiveButton(moduleHolder.hasUpdate() ? R.string.update_module : R.string.install_module, (x, y) -> {
String updateZipChecksum = moduleHolder.getUpdateZipChecksum();
IntentHelper.openInstaller(button.getContext(), updateZipUrl, moduleInfo.name, moduleInfo.config, updateZipChecksum, moduleInfo.mmtReborn);
});
builder.setPositiveButton(if (moduleHolder.hasUpdate()) R.string.update_module else R.string.install_module) { _: DialogInterface?, _: Int ->
val updateZipChecksum = moduleHolder.updateZipChecksum
openInstaller(
button.context,
updateZipUrl,
moduleInfo.name,
moduleInfo.config,
updateZipChecksum,
moduleInfo.mmtReborn
)
}
}
ExternalHelper.INSTANCE.injectButton(builder, () -> Uri.parse(updateZipUrl), moduleHolder.getUpdateZipRepo());
int dim5dp = FoxDisplay.dpToPixel(5);
builder.setBackgroundInsetStart(dim5dp).setBackgroundInsetEnd(dim5dp);
AlertDialog alertDialog = builder.show();
for (int i = -3; i < 0; i++) {
Button alertButton = alertDialog.getButton(i);
if (alertButton != null && alertButton.getPaddingStart() > dim5dp) {
alertButton.setPadding(dim5dp, dim5dp, dim5dp, dim5dp);
ExternalHelper.INSTANCE.injectButton(
builder,
{ Uri.parse(updateZipUrl) },
moduleHolder.updateZipRepo
)
val dim5dp = FoxDisplay.dpToPixel(5f)
builder.setBackgroundInsetStart(dim5dp).setBackgroundInsetEnd(dim5dp)
val alertDialog = builder.show()
for (i in -3..-1) {
val alertButton = alertDialog.getButton(i)
if (alertButton != null && alertButton.paddingStart > dim5dp) {
alertButton.setPadding(dim5dp, dim5dp, dim5dp, dim5dp)
}
}
if (markwon != null) {
TextView messageView = Objects.requireNonNull(alertDialog.getWindow()).findViewById(android.R.id.message);
markwon.setParsedMarkdown(messageView, (Spanned) desc);
val messageView = alertDialog.window!!.findViewById<TextView>(android.R.id.message)
markwon.setParsedMarkdown(messageView, (desc as Spanned?)!!)
}
}
}, UNINSTALL() {
@Override
public void update(Chip button, ModuleHolder moduleHolder) {
int icon = moduleHolder.hasFlag(ModuleInfo.FLAG_MODULE_UNINSTALLING) ? R.drawable.ic_baseline_delete_outline_24 : (!moduleHolder.hasFlag(ModuleInfo.FLAG_MODULE_UPDATING) || moduleHolder.hasFlag(ModuleInfo.FLAGS_MODULE_ACTIVE)) ? R.drawable.ic_baseline_delete_24 : R.drawable.ic_baseline_delete_forever_24;
button.setChipIcon(button.getContext().getDrawable(icon));
button.setText(R.string.uninstall);
},
UNINSTALL {
override fun update(button: Chip, moduleHolder: ModuleHolder) {
val icon =
if (moduleHolder.hasFlag(ModuleInfo.FLAG_MODULE_UNINSTALLING)) R.drawable.ic_baseline_delete_outline_24 else if (!moduleHolder.hasFlag(
ModuleInfo.FLAG_MODULE_UPDATING
) || moduleHolder.hasFlag(ModuleInfo.FLAGS_MODULE_ACTIVE)
) R.drawable.ic_baseline_delete_24 else R.drawable.ic_baseline_delete_forever_24
button.chipIcon = button.context.getDrawable(icon)
button.setText(R.string.uninstall)
}
@Override
public void doAction(Chip button, ModuleHolder moduleHolder) {
if (!moduleHolder.hasFlag(ModuleInfo.FLAGS_MODULE_ACTIVE | ModuleInfo.FLAG_MODULE_UNINSTALLING) && moduleHolder.hasFlag(ModuleInfo.FLAG_MODULE_UPDATING)) {
doActionLong(button, moduleHolder);
return;
override fun doAction(button: Chip, moduleHolder: ModuleHolder) {
if (!moduleHolder.hasFlag(ModuleInfo.FLAGS_MODULE_ACTIVE or ModuleInfo.FLAG_MODULE_UNINSTALLING) && moduleHolder.hasFlag(
ModuleInfo.FLAG_MODULE_UPDATING
)
) {
doActionLong(button, moduleHolder)
return
}
String name;
if (moduleHolder.moduleInfo != null) {
name = moduleHolder.moduleInfo.name;
val name: String? = if (moduleHolder.moduleInfo != null) {
moduleHolder.moduleInfo!!.name
} else {
name = moduleHolder.repoModule.moduleInfo.name;
moduleHolder.repoModule?.moduleInfo?.name
}
TrackHelper.track().event("uninstall_module", name).with(MainApplication.getINSTANCE().getTracker());
Timber.i(Integer.toHexString(moduleHolder.moduleInfo.flags));
if (!Objects.requireNonNull(ModuleManager.getInstance()).setUninstallState(moduleHolder.moduleInfo, !moduleHolder.hasFlag(ModuleInfo.FLAG_MODULE_UNINSTALLING))) {
Timber.e("Failed to switch uninstalled state!");
TrackHelper.track().event("uninstall_module", name).with(INSTANCE!!.getTracker())
Timber.i(Integer.toHexString(moduleHolder.moduleInfo?.flags ?: 0))
if (!instance!!.setUninstallState(
moduleHolder.moduleInfo!!, !moduleHolder.hasFlag(
ModuleInfo.FLAG_MODULE_UNINSTALLING
)
)
) {
Timber.e("Failed to switch uninstalled state!")
}
update(button, moduleHolder);
update(button, moduleHolder)
}
@Override
public boolean doActionLong(Chip button, ModuleHolder moduleHolder) {
override fun doActionLong(button: Chip, moduleHolder: ModuleHolder): Boolean {
if (moduleHolder.moduleInfo == null) {
return false
}
// Actually a module having mount is the only issue when deleting module
if (moduleHolder.moduleInfo.hasFlag(ModuleInfo.FLAG_MODULE_HAS_ACTIVE_MOUNT))
return false; // We can't trust active flag on first boot
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(button.getContext());
builder.setTitle(R.string.master_delete);
builder.setPositiveButton(R.string.master_delete_yes, (dialog, which) -> {
String moduleId = moduleHolder.moduleInfo.id;
if (!Objects.requireNonNull(ModuleManager.getInstance()).masterClear(moduleHolder.moduleInfo)) {
Toast.makeText(button.getContext(), R.string.master_delete_fail, Toast.LENGTH_SHORT).show();
if (moduleHolder.moduleInfo!!.hasFlag(ModuleInfo.FLAG_MODULE_HAS_ACTIVE_MOUNT)) return false // We can't trust active flag on first boot
val builder = MaterialAlertDialogBuilder(button.context)
builder.setTitle(R.string.master_delete)
builder.setPositiveButton(R.string.master_delete_yes) { _: DialogInterface?, _: Int ->
val moduleId = moduleHolder.moduleInfo!!.id
if (!instance!!.masterClear(moduleHolder.moduleInfo!!)) {
Toast.makeText(button.context, R.string.master_delete_fail, Toast.LENGTH_SHORT)
.show()
} else {
moduleHolder.moduleInfo = null;
FoxActivity.getFoxActivity(button).refreshUI();
Timber.e("Cleared: %s", moduleId);
moduleHolder.moduleInfo = null
FoxActivity.getFoxActivity(button).refreshUI()
Timber.e("Cleared: %s", moduleId)
}
});
builder.setNegativeButton(R.string.master_delete_no, (v, i) -> {
});
builder.create();
builder.show();
return true;
}
builder.setNegativeButton(R.string.master_delete_no) { _: DialogInterface?, _: Int -> }
builder.create()
builder.show()
return true
}
}, CONFIG() {
@Override
public void update(Chip button, ModuleHolder moduleHolder) {
button.setChipIcon(button.getContext().getDrawable(R.drawable.ic_baseline_app_settings_alt_24));
button.setText(R.string.config);
},
CONFIG {
override fun update(button: Chip, moduleHolder: ModuleHolder) {
button.chipIcon =
button.context.getDrawable(R.drawable.ic_baseline_app_settings_alt_24)
button.setText(R.string.config)
}
@Override
public void doAction(Chip button, ModuleHolder moduleHolder) {
String config = moduleHolder.getMainModuleConfig();
if (config == null) return;
String name;
if (moduleHolder.moduleInfo != null) {
name = moduleHolder.moduleInfo.name;
override fun doAction(button: Chip, moduleHolder: ModuleHolder) {
if (moduleHolder.moduleInfo == null) {
return
}
val config = moduleHolder.mainModuleConfig ?: return
val name: String? = if (moduleHolder.moduleInfo != null) {
moduleHolder.moduleInfo!!.name
} else {
name = moduleHolder.repoModule.moduleInfo.name;
moduleHolder.repoModule?.moduleInfo?.name
}
TrackHelper.track().event("config_module", name).with(MainApplication.getINSTANCE().getTracker());
if (AndroidacyUtil.Companion.isAndroidacyLink(config)) {
IntentHelper.openUrlAndroidacy(button.getContext(), config, true);
TrackHelper.track().event("config_module", name).with(INSTANCE!!.getTracker())
if (isAndroidacyLink(config)) {
openUrlAndroidacy(button.context, config, true)
} else {
IntentHelper.openConfig(button.getContext(), config);
openConfig(button.context, config)
}
}
}, SUPPORT() {
@Override
public void update(Chip button, ModuleHolder moduleHolder) {
ModuleInfo moduleInfo = moduleHolder.getMainModuleInfo();
button.setChipIcon(button.getContext().getDrawable(supportIconForUrl(moduleInfo.support)));
button.setText(R.string.support);
},
SUPPORT {
override fun update(button: Chip, moduleHolder: ModuleHolder) {
val moduleInfo = moduleHolder.mainModuleInfo
button.chipIcon = button.context.getDrawable(
supportIconForUrl(moduleInfo.support)
)
button.setText(R.string.support)
}
@Override
public void doAction(Chip button, ModuleHolder moduleHolder) {
String name;
if (moduleHolder.moduleInfo != null) {
name = moduleHolder.moduleInfo.name;
override fun doAction(button: Chip, moduleHolder: ModuleHolder) {
val name: String? = if (moduleHolder.moduleInfo != null) {
moduleHolder.moduleInfo!!.name
} else {
name = moduleHolder.repoModule.moduleInfo.name;
moduleHolder.repoModule?.moduleInfo?.name
}
TrackHelper.track().event("support_module", name).with(MainApplication.getINSTANCE().getTracker());
IntentHelper.Companion.openUrl(button.getContext(), Objects.requireNonNull(moduleHolder.getMainModuleInfo().support));
TrackHelper.track().event("support_module", name).with(INSTANCE!!.getTracker())
openUrl(button.context, Objects.requireNonNull(moduleHolder.mainModuleInfo.support))
}
}, DONATE() {
@Override
public void update(Chip button, ModuleHolder moduleHolder) {
ModuleInfo moduleInfo = moduleHolder.getMainModuleInfo();
button.setChipIcon(button.getContext().getDrawable(donateIconForUrl(moduleInfo.donate)));
button.setText(R.string.donate);
},
DONATE {
override fun update(button: Chip, moduleHolder: ModuleHolder) {
val moduleInfo = moduleHolder.mainModuleInfo
button.chipIcon = button.context.getDrawable(
donateIconForUrl(moduleInfo.donate)
)
button.setText(R.string.donate)
}
@Override
public void doAction(Chip button, ModuleHolder moduleHolder) {
String name;
if (moduleHolder.moduleInfo != null) {
name = moduleHolder.moduleInfo.name;
override fun doAction(button: Chip, moduleHolder: ModuleHolder) {
val name: String? = if (moduleHolder.moduleInfo != null) {
moduleHolder.moduleInfo!!.name
} else {
name = moduleHolder.repoModule.moduleInfo.name;
moduleHolder.repoModule?.moduleInfo?.name
}
TrackHelper.track().event("donate_module", name).with(MainApplication.getINSTANCE().getTracker());
IntentHelper.Companion.openUrl(button.getContext(), moduleHolder.getMainModuleInfo().donate);
TrackHelper.track().event("donate_module", name).with(INSTANCE!!.getTracker())
openUrl(button.context, moduleHolder.mainModuleInfo.donate)
}
}, WARNING() {
@Override
public void update(Chip button, ModuleHolder moduleHolder) {
button.setChipIcon(button.getContext().getDrawable(R.drawable.ic_baseline_warning_24));
button.setText(R.string.warning);
},
WARNING {
override fun update(button: Chip, moduleHolder: ModuleHolder) {
button.chipIcon = button.context.getDrawable(R.drawable.ic_baseline_warning_24)
button.setText(R.string.warning)
}
@Override
public void doAction(Chip button, ModuleHolder moduleHolder) {
String name;
if (moduleHolder.moduleInfo != null) {
name = moduleHolder.moduleInfo.name;
override fun doAction(button: Chip, moduleHolder: ModuleHolder) {
val name: String? = if (moduleHolder.moduleInfo != null) {
moduleHolder.moduleInfo!!.name
} else {
name = moduleHolder.repoModule.moduleInfo.name;
moduleHolder.repoModule?.moduleInfo?.name
}
TrackHelper.track().event("warning_module", name).with(MainApplication.getINSTANCE().getTracker());
new MaterialAlertDialogBuilder(button.getContext()).setTitle(R.string.warning).setMessage(R.string.warning_message).setPositiveButton(R.string.understand, (v, i) -> {
}).create().show();
TrackHelper.track().event("warning_module", name).with(INSTANCE!!.getTracker())
MaterialAlertDialogBuilder(button.context).setTitle(R.string.warning)
.setMessage(R.string.warning_message).setPositiveButton(
R.string.understand
) { _: DialogInterface?, _: Int -> }
.create().show()
}
}, SAFE() {
},
SAFE {
// SAFE is for modules that the api says are clean. only supported by androidacy currently
@Override
public void update(Chip button, ModuleHolder moduleHolder) {
button.setChipIcon(button.getContext().getDrawable(R.drawable.baseline_verified_user_24));
button.setText(R.string.safe);
override fun update(button: Chip, moduleHolder: ModuleHolder) {
button.chipIcon =
button.context.getDrawable(R.drawable.baseline_verified_user_24)
button.setText(R.string.safe)
}
@Override
public void doAction(Chip button, ModuleHolder moduleHolder) {
String name;
if (moduleHolder.moduleInfo != null) {
name = moduleHolder.moduleInfo.name;
override fun doAction(button: Chip, moduleHolder: ModuleHolder) {
val name: String? = if (moduleHolder.moduleInfo != null) {
moduleHolder.moduleInfo!!.name
} else {
name = moduleHolder.repoModule.moduleInfo.name;
moduleHolder.repoModule?.moduleInfo?.name
}
TrackHelper.track().event("safe_module", name).with(MainApplication.getINSTANCE().getTracker());
new MaterialAlertDialogBuilder(button.getContext()).setTitle(R.string.safe_module).setMessage(R.string.safe_message).setPositiveButton(R.string.understand, (v, i) -> {
}).create().show();
TrackHelper.track().event("safe_module", name).with(INSTANCE!!.getTracker())
MaterialAlertDialogBuilder(button.context).setTitle(R.string.safe_module)
.setMessage(R.string.safe_message).setPositiveButton(
R.string.understand
) { _: DialogInterface?, _: Int -> }
.create().show()
}
};
@DrawableRes
private final int iconId;
private val iconId: Int
ActionButtonType() {
this.iconId = 0;
constructor() {
iconId = 0
}
@SuppressWarnings("unused")
ActionButtonType(int iconId) {
this.iconId = iconId;
@Suppress("unused")
constructor(iconId: Int) {
this.iconId = iconId
}
@DrawableRes
public static int supportIconForUrl(String url) {
int icon = R.drawable.ic_baseline_support_24;
if (url == null) {
return icon;
} else if (url.startsWith("https://t.me/")) {
icon = R.drawable.ic_baseline_telegram_24;
} else if (url.startsWith("https://discord.gg/") || url.startsWith("https://discord.com/invite/")) {
icon = R.drawable.ic_baseline_discord_24;
} else if (url.startsWith("https://github.com/")) {
icon = R.drawable.ic_github;
} else if (url.startsWith("https://gitlab.com/")) {
icon = R.drawable.ic_gitlab;
} else if (url.startsWith("https://forum.xda-developers.com/")) {
icon = R.drawable.ic_xda;
}
return icon;
open fun update(button: Chip, moduleHolder: ModuleHolder) {
button.chipIcon = button.context.getDrawable(iconId)
}
@DrawableRes
public static int donateIconForUrl(String url) {
int icon = R.drawable.ic_baseline_monetization_on_24;
if (url == null) {
return icon;
} else if (url.startsWith("https://www.paypal.me/") || url.startsWith("https://www.paypal.com/paypalme/") || url.startsWith("https://www.paypal.com/donate/")) {
icon = R.drawable.ic_baseline_paypal_24;
} else if (url.startsWith("https://patreon.com/") || url.startsWith("https://www.patreon.com/")) {
icon = R.drawable.ic_patreon;
}
return icon;
}
public void update(Chip button, ModuleHolder moduleHolder) {
button.setChipIcon(button.getContext().getDrawable(this.iconId));
abstract fun doAction(button: Chip, moduleHolder: ModuleHolder)
open fun doActionLong(button: Chip, moduleHolder: ModuleHolder): Boolean {
return false
}
public abstract void doAction(Chip button, ModuleHolder moduleHolder);
companion object {
@JvmStatic
@DrawableRes
fun supportIconForUrl(url: String?): Int {
var icon = R.drawable.ic_baseline_support_24
if (url == null) {
return icon
} else if (url.startsWith("https://t.me/")) {
icon = R.drawable.ic_baseline_telegram_24
} else if (url.startsWith("https://discord.gg/") || url.startsWith("https://discord.com/invite/")) {
icon = R.drawable.ic_baseline_discord_24
} else if (url.startsWith("https://github.com/")) {
icon = R.drawable.ic_github
} else if (url.startsWith("https://gitlab.com/")) {
icon = R.drawable.ic_gitlab
} else if (url.startsWith("https://forum.xda-developers.com/")) {
icon = R.drawable.ic_xda
}
return icon
}
public boolean doActionLong(Chip button, ModuleHolder moduleHolder) {
return false;
@JvmStatic
@DrawableRes
fun donateIconForUrl(url: String?): Int {
var icon = R.drawable.ic_baseline_monetization_on_24
if (url == null) {
return icon
} else if (url.startsWith("https://www.paypal.me/") || url.startsWith("https://www.paypal.com/paypalme/") || url.startsWith(
"https://www.paypal.com/donate/"
)
) {
icon = R.drawable.ic_baseline_paypal_24
} else if (url.startsWith("https://patreon.com/") || url.startsWith("https://www.patreon.com/")) {
icon = R.drawable.ic_patreon
}
return icon
}
}
}
}

@ -1,374 +1,420 @@
/*
* Copyright (c) 2023 to present Androidacy and contributors. Names, logos, icons, and the Androidacy name are all trademarks of Androidacy and may not be used without license. See LICENSE for more information.
*/
package com.fox2code.mmm.module;
import android.content.Context;
import android.content.pm.PackageManager;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.StringRes;
import com.fox2code.mmm.MainApplication;
import com.fox2code.mmm.NotificationType;
import com.fox2code.mmm.R;
import com.fox2code.mmm.XHooks;
import com.fox2code.mmm.manager.LocalModuleInfo;
import com.fox2code.mmm.manager.ModuleInfo;
import com.fox2code.mmm.repo.RepoModule;
import com.fox2code.mmm.utils.IntentHelper;
import com.fox2code.mmm.utils.io.PropUtils;
import com.fox2code.mmm.utils.io.net.Http;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.Set;
import timber.log.Timber;
public final class ModuleHolder implements Comparable<ModuleHolder> {
public final String moduleId;
public final NotificationType notificationType;
public final Type separator;
public int footerPx;
public View.OnClickListener onClickListener;
public LocalModuleInfo moduleInfo;
public RepoModule repoModule;
public int filterLevel;
public ModuleHolder(String moduleId) {
this.moduleId = Objects.requireNonNull(moduleId);
this.notificationType = null;
this.separator = null;
this.footerPx = -1;
}
public ModuleHolder(NotificationType notificationType) {
this.moduleId = "";
this.notificationType = Objects.requireNonNull(notificationType);
this.separator = null;
this.footerPx = -1;
}
public ModuleHolder(Type separator) {
this.moduleId = "";
this.notificationType = null;
this.separator = separator;
this.footerPx = -1;
package com.fox2code.mmm.module
import android.content.Context
import android.content.pm.PackageManager
import android.view.View
import androidx.annotation.StringRes
import com.fox2code.mmm.MainApplication.Companion.INSTANCE
import com.fox2code.mmm.MainApplication.Companion.formatTime
import com.fox2code.mmm.MainApplication.Companion.getSharedPreferences
import com.fox2code.mmm.MainApplication.Companion.isDisableLowQualityModuleFilter
import com.fox2code.mmm.NotificationType
import com.fox2code.mmm.R
import com.fox2code.mmm.XHooks.Companion.checkConfigTargetExists
import com.fox2code.mmm.manager.LocalModuleInfo
import com.fox2code.mmm.manager.ModuleInfo
import com.fox2code.mmm.repo.RepoModule
import com.fox2code.mmm.utils.IntentHelper.Companion.getPackageOfConfig
import com.fox2code.mmm.utils.io.PropUtils.Companion.isLowQualityModule
import com.fox2code.mmm.utils.io.net.Http.Companion.hasWebView
import timber.log.Timber
import java.util.Objects
@Suppress("unused", "KotlinConstantConditions")
class ModuleHolder : Comparable<ModuleHolder?> {
val moduleId: String
val notificationType: NotificationType?
val separator: Type?
var footerPx: Int
var onClickListener: View.OnClickListener? = null
var moduleInfo: LocalModuleInfo? = null
var repoModule: RepoModule? = null
var filterLevel = 0
constructor(moduleId: String) {
this.moduleId = Objects.requireNonNull(moduleId)
notificationType = null
separator = null
footerPx = -1
}
@SuppressWarnings("unused")
public ModuleHolder(int footerPx, boolean header) {
this.moduleId = "";
this.notificationType = null;
this.separator = null;
this.footerPx = footerPx;
this.filterLevel = header ? 1 : 0;
constructor(notificationType: NotificationType) {
moduleId = ""
this.notificationType = Objects.requireNonNull(notificationType)
separator = null
footerPx = -1
}
public boolean isModuleHolder() {
return this.notificationType == null && this.separator == null && this.footerPx == -1;
constructor(separator: Type?) {
moduleId = ""
notificationType = null
this.separator = separator
footerPx = -1
}
public ModuleInfo getMainModuleInfo() {
return this.repoModule != null && (this.moduleInfo == null || this.moduleInfo.versionCode < this.repoModule.moduleInfo.versionCode) ? this.repoModule.moduleInfo : this.moduleInfo;
@Suppress("unused")
constructor(footerPx: Int, header: Boolean) {
moduleId = ""
notificationType = null
separator = null
this.footerPx = footerPx
filterLevel = if (header) 1 else 0
}
public String getUpdateZipUrl() {
return this.moduleInfo == null || (this.repoModule != null && this.moduleInfo.updateVersionCode < this.repoModule.moduleInfo.versionCode) ? this.repoModule.zipUrl : this.moduleInfo.updateZipUrl;
}
public String getUpdateZipRepo() {
return this.moduleInfo == null || (this.repoModule != null && this.moduleInfo.updateVersionCode < this.repoModule.moduleInfo.versionCode) ? this.repoModule.repoData.preferenceId : "update_json";
}
public String getUpdateZipChecksum() {
return this.moduleInfo == null || (this.repoModule != null && this.moduleInfo.updateVersionCode < this.repoModule.moduleInfo.versionCode) ? this.repoModule.checksum : this.moduleInfo.updateChecksum;
}
public String getMainModuleName() {
ModuleInfo moduleInfo = this.getMainModuleInfo();
if (moduleInfo == null || moduleInfo.name == null)
throw new Error("Error for " + this.getType().name() + " id " + this.moduleId);
return moduleInfo.name;
}
public String getMainModuleNameLowercase() {
return this.getMainModuleName().toLowerCase(Locale.ROOT);
}
public String getMainModuleConfig() {
if (this.moduleInfo == null) return null;
String config = this.moduleInfo.config;
if (config == null && this.repoModule != null) {
config = this.repoModule.moduleInfo.config;
val isModuleHolder: Boolean
get() = notificationType == null && separator == null && footerPx == -1
val mainModuleInfo: ModuleInfo
get() = if (repoModule != null && (moduleInfo == null || moduleInfo!!.versionCode < repoModule!!.moduleInfo.versionCode)) repoModule!!.moduleInfo else moduleInfo!!
val updateZipUrl: String?
get() = if (moduleInfo == null || repoModule != null && moduleInfo!!.updateVersionCode < repoModule!!.moduleInfo.versionCode) repoModule!!.zipUrl else moduleInfo!!.updateZipUrl
val updateZipRepo: String?
get() = if (moduleInfo == null || repoModule != null && moduleInfo!!.updateVersionCode < repoModule!!.moduleInfo.versionCode) repoModule!!.repoData.preferenceId else "update_json"
val updateZipChecksum: String?
get() = if (moduleInfo == null || repoModule != null && moduleInfo!!.updateVersionCode < repoModule!!.moduleInfo.versionCode) repoModule!!.checksum else moduleInfo!!.updateChecksum
val mainModuleName: String?
get() {
val moduleInfo = mainModuleInfo
if (moduleInfo.name == null) throw Error("Error for " + type.name + " id " + moduleId)
return moduleInfo.name
}
return config;
}
public String getUpdateTimeText() {
if (this.repoModule == null) return "";
long timeStamp = this.repoModule.lastUpdated;
return timeStamp <= 0 ? "" : MainApplication.formatTime(timeStamp);
}
public String getRepoName() {
if (this.repoModule == null) return "";
return this.repoModule.repoName;
}
val mainModuleNameLowercase: String
get() = mainModuleName!!.lowercase()
val mainModuleConfig: String?
get() {
if (moduleInfo == null) return null
var config = moduleInfo!!.config
if (config == null && repoModule != null) {
config = repoModule!!.moduleInfo.config
}
return config
}
val updateTimeText: String
get() {
if (repoModule == null) return ""
val timeStamp = repoModule!!.lastUpdated
return if (timeStamp <= 0) "" else formatTime(timeStamp)
}
val repoName: String?
get() = if (repoModule == null) "" else repoModule!!.repoName
public boolean hasFlag(int flag) {
return this.moduleInfo != null && this.moduleInfo.hasFlag(flag);
fun hasFlag(flag: Int): Boolean {
return moduleInfo != null && moduleInfo!!.hasFlag(flag)
}
public Type getType() {
if (this.footerPx != -1) {
Timber.i("Module %s is footer", this.moduleId);
return Type.FOOTER;
} else if (this.separator != null) {
Timber.i("Module %s is separator", this.moduleId);
return Type.SEPARATOR;
} else if (this.notificationType != null) {
Timber.i("Module %s is notification", this.moduleId);
return Type.NOTIFICATION;
} else if (this.moduleInfo == null) {
return Type.INSTALLABLE;
} else if (this.moduleInfo.versionCode < this.moduleInfo.updateVersionCode || (this.repoModule != null && this.moduleInfo.versionCode < this.repoModule.moduleInfo.versionCode)) {
boolean ignoreUpdate = false;
val type: Type
get() = if (footerPx != -1) {
Timber.i("Module %s is footer", moduleId)
Type.FOOTER
} else if (separator != null) {
Timber.i("Module %s is separator", moduleId)
Type.SEPARATOR
} else if (notificationType != null) {
Timber.i("Module %s is notification", moduleId)
Type.NOTIFICATION
} else if (moduleInfo == null) {
Type.INSTALLABLE
} else if (moduleInfo!!.versionCode < moduleInfo!!.updateVersionCode || repoModule != null && moduleInfo!!.versionCode < repoModule!!.moduleInfo.versionCode) {
var ignoreUpdate = false
try {
if (Objects.requireNonNull(Objects.requireNonNull(MainApplication.getSharedPreferences("mmm")).getStringSet("pref_background_update_check_excludes", new HashSet<>())).contains(moduleInfo.id))
ignoreUpdate = true;
} catch (Exception ignored) {
if (getSharedPreferences("mmm")?.getStringSet("pref_background_update_check_excludes", HashSet())!!
.contains(
moduleInfo!!.id
)
) ignoreUpdate = true
} catch (ignored: Exception) {
}
// now, we just had to make it more fucking complicated, didn't we?
// we now have pref_background_update_check_excludes_version, which is a id:version stringset of versions the user may want to "skip"
// oh, and because i hate myself, i made ^ at the beginning match that version and newer, and $ at the end match that version and older
Set<String> stringSetT = Objects.requireNonNull(MainApplication.getSharedPreferences("mmm")).getStringSet("pref_background_update_check_excludes_version", new HashSet<>());
String version = "";
Timber.d(stringSetT.toString());
val stringSetT = getSharedPreferences("mmm")?.getStringSet("pref_background_update_check_excludes_version", HashSet())
var version = ""
Timber.d(stringSetT.toString())
// unfortunately, stringset.contains() doesn't work for partial matches
// so we have to iterate through the set
for (String s : stringSetT) {
if (s.startsWith(this.moduleInfo.id)) {
version = s;
Timber.d("igV: %s", version);
break;
for (s in stringSetT!!) {
if (s.startsWith(moduleInfo!!.id)) {
version = s
Timber.d("igV: %s", version)
break
}
}
String remoteVersionCode = String.valueOf(moduleInfo.updateVersionCode);
var remoteVersionCode = moduleInfo!!.updateVersionCode.toString()
if (repoModule != null) {
remoteVersionCode = String.valueOf(repoModule.moduleInfo.versionCode);
remoteVersionCode = repoModule!!.moduleInfo.versionCode.toString()
}
if (!version.isEmpty()) {
if (version.isNotEmpty()) {
// now, coerce everything into an int
int remoteVersionCodeInt = Integer.parseInt(remoteVersionCode);
int wantsVersion = Integer.parseInt(version.split(":")[1].replaceAll("[^0-9]", ""));
val remoteVersionCodeInt = remoteVersionCode.toInt()
val wantsVersion = version.split(":".toRegex()).dropLastWhile { it.isEmpty() }
.toTypedArray()[1].replace("[^0-9]".toRegex(), "").toInt()
// now find out if user wants up to and including this version, or this version and newer
Timber.d("igV start with");
version = version.split(":")[1];
Timber.d("igV start with")
version =
version.split(":".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()[1]
// this version and newer
if (version.startsWith("^")) {
Timber.d("igV: newer");
Timber.d("igV: newer")
// the wantsversion and newer
if (remoteVersionCodeInt >= wantsVersion) {
Timber.d("igV: skipping");
Timber.d("igV: skipping")
// if it is, we skip it
ignoreUpdate = true;
ignoreUpdate = true
}
} else if (version.endsWith("$")) {
Timber.d("igV: older");
Timber.d("igV: older")
// this wantsversion and older
if (remoteVersionCodeInt <= wantsVersion) {
Timber.d("igV: skipping");
Timber.d("igV: skipping")
// if it is, we skip it
ignoreUpdate = true;
ignoreUpdate = true
}
} else if (wantsVersion == remoteVersionCodeInt) {
Timber.d("igV: equal");
Timber.d("igV: equal")
// if it is, we skip it
ignoreUpdate = true;
ignoreUpdate = true
}
}
if (ignoreUpdate) {
Timber.d("Module %s has update, but is ignored", this.moduleId);
return Type.INSTALLABLE;
Timber.d("Module %s has update, but is ignored", moduleId)
Type.INSTALLABLE
} else {
Objects.requireNonNull(MainApplication.getINSTANCE()).modulesHaveUpdates = true;
if (!MainApplication.getINSTANCE().updateModules.contains(this.moduleId)) {
MainApplication.getINSTANCE().updateModules.add(this.moduleId);
MainApplication.getINSTANCE().updateModuleCount++;
INSTANCE!!.modulesHaveUpdates = true
if (!INSTANCE!!.updateModules.contains(moduleId)) {
INSTANCE!!.updateModules += moduleId
INSTANCE!!.updateModuleCount++
}
Timber.d("modulesHaveUpdates = %s, updateModuleCount = %s", MainApplication.getINSTANCE().modulesHaveUpdates, MainApplication.getINSTANCE().updateModuleCount);
Timber.d("Module %s has update", this.moduleId);
return Type.UPDATABLE;
Timber.d(
"modulesHaveUpdates = %s, updateModuleCount = %s",
INSTANCE!!.modulesHaveUpdates,
INSTANCE!!.updateModuleCount
)
Timber.d("Module %s has update", moduleId)
Type.UPDATABLE
}
} else {
return Type.INSTALLED;
Type.INSTALLED
}
}
public Type getCompareType(Type type) {
if (this.separator != null) {
return this.separator;
} else if (this.notificationType != null && this.notificationType.special) {
return Type.SPECIAL_NOTIFICATIONS;
} else {
return type;
}
fun getCompareType(type: Type?): Type? {
return separator
?: if (notificationType != null && notificationType.special) {
Type.SPECIAL_NOTIFICATIONS
} else {
type
}
}
public boolean shouldRemove() {
if (this.repoModule != null && this.moduleInfo != null && !hasUpdate()) {
return true;
fun shouldRemove(): Boolean {
if (repoModule != null && moduleInfo != null && !hasUpdate()) {
return true
}
return this.notificationType != null ? this.notificationType.shouldRemove() : this.footerPx == -1 && this.moduleInfo == null && (this.repoModule == null || !this.repoModule.repoData.isEnabled() || (PropUtils.isLowQualityModule(this.repoModule.moduleInfo) && !MainApplication.Companion.isDisableLowQualityModuleFilter()));
return notificationType?.shouldRemove()
?: (footerPx == -1 && moduleInfo == null && (repoModule == null || !repoModule!!.repoData.isEnabled || isLowQualityModule(
repoModule!!.moduleInfo
) && !isDisableLowQualityModuleFilter))
}
public void getButtons(Context context, List<ActionButtonType> buttonTypeList, boolean showcaseMode) {
if (!this.isModuleHolder()) return;
LocalModuleInfo localModuleInfo = this.moduleInfo;
fun getButtons(
context: Context?,
buttonTypeList: MutableList<ActionButtonType?>,
showcaseMode: Boolean
) {
if (!isModuleHolder) return
val localModuleInfo = moduleInfo
// Add warning button if module id begins with a dot - this is a hidden module which could indicate malware
if (this.moduleId.startsWith(".") || !this.moduleId.matches("^[a-zA-Z][a-zA-Z0-9._-]+$")) {
buttonTypeList.add(ActionButtonType.WARNING);
if (moduleId.startsWith(".") || !moduleId.matches("^[a-zA-Z][a-zA-Z0-9._-]+$".toRegex())) {
buttonTypeList.add(ActionButtonType.WARNING)
}
if (localModuleInfo != null && !showcaseMode) {
buttonTypeList.add(ActionButtonType.UNINSTALL);
buttonTypeList.add(ActionButtonType.UNINSTALL)
}
if (this.repoModule != null && this.repoModule.notesUrl != null) {
buttonTypeList.add(ActionButtonType.INFO);
if (repoModule != null && repoModule!!.notesUrl != null) {
buttonTypeList.add(ActionButtonType.INFO)
}
if ((this.repoModule != null || (localModuleInfo != null && localModuleInfo.updateZipUrl != null))) {
buttonTypeList.add(ActionButtonType.UPDATE_INSTALL);
if (repoModule != null || localModuleInfo?.updateZipUrl != null) {
buttonTypeList.add(ActionButtonType.UPDATE_INSTALL)
}
String config = this.getMainModuleConfig();
val config = mainModuleConfig
if (config != null) {
if (config.startsWith("https://www.androidacy.com/") && Http.hasWebView()) {
buttonTypeList.add(ActionButtonType.CONFIG);
if (config.startsWith("https://www.androidacy.com/") && hasWebView()) {
buttonTypeList.add(ActionButtonType.CONFIG)
} else {
String pkg = IntentHelper.getPackageOfConfig(config);
val pkg = getPackageOfConfig(config)
try {
XHooks.checkConfigTargetExists(context, pkg, config);
buttonTypeList.add(ActionButtonType.CONFIG);
} catch (PackageManager.NameNotFoundException e) {
Timber.w("Config package \"" + pkg + "\" missing for module \"" + this.moduleId + "\"");
checkConfigTargetExists(context!!, pkg, config)
buttonTypeList.add(ActionButtonType.CONFIG)
} catch (e: PackageManager.NameNotFoundException) {
Timber.w("Config package \"$pkg\" missing for module \"$moduleId\"")
}
}
}
ModuleInfo moduleInfo = this.getMainModuleInfo();
var moduleInfo: ModuleInfo? = mainModuleInfo
if (moduleInfo == null) { // Avoid concurrency NPE
if (localModuleInfo == null) return;
moduleInfo = localModuleInfo;
if (localModuleInfo == null) return
moduleInfo = localModuleInfo
}
if (moduleInfo.support != null) {
buttonTypeList.add(ActionButtonType.SUPPORT);
buttonTypeList.add(ActionButtonType.SUPPORT)
}
if (moduleInfo.donate != null) {
buttonTypeList.add(ActionButtonType.DONATE);
buttonTypeList.add(ActionButtonType.DONATE)
}
if (moduleInfo.safe) {
buttonTypeList.add(ActionButtonType.SAFE);
buttonTypeList.add(ActionButtonType.SAFE)
} else {
Timber.d("Module %s is not safe", this.moduleId);
Timber.d("Module %s is not safe", moduleId)
}
}
public boolean hasUpdate() {
return this.moduleInfo != null && this.repoModule != null && this.moduleInfo.versionCode < this.repoModule.moduleInfo.versionCode;
fun hasUpdate(): Boolean {
return moduleInfo != null && repoModule != null && moduleInfo!!.versionCode < repoModule!!.moduleInfo.versionCode
}
@Override
public int compareTo(ModuleHolder o) {
override operator fun compareTo(other: ModuleHolder?): Int {
// Compare depend on type, also allow type spoofing
Type selfTypeReal = this.getType();
Type otherTypeReal = o.getType();
Type selfType = this.getCompareType(selfTypeReal);
Type otherType = o.getCompareType(otherTypeReal);
int compare = selfType.compareTo(otherType);
return compare != 0 ? compare : selfTypeReal == otherTypeReal ? selfTypeReal.compare(this, o) : selfTypeReal.compareTo(otherTypeReal);
val selfTypeReal = type
val otherTypeReal = other!!.type
val selfType = getCompareType(selfTypeReal)
val otherType = other.getCompareType(otherTypeReal)
val compare = selfType!!.compareTo(otherType!!)
return if (compare != 0) compare else if (selfTypeReal === otherTypeReal) selfTypeReal.compare(
this,
other
) else selfTypeReal.compareTo(otherTypeReal)
}
@NonNull
@Override
public String toString() {
return "ModuleHolder{" + "moduleId='" + moduleId + '\'' + ", notificationType=" + notificationType + ", separator=" + separator + ", footerPx=" + footerPx + '}';
override fun toString(): String {
return "ModuleHolder{moduleId='$moduleId', notificationType=$notificationType, separator=$separator, footerPx=$footerPx}"
}
public enum Type implements Comparator<ModuleHolder> {
HEADER(R.string.loading, false, false), SEPARATOR(R.string.loading, false, false) {
@Override
@SuppressWarnings("ConstantConditions")
public int compare(ModuleHolder o1, ModuleHolder o2) {
return o1.separator.compareTo(o2.separator);
enum class Type(
@field:StringRes @param:StringRes val title: Int,
val hasBackground: Boolean,
val moduleHolder: Boolean
) : Comparator<ModuleHolder?> {
HEADER(R.string.loading, false, false) {
override fun compare(o1: ModuleHolder?, o2: ModuleHolder?): Int {
if (o1 != null && o2 != null) {
return o1.separator!!.compareTo(o2.separator!!)
} else if (o1 != null) {
return -1
} else if (o2 != null) {
return 1
}
return 0
}
}, NOTIFICATION(R.string.loading, true, false) {
@Override
@SuppressWarnings("ConstantConditions")
public int compare(ModuleHolder o1, ModuleHolder o2) {
return o1.notificationType.compareTo(o2.notificationType);
}, SEPARATOR(R.string.loading, false, false) {
override fun compare(o1: ModuleHolder?, o2: ModuleHolder?): Int {
if (o1 != null && o2 != null) {
return o1.separator!!.compareTo(o2.separator!!)
} else if (o1 != null) {
return -1
} else if (o2 != null) {
return 1
}
return 0
}
}, UPDATABLE(R.string.updatable, true, true) {
@Override
public int compare(ModuleHolder o1, ModuleHolder o2) {
int cmp = Integer.compare(o1.filterLevel, o2.filterLevel);
if (cmp != 0) return cmp;
long lastUpdated1 = o1.repoModule == null ? 0L : o1.repoModule.lastUpdated;
long lastUpdated2 = o2.repoModule == null ? 0L : o2.repoModule.lastUpdated;
cmp = Long.compare(lastUpdated2, lastUpdated1);
if (cmp != 0) return cmp;
return o1.getMainModuleName().compareTo(o2.getMainModuleName());
},
NOTIFICATION(R.string.loading, true, false) {
override fun compare(o1: ModuleHolder?, o2: ModuleHolder?): Int {
if (o1 != null && o2 != null) {
return o1.notificationType!!.compareTo(o2.notificationType!!)
} else if (o1 != null) {
return -1
} else if (o2 != null) {
return 1
}
return 0
}
}, INSTALLED(R.string.installed, true, true) {
},
UPDATABLE(R.string.updatable, true, true) {
override fun compare(o1: ModuleHolder?, o2: ModuleHolder?): Int {
if (o1 != null && o2 != null) {
var cmp = o1.filterLevel.compareTo(o2.filterLevel)
if (cmp != 0) return cmp
val lastUpdated1 =
if (o1.repoModule == null) 0L else o1.repoModule!!.lastUpdated
val lastUpdated2 =
if (o2.repoModule == null) 0L else o2.repoModule!!.lastUpdated
cmp = lastUpdated2.compareTo(lastUpdated1)
return if (cmp != 0) cmp else o1.mainModuleName!!.compareTo(o2.mainModuleName!!)
} else if (o1 != null) {
return -1
} else if (o2 != null) {
return 1
}
return 0
}
},
INSTALLED(R.string.installed, true, true) {
// get stacktrace for debugging
@Override
public int compare(ModuleHolder o1, ModuleHolder o2) {
int cmp = Integer.compare(o1.filterLevel, o2.filterLevel);
if (cmp != 0) return cmp;
return o1.getMainModuleNameLowercase().compareTo(o2.getMainModuleNameLowercase());
override fun compare(o1: ModuleHolder?, o2: ModuleHolder?): Int {
if (o1 != null && o2 != null) {
val cmp = o1.filterLevel.compareTo(o2.filterLevel)
return if (cmp != 0) cmp else o1.mainModuleNameLowercase.compareTo(o2.mainModuleNameLowercase)
} else if (o1 != null) {
return -1
} else if (o2 != null) {
return 1
}
return 0
}
}, SPECIAL_NOTIFICATIONS(R.string.loading, true, false), INSTALLABLE(R.string.online_repo, true, true) {
@Override
public int compare(ModuleHolder o1, ModuleHolder o2) {
int cmp = Integer.compare(o1.filterLevel, o2.filterLevel);
if (cmp != 0) return cmp;
long lastUpdated1 = o1.repoModule == null ? 0L : o1.repoModule.lastUpdated;
long lastUpdated2 = o2.repoModule == null ? 0L : o2.repoModule.lastUpdated;
cmp = Long.compare(lastUpdated2, lastUpdated1);
if (cmp != 0) return cmp;
return o1.getMainModuleName().compareTo(o2.getMainModuleName());
},
SPECIAL_NOTIFICATIONS(R.string.loading, true, false) {
override fun compare(o1: ModuleHolder?, o2: ModuleHolder?): Int {
if (o1 != null && o2 != null) {
var cmp = o1.filterLevel.compareTo(o2.filterLevel)
if (cmp != 0) return cmp
val lastUpdated1 =
if (o1.repoModule == null) 0L else o1.repoModule!!.lastUpdated
val lastUpdated2 =
if (o2.repoModule == null) 0L else o2.repoModule!!.lastUpdated
cmp = lastUpdated2.compareTo(lastUpdated1)
return if (cmp != 0) cmp else o1.mainModuleName!!.compareTo(o2.mainModuleName!!)
} else if (o1 != null) {
return -1
} else if (o2 != null) {
return 1
}
return 0
}
}, FOOTER(R.string.loading, false, false);
@StringRes
public final int title;
public final boolean hasBackground;
public final boolean moduleHolder;
Type(@StringRes int title, boolean hasBackground, boolean moduleHolder) {
this.title = title;
this.hasBackground = hasBackground;
this.moduleHolder = moduleHolder;
}
// Note: This method should only be called if both element have the same type
@Override
public int compare(ModuleHolder o1, ModuleHolder o2) {
if (o1 == o2) {
return 0;
} else if (o1 == null) {
return -1;
} else if (o2 == null) {
return 1;
} else {
return o1.moduleId.compareTo(o2.moduleId);
}, INSTALLABLE(
R.string.online_repo,
true,
true
) {
override fun compare(o1: ModuleHolder?, o2: ModuleHolder?): Int {
if (o1 != null && o2 != null) {
var cmp = o1.filterLevel.compareTo(o2.filterLevel)
if (cmp != 0) return cmp
val lastUpdated1 =
if (o1.repoModule == null) 0L else o1.repoModule!!.lastUpdated
val lastUpdated2 =
if (o2.repoModule == null) 0L else o2.repoModule!!.lastUpdated
cmp = lastUpdated2.compareTo(lastUpdated1)
return if (cmp != 0) cmp else o1.mainModuleName!!.compareTo(o2.mainModuleName!!)
} else if (o1 != null) {
return -1
} else if (o2 != null) {
return 1
}
return 0
}
}
},
FOOTER(R.string.loading, false, false) {
override fun compare(o1: ModuleHolder?, o2: ModuleHolder?): Int {
if (o1 != null && o2 != null) {
return o1.footerPx.compareTo(o2.footerPx)
} else if (o1 != null) {
return -1
} else if (o2 != null) {
return 1
}
return 0
}
};
}
}
}

@ -56,8 +56,8 @@ class ModuleViewAdapter : RecyclerView.Adapter<ModuleViewAdapter.ViewHolder>() {
}
}
}
} catch (ignored: Exception) {
Timber.e("Error while updating module holder. This may mean we're trying to update too early.")
} catch (e: Exception) {
Timber.e(e, "Error while updating module holder. This may mean we're trying to update too early.")
}
}
@ -138,7 +138,7 @@ class ModuleViewAdapter : RecyclerView.Adapter<ModuleViewAdapter.ViewHolder>() {
if (initState) return@setOnClickListener // Skip if non user
val moduleHolder = moduleHolder
if (i < actionButtonsTypes.size && moduleHolder != null) {
actionButtonsTypes[i]!!.doAction(v as Chip?, moduleHolder)
(v as Chip?)?.let { actionButtonsTypes[i]!!.doAction(it, moduleHolder) }
if (moduleHolder.shouldRemove()) {
cardView.visibility = View.GONE
}
@ -149,8 +149,10 @@ class ModuleViewAdapter : RecyclerView.Adapter<ModuleViewAdapter.ViewHolder>() {
val moduleHolder = moduleHolder
var didSomething = false
if (i < actionButtonsTypes.size && moduleHolder != null) {
didSomething = actionButtonsTypes[i]!!
.doActionLong(v as Chip?, moduleHolder)
didSomething = (v as Chip?)?.let {
actionButtonsTypes[i]!!
.doActionLong(it, moduleHolder)
} == true
if (moduleHolder.shouldRemove()) {
cardView.visibility = View.GONE
}

@ -72,6 +72,8 @@ class ModuleViewListBuilder(private val activity: Activity) {
Timber.i("appendRemoteModules() called")
}
synchronized(updateLock) {
Timber.i("appendRemoteModules() started")
val startTime = System.currentTimeMillis()
val showIncompatible = MainApplication.isShowIncompatibleModules
for (moduleHolder in mappedModuleHolders.values) {
moduleHolder.repoModule = null
@ -119,6 +121,7 @@ class ModuleViewListBuilder(private val activity: Activity) {
}
}
}
Timber.i("appendRemoteModules() finished in %dms", System.currentTimeMillis() - startTime)
}
}
}

@ -10,12 +10,13 @@ import com.fox2code.mmm.MainApplication.Companion.getSharedPreferences
import com.fox2code.mmm.utils.io.Hashes.Companion.hashSha256
import com.fox2code.mmm.utils.io.PropUtils.Companion.isNullString
import com.fox2code.mmm.utils.io.net.Http.Companion.doHttpGet
import com.fox2code.mmm.utils.room.ReposList
import com.fox2code.mmm.utils.room.ReposListDatabase
import org.json.JSONObject
import timber.log.Timber
import java.nio.charset.StandardCharsets
@Suppress("UNUSED_PARAMETER", "MemberVisibilityCanBePrivate")
@Suppress("MemberVisibilityCanBePrivate")
class CustomRepoManager internal constructor(
mainApplication: MainApplication?, private val repoManager: RepoManager
) {
@ -35,7 +36,7 @@ class CustomRepoManager internal constructor(
// now the same as above but for room database
val applicationContext = mainApplication!!.applicationContext
val db = Room.databaseBuilder(
applicationContext, ReposListDatabase::class.java, "reposlist.db"
applicationContext, ReposListDatabase::class.java, "ReposList.db"
).build()
val reposListDao = db.reposListDao()
val reposListList = reposListDao.getAll()
@ -49,6 +50,7 @@ class CustomRepoManager internal constructor(
(repoManager.addOrGet(repo) as CustomRepoData).override = "custom_repo_$index"
}
}
db.close()
}
}
@ -113,7 +115,8 @@ class CustomRepoManager internal constructor(
applicationContext, ReposListDatabase::class.java, "reposlist.db"
).build()
val reposListDao = db.reposListDao()
reposListDao.insert(id, repo, true, donate, support, submitModule, 0, name, website)
val reposList = ReposList(id, repo, true, donate, support, submitModule, 0, name, website)
reposListDao.insert(reposList)
repoCount++
dirty = true
val customRepoData = repoManager.addOrGet(repo) as CustomRepoData
@ -127,6 +130,7 @@ class CustomRepoManager internal constructor(
// Set the enabled state to true
customRepoData.isEnabled = true
customRepoData.updateEnabledState()
db.close()
return customRepoData
}

@ -41,8 +41,7 @@ open class RepoData(url: String, cacheRoot: File) : XRepo() {
private var metaDataCache: JSONObject?
@JvmField
var lastUpdate
: Long = 0
var lastUpdate: Long = 0
@JvmField
var website: String? = null
@ -72,18 +71,7 @@ open class RepoData(url: String, cacheRoot: File) : XRepo() {
var defaultSubmitModule: String? = null
override var name: String? = null
get() = {
// if name is null return defaultName and if defaultName is null return url
if (field == null) {
if (defaultName == null) {
url
} else {
defaultName
}
} else {
field
}
}.toString()
get() = field ?: defaultName
set(value) {
field = value
}
@ -150,9 +138,7 @@ open class RepoData(url: String, cacheRoot: File) : XRepo() {
isForceHide = shouldForceHide(tempVarForPreferenceId)
// basically same as above but for room database
val db = Room.databaseBuilder(
INSTANCE!!.applicationContext,
ReposListDatabase::class.java,
"repo_database"
INSTANCE!!.applicationContext, ReposListDatabase::class.java, "ReposList.db"
).allowMainThreadQueries().build()
val reposListRoom = db.reposListDao()
val reposListRoomList = reposListRoom.getById(preferenceId!!)
@ -189,6 +175,7 @@ open class RepoData(url: String, cacheRoot: File) : XRepo() {
Timber.w("Failed to load repo metadata from database: " + e.message + ". If this is a first time run, this is normal.")
}
}
db.close()
}
open fun prepare(): Boolean {
@ -204,8 +191,7 @@ open class RepoData(url: String, cacheRoot: File) : XRepo() {
var nameForModules =
if (name.endsWith(" (Official)")) name.substring(0, name.length - 11) else name
nameForModules = if (nameForModules.endsWith(" [Official]")) nameForModules.substring(
0,
nameForModules.length - 11
0, nameForModules.length - 11
) else nameForModules
nameForModules =
if (nameForModules.contains("Official")) nameForModules.replace("Official", "")
@ -307,9 +293,9 @@ open class RepoData(url: String, cacheRoot: File) : XRepo() {
} else {
val db = Room.databaseBuilder(
INSTANCE!!.applicationContext,
ReposListDatabase::class.java,
ReposListDatabase::class.java,
"ReposList.db",
).build()
).allowMainThreadQueries().build()
val reposList = db.reposListDao().getById(preferenceId!!)
// should never happen but for safety
if (reposList.enabled) {
@ -325,9 +311,21 @@ open class RepoData(url: String, cacheRoot: File) : XRepo() {
INSTANCE!!.applicationContext,
ReposListDatabase::class.java,
"ReposList.db",
).build()
).allowMainThreadQueries().build()
val reposList = db.reposListDao().getById(preferenceId!!)
db.reposListDao().update(name = reposList.name, enabled = value, id = reposList.id, donate = reposList.donate, support = reposList.support, website = reposList.website, submitModule = reposList.submitModule, lastUpdate = reposList.lastUpdate.toLong(), url = reposList.url)
db.reposListDao().update(
name = reposList.name,
enabled = value,
id = reposList.id,
donate = reposList.donate.toString(),
support = reposList.support.toString(),
website = reposList.website.toString(),
submitModule = reposList.submitModule.toString(),
lastUpdate = reposList.lastUpdate.toLong(),
url = reposList.url
)
db.close()
}
@Throws(IOException::class)
@ -381,6 +379,7 @@ open class RepoData(url: String, cacheRoot: File) : XRepo() {
ReposListDatabase::class.java,
"ReposList.db",
).allowMainThreadQueries().build()
val reposList = db.reposListDao().getById(preferenceId!!)
enabled = if (reposList.enabled) {
!isForceHide
@ -433,10 +432,14 @@ open class RepoData(url: String, cacheRoot: File) : XRepo() {
val diff = currentTime - lastUpdate
val diffMinutes = diff / (60 * 1000) % 60
Timber.d("Repo $preferenceId updated: $diffMinutes minutes ago")
db.close()
db2.close()
diffMinutes > if (BuildConfig.DEBUG) 15 else 30
} else {
Timber.d("Repo $preferenceId should update could not find repo in database")
Timber.d("This is probably an error, please report this to the developer")
db.close()
db2.close()
true
}
} else {

@ -7,6 +7,7 @@ package com.fox2code.mmm.repo
import androidx.room.Room
import com.fox2code.mmm.MainApplication
import com.fox2code.mmm.utils.io.net.Http.Companion.doHttpGet
import com.fox2code.mmm.utils.room.ModuleListCache
import com.fox2code.mmm.utils.room.ModuleListCacheDatabase
import com.fox2code.mmm.utils.room.ReposListDatabase
import org.json.JSONArray
@ -127,169 +128,178 @@ class RepoUpdater(repoData2: RepoData) {
return true
}
if (indexRaw != null) {
try {
// iterate over modules, using this.supportedProperties as a template to attempt to get each property from the module. everything that is not null is added to the module
// use room to insert to
// props avail:
val db = Room.databaseBuilder(
MainApplication.INSTANCE!!,
ModuleListCacheDatabase::class.java,
"ModuleListCache.db"
).allowMainThreadQueries().build()
// array with module info default values
// supported properties for a module
//id=<string>
//name=<string>
//version=<string>
//versionCode=<int>
//author=<string>
//description=<string>
//minApi=<int>
//maxApi=<int>
//minMagisk=<int>
//needRamdisk=<boolean>
//support=<url>
//donate=<url>
//config=<package>
//changeBoot=<boolean>
//mmtReborn=<boolean>
// extra properties only useful for the database
//repoId=<string>
//installed=<boolean>
//installedVersionCode=<int> (only if installed)
//
// all except first six can be null
// this.indexRaw is the raw index file (json)
val modules = JSONObject(String(indexRaw!!, StandardCharsets.UTF_8))
// androidacy repo uses "data" key, others should use "modules" key. Both are JSONArrays
val modulesArray: JSONArray = if (repoData.name == "Androidacy Modules Repo") {
// get modules from "data" key. This is a JSONArray so we need to convert it to a JSONObject
modules.getJSONArray("data")
} else {
// get modules from "modules" key. This is a JSONArray so we need to convert it to a JSONObject
modules.getJSONArray("modules")
}
val moduleListCacheDao = db.moduleListCacheDao()
moduleListCacheDao.deleteByRepoId(repoData.preferenceId!!)
// iterate over modules. pls don't hate me for this, its ugly but it works
for (n in 0 until modulesArray.length()) {
// get module
val module = modulesArray.getJSONObject(n)
try {
// get module id
// if codename is present, prefer that over id
val id: String? =
if (module.has("codename") && module.getString("codename") != "") {
module.getString("codename")
// new thread to update the database
val thread = Thread {
val startTime = System.currentTimeMillis()
Timber.d("Updating database for %s", repoData.preferenceId)
try {
// iterate over modules, using this.supportedProperties as a template to attempt to get each property from the module. everything that is not null is added to the module
// use room to insert to
// props avail:
val db = Room.databaseBuilder(
MainApplication.INSTANCE!!,
ModuleListCacheDatabase::class.java,
"ModuleListCache.db"
).build()
// all except first six can be null
// this.indexRaw is the raw index file (json)
val modules = JSONObject(String(indexRaw!!, StandardCharsets.UTF_8))
// androidacy repo uses "data" key, others should use "modules" key. Both are JSONArrays
val modulesArray: JSONArray = if (repoData.name == "Androidacy Modules Repo") {
// get modules from "data" key. This is a JSONArray so we need to convert it to a JSONObject
modules.getJSONArray("data")
} else {
// get modules from "modules" key. This is a JSONArray so we need to convert it to a JSONObject
modules.getJSONArray("modules")
}
val moduleListCacheDao = db.moduleListCacheDao()
moduleListCacheDao.deleteByRepoId(repoData.preferenceId!!)
// iterate over modules. pls don't hate me for this, its ugly but it works
for (n in 0 until modulesArray.length()) {
// get module
val module = modulesArray.getJSONObject(n)
try {
// get module id
// if codename is present, prefer that over id
val id: String? =
if (module.has("codename") && module.getString("codename") != "") {
module.getString("codename")
} else {
module.getString("id")
}
// get module name
val name = module.getString("name")
// get module version
val version = module.getString("version")
// get module version code
val versionCode = module.getInt("versionCode")
// get module author
val author = module.getString("author")
// get module description
val description = module.getString("description")
// get module min api
val minApi: String =
if (module.has("minApi") && module.getString("minApi") != "") {
module.getString("minApi")
} else {
"0"
}
// coerce min api to int
val minApiInt = minApi.toInt()
// get module max api and set to 0 if it's "" or null
val maxApi: String =
if (module.has("maxApi") && module.getString("maxApi") != "") {
module.getString("maxApi")
} else {
"0"
}
// coerce max api to int
val maxApiInt = maxApi.toInt()
// get module min magisk
val minMagisk: String =
if (module.has("minMagisk") && module.getString("minMagisk") != "") {
module.getString("minMagisk")
} else {
"0"
}
// coerce min magisk to int
val minMagiskInt = minMagisk.toInt()
// get module need ramdisk
val needRamdisk: Boolean = if (module.has("needRamdisk")) {
module.getBoolean("needRamdisk")
} else {
module.getString("id")
false
}
// get module name
val name = module.getString("name")
// get module version
val version = module.getString("version")
// get module version code
val versionCode = module.getInt("versionCode")
// get module author
val author = module.getString("author")
// get module description
val description = module.getString("description")
// get module min api
val minApi: String =
if (module.has("minApi") && module.getString("minApi") != "") {
module.getString("minApi")
// get module support
val support: String? = if (module.has("support")) {
module.getString("support")
} else {
"0"
""
}
// coerce min api to int
val minApiInt = minApi.toInt()
// get module max api and set to 0 if it's "" or null
val maxApi: String =
if (module.has("maxApi") && module.getString("maxApi") != "") {
module.getString("maxApi")
// get module donate
val donate: String? = if (module.has("donate")) {
module.getString("donate")
} else {
"0"
""
}
// coerce max api to int
val maxApiInt = maxApi.toInt()
// get module min magisk
val minMagisk: String =
if (module.has("minMagisk") && module.getString("minMagisk") != "") {
module.getString("minMagisk")
// get module config
val config: String? = if (module.has("config")) {
module.getString("config")
} else {
"0"
""
}
// coerce min magisk to int
val minMagiskInt = minMagisk.toInt()
// get module need ramdisk
val needRamdisk: Boolean = if (module.has("needRamdisk")) {
module.getBoolean("needRamdisk")
} else {
false
}
// get module support
val support: String? = if (module.has("support")) {
module.getString("support")
} else {
""
}
// get module donate
val donate: String? = if (module.has("donate")) {
module.getString("donate")
} else {
""
}
// get module config
val config: String? = if (module.has("config")) {
module.getString("config")
} else {
""
}
// get module change boot
val changeBoot: Boolean = if (module.has("changeBoot")) {
module.getBoolean("changeBoot")
} else {
false
}
// get module mmt reborn
val mmtReborn: Boolean = if (module.has("mmtReborn")) {
module.getBoolean("mmtReborn")
} else {
false
}
// try to get updated_at or lastUpdate value for lastUpdate
val lastUpdate: Int = if (module.has("updated_at")) {
module.getInt("updated_at")
} else if (module.has("lastUpdate")) {
module.getInt("lastUpdate")
} else {
0
}
// now downloads or stars
val downloads: Int = if (module.has("downloads")) {
module.getInt("downloads")
} else if (module.has("stars")) {
module.getInt("stars")
} else {
0
}
// get module repo id
val repoId = repoData.preferenceId
// get safe property. for now, only supported by androidacy repo and they use "vt_status" key
var safe = false
if (repoData.name == "Androidacy Modules Repo") {
if (module.has("vt_status")) {
if (module.getString("vt_status") == "Clean") {
safe = true
// get module change boot
val changeBoot: Boolean = if (module.has("changeBoot")) {
module.getBoolean("changeBoot")
} else {
false
}
// get module mmt reborn
val mmtReborn: Boolean = if (module.has("mmtReborn")) {
module.getBoolean("mmtReborn")
} else {
false
}
// try to get updated_at or lastUpdate value for lastUpdate
val lastUpdate: Int = if (module.has("updated_at")) {
module.getInt("updated_at")
} else if (module.has("lastUpdate")) {
module.getInt("lastUpdate")
} else {
0
}
// now downloads or stars
val downloads: Int = if (module.has("downloads")) {
module.getInt("downloads")
} else if (module.has("stars")) {
module.getInt("stars")
} else {
0
}
// get module repo id
val repoId = repoData.preferenceId
// get safe property. for now, only supported by androidacy repo and they use "vt_status" key
var safe = false
if (repoData.name == "Androidacy Modules Repo") {
if (module.has("vt_status")) {
if (module.getString("vt_status") == "Clean") {
safe = true
}
}
}
val moduleListCache = ModuleListCache(
name = name,
version = version,
versionCode = versionCode,
author = author,
description = description,
minApi = minApiInt,
maxApi = maxApiInt,
minMagisk = minMagiskInt,
needRamdisk = needRamdisk,
support = support ?: "",
donate = donate ?: "",
config = config ?: "",
changeBoot = changeBoot,
mmtReborn = mmtReborn,
repoId = repoId!!,
safe = safe,
lastUpdate = lastUpdate.toLong(),
stats = downloads,
codename = id ?: ""
)
moduleListCacheDao.insert(moduleListCache)
} catch (ignored: Exception) {
}
moduleListCacheDao.insert(name = name, version = version, versionCode = versionCode, author = author, description = description, minApi = minApiInt, maxApi = maxApiInt, minMagisk = minMagiskInt, needRamdisk = needRamdisk, support = support ?: "", donate = donate ?: "", config = config ?: "", changeBoot = changeBoot, mmtReborn = mmtReborn, repoId = repoId!!, safe = safe, lastUpdate = lastUpdate.toLong(), stats = downloads, codename = id ?: "")
} catch (ignored: Exception) {
}
db.close()
val endTime = System.currentTimeMillis()
val timeTaken = endTime - startTime
Timber.d("Time taken to parse modules: $timeTaken ms")
} catch (ignored: Exception) {
}
} catch (ignored: Exception) {
}
thread.start()
indexRaw = null
// set lastUpdate
val db = Room.databaseBuilder(
@ -300,6 +310,7 @@ class RepoUpdater(repoData2: RepoData) {
val repoListDao = db.reposListDao()
repoListDao.setLastUpdate(repoData.preferenceId!!, System.currentTimeMillis())
db.close()
success.set(true)
} else {
success.set(true) // assume we're reading from cache. this may be unsafe but it's better than nothing
}

@ -5,7 +5,6 @@
package com.fox2code.mmm.settings;
import static com.fox2code.mmm.settings.SettingsActivity.RepoFragment.applyMaterial3;
import static java.lang.Integer.parseInt;
import android.annotation.SuppressLint;
import android.app.ActivityManager;
@ -98,12 +97,12 @@ import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.RandomAccessFile;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Locale;
import java.util.List;
import java.util.Objects;
import java.util.Random;
import java.util.Set;
@ -136,35 +135,30 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
@PerformanceClass
public static int getDevicePerformanceClass() {
int devicePerformanceClass;
int androidVersion = Build.VERSION.SDK_INT;
int cpuCount = Runtime.getRuntime().availableProcessors();
int memoryClass = ((ActivityManager) Objects.requireNonNull(MainApplication.getINSTANCE()).getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass();
int totalCpuFreq = 0;
int freqResolved = 0;
for (int i = 0; i < cpuCount; i++) {
try (RandomAccessFile reader = new RandomAccessFile(String.format(Locale.ENGLISH, "/sys/devices/system/cpu/cpu%d/cpufreq/cpuinfo_max_freq", i), "r")) {
String line = reader.readLine();
if (line != null) {
totalCpuFreq += parseInt(line) / 1000;
freqResolved++;
}
} catch (Exception ignore) {
}
}
int maxCpuFreq = freqResolved == 0 ? -1 : (int) Math.ceil(totalCpuFreq / (float) freqResolved);
// special algorithm to determine performance class. low is < 4 cores and/ore < 4GB ram, mid is 4-6 cores and 4-6GB ram, high is > 6 cores and > 6GB ram. android sdk version is used as well
// device is awarded 1 point for each core and 1 point for each GB of ram.
if (androidVersion < 21 || cpuCount <= 2 || memoryClass <= 100 || cpuCount <= 4 && maxCpuFreq != -1 && maxCpuFreq <= 1250 || cpuCount <= 4 && maxCpuFreq <= 1600 && memoryClass <= 128 && androidVersion == 21 || cpuCount <= 4 && maxCpuFreq <= 1300 && memoryClass <= 128 && androidVersion <= 24) {
devicePerformanceClass = PERFORMANCE_CLASS_LOW;
} else if (cpuCount < 8 || memoryClass <= 160 || maxCpuFreq != -1 && maxCpuFreq <= 2050 || maxCpuFreq == -1 && cpuCount == 8 && androidVersion <= 23) {
devicePerformanceClass = PERFORMANCE_CLASS_AVERAGE;
int points = 0;
int cores = Runtime.getRuntime().availableProcessors();
ActivityManager activityManager = (ActivityManager) Objects.requireNonNull(MainApplication.getINSTANCE()).getSystemService(Context.ACTIVITY_SERVICE);
if (activityManager != null) {
ActivityManager.MemoryInfo memoryInfo = new ActivityManager.MemoryInfo();
activityManager.getMemoryInfo(memoryInfo);
long totalMemory = memoryInfo.totalMem;
points += cores;
points += totalMemory / 1024 / 1024 / 1024;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
points += 1;
}
Timber.d("Device performance class: %d", points);
if (points <= 9) {
return PERFORMANCE_CLASS_LOW;
} else if (points <= 12) {
return PERFORMANCE_CLASS_AVERAGE;
} else {
devicePerformanceClass = PERFORMANCE_CLASS_HIGH;
return PERFORMANCE_CLASS_HIGH;
}
Timber.d("getDevicePerformanceClass: androidVersion=" + androidVersion + " cpuCount=" + cpuCount + " memoryClass=" + memoryClass + " maxCpuFreq=" + maxCpuFreq + " devicePerformanceClass=" + devicePerformanceClass);
return devicePerformanceClass;
}
@SuppressLint("RestrictedApi")
@ -1072,6 +1066,7 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
}
// Get magisk_alt_repo enabled state from room reposlist db
ReposListDatabase db = Room.databaseBuilder(requireContext(), ReposListDatabase.class, "ReposList.db").allowMainThreadQueries().build();
// add listener to magisk_alt_repo_enabled switch to update room db
Preference magiskAltRepoEnabled = Objects.requireNonNull(findPreference("pref_magisk_alt_repo_enabled"));
magiskAltRepoEnabled.setOnPreferenceChangeListener((preference, newValue) -> {
@ -1259,17 +1254,17 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
@SuppressLint("RestrictedApi")
public void updateCustomRepoList(boolean initial) {
RealmConfiguration realmConfiguration = new RealmConfiguration.Builder().name("ReposList.realm").encryptionKey(MainApplication.getINSTANCE().getKey()).allowQueriesOnUiThread(true).allowWritesOnUiThread(true).directory(MainApplication.getINSTANCE().getDataDirWithPath("realms")).schemaVersion(1).build();
Realm realm = Realm.getInstance(realmConfiguration);
// get all repos that are not built-in
int CUSTOM_REPO_ENTRIES = 0;
// array of custom repos
ArrayList<String> customRepos = new ArrayList<>();
RealmResults<ReposList> customRepoDataDB = realm.where(ReposList.class).findAll();
for (ReposList repo : customRepoDataDB) {
if (!repo.getId().equals("androidacy_repo") && !repo.getId().equals("magisk_alt_repo")) {
ReposListDatabase db = Room.databaseBuilder(requireContext(), ReposListDatabase.class, "ReposList.db").allowMainThreadQueries().build();
List<ReposList> reposList = db.reposListDao().getAll();
for (ReposList repo : reposList) {
ArrayList<String> buildInRepos = new ArrayList<>(Arrays.asList("androidacy_repo", "magisk_alt_repo"));
if (!buildInRepos.contains(repo.getId())) {
CUSTOM_REPO_ENTRIES++;
customRepos.add(repo.getUrl());
customRepos.add(repo.getId());
}
}
Timber.d("%d repos: %s", CUSTOM_REPO_ENTRIES, customRepos);
@ -1286,12 +1281,7 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
if (preference == null) continue;
final int index = i;
preference.setOnPreferenceClickListener(preference1 -> {
if (realm.isInTransaction()) {
realm.commitTransaction();
}
realm.beginTransaction();
Objects.requireNonNull(realm.where(ReposList.class).equalTo("id", repoData.preferenceId).findFirst()).deleteFromRealm();
realm.commitTransaction();
db.reposListDao().delete(customRepos.get(index));
customRepoManager.removeRepo(index);
updateCustomRepoList(false);
preference1.setVisible(false);
@ -1428,16 +1418,15 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
if (preference == null) return;
if (!preferenceName.contains("androidacy") && !preferenceName.contains("magisk_alt_repo")) {
if (repoData != null) {
RealmConfiguration realmConfiguration = new RealmConfiguration.Builder().name("ReposList.realm").encryptionKey(MainApplication.getINSTANCE().getKey()).allowQueriesOnUiThread(true).allowWritesOnUiThread(true).directory(MainApplication.getINSTANCE().getDataDirWithPath("realms")).schemaVersion(1).build();
Realm realm = Realm.getInstance(realmConfiguration);
RealmResults<ReposList> repoDataRealmResults = realm.where(ReposList.class).equalTo("id", repoData.preferenceId).findAll();
ReposListDatabase db = Room.databaseBuilder(requireContext(), ReposListDatabase.class, "ReposList.db").allowMainThreadQueries().build();
ReposList reposList = db.reposListDao().getById(repoData.preferenceId);
Timber.d("Setting preference " + preferenceName + " because it is not the Androidacy repo or the Magisk Alt Repo");
if (repoData.isForceHide() || repoDataRealmResults.isEmpty()) {
if (repoData.isForceHide() || reposList == null) {
Timber.d("Hiding preference " + preferenceName + " because it is null or force hidden");
hideRepoData(preferenceName);
return;
} else {
Timber.d("Showing preference %s because the forceHide status is %s and the RealmResults is %s", preferenceName, repoData.isForceHide(), repoDataRealmResults.toString());
Timber.d("Showing preference %s because the forceHide status is %s and the RealmResults is %s", preferenceName, repoData.isForceHide(), reposList);
preference.setTitle(repoData.getName());
preference.setVisible(true);
// set website, support, and submitmodule as well as donate
@ -1474,7 +1463,6 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
findPreference(preferenceName + "_donate").setVisible(false);
}
}
realm.close();
} else {
Timber.d("Hiding preference " + preferenceName + " because it's data is null");
hideRepoData(preferenceName);

@ -382,10 +382,15 @@ enum class PropUtils {
InputStreamReader(inputStream, StandardCharsets.UTF_8)
).use { bufferedReader ->
var line: String
while (bufferedReader.readLine().also { line = it } != null) {
var lineNumber = 0
val iterator = bufferedReader.lineSequence().iterator()
while (iterator.hasNext()) {
line = iterator.next()
lineNumber++
while (line.startsWith("\u0000")) line = line.substring(1)
if (line.startsWith("$what=")) {
moduleId = line.substring(what.length + 1).trim { it <= ' ' }
break
}
}
}

@ -7,7 +7,7 @@ package com.fox2code.mmm.utils.room
import androidx.room.Entity
@Suppress("unused")
@Entity(tableName = "modulelistcache")
@Entity(tableName = "modulelistcache", primaryKeys = ["codename"], indices = [androidx.room.Index(value = ["codename"], unique = true)])
class ModuleListCache (
var codename: String,
var version: String,

@ -69,7 +69,7 @@ interface ModuleListCacheDao {
fun getByCodename(codename: String): ModuleListCache
@Insert(entity = ModuleListCache::class, onConflict = OnConflictStrategy.REPLACE)
fun insert(codename: String, version: String, versionCode: Int, author: String, description: String, minApi: Int, maxApi: Int, minMagisk: Int, needRamdisk: Boolean, support: String, donate: String, config: String, changeBoot: Boolean, mmtReborn: Boolean, repoId: String, lastUpdate: Long, safe: Boolean, name: String, stats: Int)
fun insert(moduleListCache: ModuleListCache)
@Query("UPDATE modulelistcache SET version = :version WHERE codename = :codename")
fun setVersion(codename: String, version: String)

@ -5,11 +5,10 @@
package com.fox2code.mmm.utils.room
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "ReposList")
@Entity(tableName = "ReposList", primaryKeys = ["id"], indices = [androidx.room.Index(value = ["id"], unique = true)])
data class ReposList(
@PrimaryKey var id: String,
var id: String,
var url: String,
var enabled: Boolean,
var donate: String?,

@ -5,6 +5,8 @@
package com.fox2code.mmm.utils.room
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
@Suppress("unused")
@ -26,11 +28,11 @@ interface ReposListDao {
@Query("SELECT * FROM ReposList WHERE id = :id")
fun getById(id: String): ReposList
@Query("INSERT INTO ReposList VALUES (:id, :url, :enabled, :donate, :support, :submitModule, :lastUpdate, :name, :website)")
fun insert(id: String, url: String, enabled: Boolean, donate: String?, support: String?, submitModule: String?, lastUpdate: Long, name: String, website: String?)
@Insert(entity = ReposList::class, onConflict = OnConflictStrategy.REPLACE)
fun insert(reposList: ReposList)
@Query("UPDATE ReposList SET url = :url, enabled = :enabled, donate = :donate, support = :support, submitModule = :submitModule, lastUpdate = :lastUpdate, name = :name, website = :website WHERE id = :id")
fun update(id: String, url: String?, enabled: Boolean?, donate: String?, support: String?, submitModule: String?, lastUpdate: Long?, name: String?, website: String?)
fun update(id: String = "", url: String = "", enabled: Boolean = false, donate: String = "", support: String = "", submitModule: String = "", lastUpdate: Long = 0, name: String = "", website: String = "")
@Query("DELETE FROM ReposList WHERE id = :id")
fun delete(id: String)

@ -439,4 +439,6 @@
<string name="reinstall_warning">Most modules should be uninstalled, then reinstalled cleanly. For this reason, AMM does not support reinstalling modules while they are installed on the device.</string>
<string name="build_expired">This build has expired. Please update to the latest version.</string>
<string name="eula_agree_v2_headline">EULA and Terms</string>
<string name="error_creating_repos_database">Could not create repos db</string>
<string name="error_creating_modulelistcache_database">Failed to create module cache db</string>
</resources>

@ -5,8 +5,8 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
mavenCentral()
google()
mavenCentral()
maven {
setUrl("https://jitpack.io")
}

Loading…
Cancel
Save