diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 2474cd66e..f88e8891f 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -126,7 +126,7 @@ dependencies { val vRoom = "2.2.5" implementation("androidx.room:room-runtime:${vRoom}") - implementation("androidx.room:room-rxjava2:${vRoom}") + implementation("androidx.room:room-ktx:${vRoom}") kapt("androidx.room:room-compiler:${vRoom}") implementation("androidx.navigation:navigation-fragment-ktx:${Deps.vNav}") @@ -139,9 +139,10 @@ dependencies { implementation("androidx.preference:preference:1.1.1") implementation("androidx.recyclerview:recyclerview:1.1.0") implementation("androidx.fragment:fragment-ktx:1.2.5") - implementation("androidx.work:work-runtime:2.3.4") + implementation("androidx.work:work-runtime-ktx:2.3.4") implementation("androidx.transition:transition:1.3.1") implementation("androidx.multidex:multidex:2.0.1") implementation("androidx.core:core-ktx:1.3.0") + implementation("androidx.localbroadcastmanager:localbroadcastmanager:1.0.0") implementation("com.google.android.material:material:1.2.0-beta01") } diff --git a/app/src/main/java/com/topjohnwu/magisk/core/GeneralReceiver.kt b/app/src/main/java/com/topjohnwu/magisk/core/GeneralReceiver.kt index ac0c384fe..ea243b962 100644 --- a/app/src/main/java/com/topjohnwu/magisk/core/GeneralReceiver.kt +++ b/app/src/main/java/com/topjohnwu/magisk/core/GeneralReceiver.kt @@ -12,6 +12,8 @@ import com.topjohnwu.magisk.extensions.reboot import com.topjohnwu.magisk.model.entity.internal.Configuration import com.topjohnwu.magisk.model.entity.internal.DownloadSubject import com.topjohnwu.superuser.Shell +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch import org.koin.core.inject open class GeneralReceiver : BaseReceiver() { @@ -25,6 +27,10 @@ open class GeneralReceiver : BaseReceiver() { override fun onReceive(context: ContextWrapper, intent: Intent?) { intent ?: return + fun rmPolicy(pkg: String) = GlobalScope.launch { + policyDB.delete(pkg) + } + when (intent.action ?: return) { Intent.ACTION_REBOOT -> { SuCallbackHandler(context, intent.getStringExtra("action"), intent.extras) @@ -32,11 +38,11 @@ open class GeneralReceiver : BaseReceiver() { Intent.ACTION_PACKAGE_REPLACED -> { // This will only work pre-O if (Config.suReAuth) - policyDB.delete(getPkg(intent)).blockingGet() + rmPolicy(getPkg(intent)) } Intent.ACTION_PACKAGE_FULLY_REMOVED -> { val pkg = getPkg(intent) - policyDB.delete(pkg).blockingGet() + rmPolicy(pkg) Shell.su("magiskhide --rm $pkg").submit() } Intent.ACTION_LOCALE_CHANGED -> Shortcuts.setup(context) diff --git a/app/src/main/java/com/topjohnwu/magisk/core/UpdateCheckService.kt b/app/src/main/java/com/topjohnwu/magisk/core/UpdateCheckService.kt index dd9345d6e..da2cc5543 100644 --- a/app/src/main/java/com/topjohnwu/magisk/core/UpdateCheckService.kt +++ b/app/src/main/java/com/topjohnwu/magisk/core/UpdateCheckService.kt @@ -1,31 +1,33 @@ package com.topjohnwu.magisk.core import android.content.Context -import androidx.work.Worker +import androidx.work.CoroutineWorker import androidx.work.WorkerParameters import com.topjohnwu.magisk.BuildConfig import com.topjohnwu.magisk.core.view.Notifications import com.topjohnwu.magisk.data.repository.MagiskRepository -import com.topjohnwu.magisk.extensions.inject import com.topjohnwu.superuser.Shell +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.koin.core.KoinComponent +import org.koin.core.inject class UpdateCheckService(context: Context, workerParams: WorkerParameters) - : Worker(context, workerParams) { + : CoroutineWorker(context, workerParams), KoinComponent { private val magiskRepo: MagiskRepository by inject() - override fun doWork(): Result { + override suspend fun doWork(): Result { // Make sure shell initializer was ran - Shell.getShell() - return runCatching { - magiskRepo.fetchUpdate().blockingGet() - if (BuildConfig.VERSION_CODE < Info.remote.app.versionCode) + withContext(Dispatchers.IO) { + Shell.getShell() + } + return magiskRepo.fetchUpdate()?.let { + if (BuildConfig.VERSION_CODE < it.app.versionCode) Notifications.managerUpdate(applicationContext) - else if (Info.env.isActive && Info.env.magiskVersionCode < Info.remote.magisk.versionCode) + else if (Info.env.isActive && Info.env.magiskVersionCode < it.magisk.versionCode) Notifications.magiskUpdate(applicationContext) Result.success() - }.getOrElse { - Result.failure() - } + } ?: Result.failure() } } diff --git a/app/src/main/java/com/topjohnwu/magisk/core/magiskdb/BaseDao.kt b/app/src/main/java/com/topjohnwu/magisk/core/magiskdb/BaseDao.kt index 0bc976b77..462f81e64 100644 --- a/app/src/main/java/com/topjohnwu/magisk/core/magiskdb/BaseDao.kt +++ b/app/src/main/java/com/topjohnwu/magisk/core/magiskdb/BaseDao.kt @@ -1,8 +1,6 @@ package com.topjohnwu.magisk.core.magiskdb import androidx.annotation.StringDef -import com.topjohnwu.superuser.Shell -import io.reactivex.Single abstract class BaseDao { @@ -20,25 +18,11 @@ abstract class BaseDao { @TableStrict abstract val table: String - inline fun query(builder: Builder.() -> Unit = {}) = + inline fun buildQuery(builder: Builder.() -> Unit = {}) = Builder::class.java.newInstance() .apply { table = this@BaseDao.table } .apply(builder) .toString() .let { Query(it) } - .query() } - -fun Query.query() = query.su() - -private fun String.suRaw() = Single.fromCallable { Shell.su(this).exec().out } -private fun String.su() = suRaw().map { it.toMap() } - -private fun List.toMap() = map { it.split(Regex("\\|")) } - .map { it.toMapInternal() } - -private fun List.toMapInternal() = map { it.split("=", limit = 2) } - .filter { it.size == 2 } - .map { Pair(it[0], it[1]) } - .toMap() diff --git a/app/src/main/java/com/topjohnwu/magisk/core/magiskdb/PolicyDao.kt b/app/src/main/java/com/topjohnwu/magisk/core/magiskdb/PolicyDao.kt index a1aab0560..62b16faf9 100644 --- a/app/src/main/java/com/topjohnwu/magisk/core/magiskdb/PolicyDao.kt +++ b/app/src/main/java/com/topjohnwu/magisk/core/magiskdb/PolicyDao.kt @@ -7,6 +7,8 @@ import com.topjohnwu.magisk.core.model.MagiskPolicy import com.topjohnwu.magisk.core.model.toMap import com.topjohnwu.magisk.core.model.toPolicy import com.topjohnwu.magisk.extensions.now +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch import timber.log.Timber import java.util.concurrent.TimeUnit @@ -17,55 +19,56 @@ class PolicyDao( override val table: String = Table.POLICY - fun deleteOutdated( - nowSeconds: Long = TimeUnit.MILLISECONDS.toSeconds(now) - ) = query { + suspend fun deleteOutdated() = buildQuery { condition { greaterThan("until", "0") and { - lessThan("until", nowSeconds.toString()) + lessThan("until", TimeUnit.MILLISECONDS.toSeconds(now).toString()) } or { lessThan("until", "0") } } - }.ignoreElement() + }.commit() - fun delete(packageName: String) = query { + suspend fun delete(packageName: String) = buildQuery { condition { equals("package_name", packageName) } - }.ignoreElement() + }.commit() - fun delete(uid: Int) = query { + suspend fun delete(uid: Int) = buildQuery { condition { equals("uid", uid) } - }.ignoreElement() + }.commit() - fun fetch(uid: Int) = query { condition { equals("uid", uid) } - }.map { it.first().toPolicySafe() } + }.query().first().toPolicyOrNull() - fun update(policy: MagiskPolicy) = query { + suspend fun update(policy: MagiskPolicy) = buildQuery { values(policy.toMap()) - }.ignoreElement() + }.commit() - fun fetchAll() = query { condition { equals("uid/100000", Const.USER_ID) } - }.map { it.mapNotNull { it.toPolicySafe() } } + }.query { + it.toPolicyOrNull()?.let(mapper) + } - - private fun Map.toPolicySafe(): MagiskPolicy? { + private fun Map.toPolicyOrNull(): MagiskPolicy? { return runCatching { toPolicy(context.packageManager) }.getOrElse { Timber.e(it) if (it is PackageManager.NameNotFoundException) { val uid = getOrElse("uid") { null } ?: return null - delete(uid).subscribe() + GlobalScope.launch { + delete(uid.toInt()) + } } null } diff --git a/app/src/main/java/com/topjohnwu/magisk/core/magiskdb/Query.kt b/app/src/main/java/com/topjohnwu/magisk/core/magiskdb/Query.kt index 6f7d53b81..50900feb3 100644 --- a/app/src/main/java/com/topjohnwu/magisk/core/magiskdb/Query.kt +++ b/app/src/main/java/com/topjohnwu/magisk/core/magiskdb/Query.kt @@ -1,6 +1,12 @@ package com.topjohnwu.magisk.core.magiskdb import androidx.annotation.StringDef +import com.topjohnwu.magisk.extensions.await +import com.topjohnwu.superuser.Shell +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.withContext class Query(private val _query: String) { val query get() = "magisk --sqlite '$_query'" @@ -9,6 +15,24 @@ class Query(private val _query: String) { val requestType: String var table: String } + + suspend inline fun query(crossinline mapper: (Map) -> R?): List = + withContext(Dispatchers.Default) { + Shell.su(query).await().out.map { line -> + async { + line.split("\\|".toRegex()) + .map { it.split("=", limit = 2) } + .filter { it.size == 2 } + .map { it[0] to it[1] } + .toMap() + .let(mapper) + } + }.awaitAll().filterNotNull() + } + + suspend inline fun query() = query { it } + + suspend inline fun commit() = Shell.su(query).to(null).await() } class Delete : Query.Builder { diff --git a/app/src/main/java/com/topjohnwu/magisk/core/magiskdb/SettingsDao.kt b/app/src/main/java/com/topjohnwu/magisk/core/magiskdb/SettingsDao.kt index 197c71ede..db1d43cf1 100644 --- a/app/src/main/java/com/topjohnwu/magisk/core/magiskdb/SettingsDao.kt +++ b/app/src/main/java/com/topjohnwu/magisk/core/magiskdb/SettingsDao.kt @@ -4,17 +4,19 @@ class SettingsDao : BaseDao() { override val table = Table.SETTINGS - fun delete(key: String) = query { + suspend fun delete(key: String) = buildQuery { condition { equals("key", key) } - }.ignoreElement() + }.commit() - fun put(key: String, value: Int) = query { + suspend fun put(key: String, value: Int) = buildQuery { values("key" to key, "value" to value) - }.ignoreElement() + }.commit() - fun fetch(key: String, default: Int = -1) = query { fields("value") condition { equals("key", key) } - }.map { it.firstOrNull()?.values?.firstOrNull()?.toIntOrNull() ?: default } + }.query { + it["value"]?.toIntOrNull() + }.firstOrNull() ?: default } diff --git a/app/src/main/java/com/topjohnwu/magisk/core/magiskdb/StringDao.kt b/app/src/main/java/com/topjohnwu/magisk/core/magiskdb/StringDao.kt index 0fedaa414..bbfa99f7e 100644 --- a/app/src/main/java/com/topjohnwu/magisk/core/magiskdb/StringDao.kt +++ b/app/src/main/java/com/topjohnwu/magisk/core/magiskdb/StringDao.kt @@ -4,17 +4,19 @@ class StringDao : BaseDao() { override val table = Table.STRINGS - fun delete(key: String) = query { + suspend fun delete(key: String) = buildQuery { condition { equals("key", key) } - }.ignoreElement() + }.commit() - fun put(key: String, value: String) = query { + suspend fun put(key: String, value: String) = buildQuery { values("key" to key, "value" to value) - }.ignoreElement() + }.commit() - fun fetch(key: String, default: String = "") = query { fields("value") condition { equals("key", key) } - }.map { it.firstOrNull()?.values?.firstOrNull() ?: default } + }.query { + it["value"] + }.firstOrNull() ?: default } diff --git a/app/src/main/java/com/topjohnwu/magisk/core/su/SuCallbackHandler.kt b/app/src/main/java/com/topjohnwu/magisk/core/su/SuCallbackHandler.kt index 9057b7fe0..f1a63ca79 100644 --- a/app/src/main/java/com/topjohnwu/magisk/core/su/SuCallbackHandler.kt +++ b/app/src/main/java/com/topjohnwu/magisk/core/su/SuCallbackHandler.kt @@ -19,10 +19,11 @@ import com.topjohnwu.magisk.data.repository.LogRepository import com.topjohnwu.magisk.extensions.get import com.topjohnwu.magisk.extensions.startActivity import com.topjohnwu.magisk.extensions.startActivityWithRoot -import com.topjohnwu.magisk.extensions.subscribeK import com.topjohnwu.magisk.model.entity.toLog import com.topjohnwu.magisk.ui.surequest.SuRequestActivity import com.topjohnwu.superuser.Shell +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch import timber.log.Timber object SuCallbackHandler : ProviderCallHandler { @@ -111,7 +112,9 @@ object SuCallbackHandler : ProviderCallHandler { ) val logRepo = get() - logRepo.insert(log).subscribeK(onError = { Timber.e(it) }) + GlobalScope.launch { + logRepo.insert(log) + } } private fun handleNotify(context: Context, data: Bundle) { diff --git a/app/src/main/java/com/topjohnwu/magisk/core/su/SuRequestHandler.kt b/app/src/main/java/com/topjohnwu/magisk/core/su/SuRequestHandler.kt index deeaa63c6..d3736bd2a 100644 --- a/app/src/main/java/com/topjohnwu/magisk/core/su/SuRequestHandler.kt +++ b/app/src/main/java/com/topjohnwu/magisk/core/su/SuRequestHandler.kt @@ -111,13 +111,13 @@ abstract class SuRequestHandler( } catch (e: IOException) { Timber.e(e) } finally { - if (until >= 0) - policyDB.update(policy).blockingAwait() runCatching { input.close() output.close() socket.close() } + if (until >= 0) + policyDB.update(policy) } } } diff --git a/app/src/main/java/com/topjohnwu/magisk/data/database/SuLogDao.kt b/app/src/main/java/com/topjohnwu/magisk/data/database/SuLogDao.kt index 29100b00d..b35ce4951 100644 --- a/app/src/main/java/com/topjohnwu/magisk/data/database/SuLogDao.kt +++ b/app/src/main/java/com/topjohnwu/magisk/data/database/SuLogDao.kt @@ -2,8 +2,8 @@ package com.topjohnwu.magisk.data.database import androidx.room.* import com.topjohnwu.magisk.model.entity.MagiskLog -import io.reactivex.Completable -import io.reactivex.Single +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext import java.util.* @Database(version = 1, entities = [MagiskLog::class], exportSchema = false) @@ -18,19 +18,20 @@ abstract class SuLogDao(private val db: SuLogDatabase) { private val twoWeeksAgo = Calendar.getInstance().apply { add(Calendar.WEEK_OF_YEAR, -2) }.timeInMillis - fun deleteAll() = Completable.fromAction { db.clearAllTables() } + suspend fun deleteAll() = withContext(Dispatchers.IO) { db.clearAllTables() } - fun fetchAll() = deleteOutdated().andThen(fetch()) + suspend fun fetchAll(): MutableList { + deleteOutdated() + return fetch() + } @Query("SELECT * FROM logs ORDER BY time DESC") - protected abstract fun fetch(): Single> - - @Insert - abstract fun insert(log: MagiskLog): Completable + protected abstract suspend fun fetch(): MutableList @Query("DELETE FROM logs WHERE time < :timeout") - protected abstract fun deleteOutdated( - timeout: Long = twoWeeksAgo - ): Completable + protected abstract suspend fun deleteOutdated(timeout: Long = twoWeeksAgo) + + @Insert + abstract suspend fun insert(log: MagiskLog) } diff --git a/app/src/main/java/com/topjohnwu/magisk/data/network/GithubServices.kt b/app/src/main/java/com/topjohnwu/magisk/data/network/GithubServices.kt index 58e4626c4..2049bcaa6 100644 --- a/app/src/main/java/com/topjohnwu/magisk/data/network/GithubServices.kt +++ b/app/src/main/java/com/topjohnwu/magisk/data/network/GithubServices.kt @@ -13,16 +13,16 @@ interface GithubRawServices { //region topjohnwu/magisk_files @GET("$MAGISK_FILES/master/stable.json") - fun fetchStableUpdate(): Single + suspend fun fetchStableUpdate(): UpdateInfo @GET("$MAGISK_FILES/master/beta.json") - fun fetchBetaUpdate(): Single + suspend fun fetchBetaUpdate(): UpdateInfo @GET("$MAGISK_FILES/canary/debug.json") - fun fetchCanaryUpdate(): Single + suspend fun fetchCanaryUpdate(): UpdateInfo @GET - fun fetchCustomUpdate(@Url url: String): Single + suspend fun fetchCustomUpdate(@Url url: String): UpdateInfo @GET("$MAGISK_FILES/{$REVISION}/snet.jar") @Streaming diff --git a/app/src/main/java/com/topjohnwu/magisk/data/repository/DBConfig.kt b/app/src/main/java/com/topjohnwu/magisk/data/repository/DBConfig.kt index a33bdde4b..be897fe45 100644 --- a/app/src/main/java/com/topjohnwu/magisk/data/repository/DBConfig.kt +++ b/app/src/main/java/com/topjohnwu/magisk/data/repository/DBConfig.kt @@ -2,7 +2,9 @@ package com.topjohnwu.magisk.data.repository import com.topjohnwu.magisk.core.magiskdb.SettingsDao import com.topjohnwu.magisk.core.magiskdb.StringDao -import io.reactivex.schedulers.Schedulers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking import kotlin.properties.ReadWriteProperty import kotlin.reflect.KProperty @@ -38,17 +40,19 @@ class DBSettingsValue( @Synchronized override fun getValue(thisRef: DBConfig, property: KProperty<*>): Int { if (value == null) - value = thisRef.settingsDao.fetch(name, default).blockingGet() - return value!! + value = runBlocking { + thisRef.settingsDao.fetch(name, default) + } + return value as Int } override fun setValue(thisRef: DBConfig, property: KProperty<*>, value: Int) { synchronized(this) { this.value = value } - thisRef.settingsDao.put(name, value) - .subscribeOn(Schedulers.io()) - .subscribe() + GlobalScope.launch { + thisRef.settingsDao.put(name, value) + } } } @@ -77,7 +81,9 @@ class DBStringsValue( @Synchronized override fun getValue(thisRef: DBConfig, property: KProperty<*>): String { if (value == null) - value = thisRef.stringDao.fetch(name, default).blockingGet() + value = runBlocking { + thisRef.stringDao.fetch(name, default) + } return value!! } @@ -87,19 +93,23 @@ class DBStringsValue( } if (value.isEmpty()) { if (sync) { - thisRef.stringDao.delete(name).blockingAwait() + runBlocking { + thisRef.stringDao.delete(name) + } } else { - thisRef.stringDao.delete(name) - .subscribeOn(Schedulers.io()) - .subscribe() + GlobalScope.launch { + thisRef.stringDao.delete(name) + } } } else { if (sync) { - thisRef.stringDao.put(name, value).blockingAwait() + runBlocking { + thisRef.stringDao.put(name, value) + } } else { - thisRef.stringDao.put(name, value) - .subscribeOn(Schedulers.io()) - .subscribe() + GlobalScope.launch { + thisRef.stringDao.put(name, value) + } } } } diff --git a/app/src/main/java/com/topjohnwu/magisk/data/repository/LogRepository.kt b/app/src/main/java/com/topjohnwu/magisk/data/repository/LogRepository.kt index 984f4abe1..8b88f98f3 100644 --- a/app/src/main/java/com/topjohnwu/magisk/data/repository/LogRepository.kt +++ b/app/src/main/java/com/topjohnwu/magisk/data/repository/LogRepository.kt @@ -2,19 +2,18 @@ package com.topjohnwu.magisk.data.repository import com.topjohnwu.magisk.core.Const import com.topjohnwu.magisk.data.database.SuLogDao +import com.topjohnwu.magisk.extensions.await import com.topjohnwu.magisk.model.entity.MagiskLog import com.topjohnwu.superuser.Shell -import io.reactivex.Completable -import io.reactivex.Single class LogRepository( private val logDao: SuLogDao ) { - fun fetchLogs() = logDao.fetchAll() + suspend fun fetchSuLogs() = logDao.fetchAll() - fun fetchMagiskLogs() = Single.fromCallable { + suspend fun fetchMagiskLogs(): String { val list = object : AbstractMutableList() { val buf = StringBuilder() override val size get() = 0 @@ -28,16 +27,15 @@ class LogRepository( } } } - Shell.su("cat ${Const.MAGISK_LOG}").to(list).exec() - list.buf.toString() + Shell.su("cat ${Const.MAGISK_LOG}").to(list).await() + return list.buf.toString() } - fun clearLogs() = logDao.deleteAll() + suspend fun clearLogs() = logDao.deleteAll() - fun clearMagiskLogs() = Completable.fromAction { - Shell.su("echo -n > ${Const.MAGISK_LOG}").exec() - } + fun clearMagiskLogs(cb: (Shell.Result) -> Unit) = + Shell.su("echo -n > ${Const.MAGISK_LOG}").submit(cb) - fun insert(log: MagiskLog) = logDao.insert(log) + suspend fun insert(log: MagiskLog) = logDao.insert(log) } diff --git a/app/src/main/java/com/topjohnwu/magisk/data/repository/MagiskRepository.kt b/app/src/main/java/com/topjohnwu/magisk/data/repository/MagiskRepository.kt index 4ffe3c648..d9a300c6b 100644 --- a/app/src/main/java/com/topjohnwu/magisk/data/repository/MagiskRepository.kt +++ b/app/src/main/java/com/topjohnwu/magisk/data/repository/MagiskRepository.kt @@ -4,55 +4,58 @@ import android.content.pm.PackageManager import com.topjohnwu.magisk.core.Config import com.topjohnwu.magisk.core.Info import com.topjohnwu.magisk.data.network.GithubRawServices +import com.topjohnwu.magisk.extensions.await import com.topjohnwu.magisk.extensions.getLabel import com.topjohnwu.magisk.extensions.packageName -import com.topjohnwu.magisk.extensions.toSingle import com.topjohnwu.magisk.model.entity.HideAppInfo import com.topjohnwu.magisk.model.entity.HideTarget import com.topjohnwu.superuser.Shell -import io.reactivex.Single +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import timber.log.Timber +import java.io.IOException class MagiskRepository( - private val apiRaw: GithubRawServices, - private val packageManager: PackageManager + private val apiRaw: GithubRawServices, + private val packageManager: PackageManager ) { fun fetchSafetynet() = apiRaw.fetchSafetynet() - fun fetchUpdate() = when (Config.updateChannel) { - Config.Value.DEFAULT_CHANNEL, Config.Value.STABLE_CHANNEL -> apiRaw.fetchStableUpdate() - Config.Value.BETA_CHANNEL -> apiRaw.fetchBetaUpdate() - Config.Value.CANARY_CHANNEL -> apiRaw.fetchCanaryUpdate() - Config.Value.CUSTOM_CHANNEL -> apiRaw.fetchCustomUpdate(Config.customChannelUrl) - else -> throw IllegalArgumentException() - }.flatMap { - // If remote version is lower than current installed, try switching to beta - if (it.magisk.versionCode < Info.env.magiskVersionCode - && Config.updateChannel == Config.Value.DEFAULT_CHANNEL) { - Config.updateChannel = Config.Value.BETA_CHANNEL - apiRaw.fetchBetaUpdate() - } else { - Single.just(it) + suspend fun fetchUpdate() = try { + var info = when (Config.updateChannel) { + Config.Value.DEFAULT_CHANNEL, Config.Value.STABLE_CHANNEL -> apiRaw.fetchStableUpdate() + Config.Value.BETA_CHANNEL -> apiRaw.fetchBetaUpdate() + Config.Value.CANARY_CHANNEL -> apiRaw.fetchCanaryUpdate() + Config.Value.CUSTOM_CHANNEL -> apiRaw.fetchCustomUpdate(Config.customChannelUrl) + else -> throw IllegalArgumentException() } - }.doOnSuccess { Info.remote = it } + if (info.magisk.versionCode < Info.env.magiskVersionCode && + Config.updateChannel == Config.Value.DEFAULT_CHANNEL) { + Config.updateChannel = Config.Value.BETA_CHANNEL + info = apiRaw.fetchBetaUpdate() + } + Info.remote = info + info + } catch (e: IOException) { + Timber.e(e) + null + } - fun fetchApps() = - Single.fromCallable { packageManager.getInstalledApplications(0) } - .flattenAsFlowable { it } - .filter { it.enabled && !blacklist.contains(it.packageName) } - .map { - val label = it.getLabel(packageManager) - val icon = it.loadIcon(packageManager) - HideAppInfo(it, label, icon) - } - .filter { it.processes.isNotEmpty() } - .toList() + suspend fun fetchApps() = withContext(Dispatchers.Default) { + packageManager.getInstalledApplications(0).filter { + it.enabled && !blacklist.contains(it.packageName) + }.map { + val label = it.getLabel(packageManager) + val icon = it.loadIcon(packageManager) + HideAppInfo(it, label, icon) + }.filter { it.processes.isNotEmpty() } + } - fun fetchHideTargets() = Shell.su("magiskhide --ls").toSingle() - .map { it.exec().out } - .flattenAsFlowable { it } - .map { HideTarget(it) } - .toList() + suspend fun fetchHideTargets() = + Shell.su("\"magiskhide --ls\"").await().out.map { + HideTarget(it) + } fun toggleHide(isEnabled: Boolean, packageName: String, process: String) = Shell.su("magiskhide --${isEnabled.state} $packageName $process").submit() diff --git a/app/src/main/java/com/topjohnwu/magisk/di/ApplicationModule.kt b/app/src/main/java/com/topjohnwu/magisk/di/ApplicationModule.kt index 0963a0680..b9014913f 100644 --- a/app/src/main/java/com/topjohnwu/magisk/di/ApplicationModule.kt +++ b/app/src/main/java/com/topjohnwu/magisk/di/ApplicationModule.kt @@ -2,7 +2,6 @@ package com.topjohnwu.magisk.di import android.content.Context import android.os.Build -import androidx.localbroadcastmanager.content.LocalBroadcastManager import androidx.preference.PreferenceManager import com.topjohnwu.magisk.core.ResMgr import com.topjohnwu.magisk.utils.RxBus @@ -19,7 +18,6 @@ val applicationModule = module { factory(Protected) { createDEContext(get()) } single(SUTimeout) { get(Protected).getSharedPreferences("su_timeout", 0) } single { PreferenceManager.getDefaultSharedPreferences(get(Protected)) } - single { LocalBroadcastManager.getInstance(get()) } } private fun createDEContext(context: Context): Context { diff --git a/app/src/main/java/com/topjohnwu/magisk/extensions/XSU.kt b/app/src/main/java/com/topjohnwu/magisk/extensions/XSU.kt index fa93fa1bf..b6cce5211 100644 --- a/app/src/main/java/com/topjohnwu/magisk/extensions/XSU.kt +++ b/app/src/main/java/com/topjohnwu/magisk/extensions/XSU.kt @@ -4,6 +4,8 @@ import com.topjohnwu.magisk.core.Info import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.io.SuFileInputStream import com.topjohnwu.superuser.io.SuFileOutputStream +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext import java.io.File fun reboot(reason: String = if (Info.recovery) "recovery" else "") { @@ -14,3 +16,5 @@ fun File.suOutputStream() = SuFileOutputStream(this) fun File.suInputStream() = SuFileInputStream(this) val hasRoot get() = Shell.rootAccess() + +suspend fun Shell.Job.await() = withContext(Dispatchers.IO) { exec() } diff --git a/app/src/main/java/com/topjohnwu/magisk/model/entity/HideTarget.kt b/app/src/main/java/com/topjohnwu/magisk/model/entity/HideTarget.kt index 27573783c..bd8f1425f 100644 --- a/app/src/main/java/com/topjohnwu/magisk/model/entity/HideTarget.kt +++ b/app/src/main/java/com/topjohnwu/magisk/model/entity/HideTarget.kt @@ -2,9 +2,13 @@ package com.topjohnwu.magisk.model.entity class HideTarget(line: String) { - private val split = line.split(Regex("\\|"), 2) + val packageName: String + val process: String - val packageName = split[0] - val process = split.getOrElse(1) { packageName } + init { + val split = line.split(Regex("\\|"), 2) + packageName = split[0] + process = split.getOrElse(1) { packageName } + } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/base/BaseViewModel.kt b/app/src/main/java/com/topjohnwu/magisk/ui/base/BaseViewModel.kt index 05b17d9f0..58ae55d7e 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/base/BaseViewModel.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ui/base/BaseViewModel.kt @@ -4,6 +4,7 @@ import android.Manifest import androidx.annotation.CallSuper import androidx.core.graphics.Insets import androidx.databinding.Bindable +import androidx.databinding.Observable import androidx.databinding.ObservableField import androidx.databinding.PropertyChangeRegistry import androidx.lifecycle.LiveData @@ -13,23 +14,18 @@ import androidx.navigation.NavDirections import com.topjohnwu.magisk.BR import com.topjohnwu.magisk.core.Info import com.topjohnwu.magisk.core.base.BaseActivity -import com.topjohnwu.magisk.extensions.doOnSubscribeUi import com.topjohnwu.magisk.extensions.value import com.topjohnwu.magisk.model.events.* import com.topjohnwu.magisk.model.navigation.NavigationWrapper import com.topjohnwu.magisk.model.observer.Observer -import io.reactivex.* import io.reactivex.disposables.CompositeDisposable import io.reactivex.disposables.Disposable -import io.reactivex.subjects.PublishSubject import kotlinx.coroutines.Job import org.koin.core.KoinComponent -import androidx.databinding.Observable as BindingObservable abstract class BaseViewModel( - initialState: State = State.LOADING, - val useRx: Boolean = true -) : ViewModel(), BindingObservable, KoinComponent { + initialState: State = State.LOADING +) : ViewModel(), Observable, KoinComponent { enum class State { LOADED, LOADING, LOADING_FAILED @@ -53,8 +49,8 @@ abstract class BaseViewModel( private val _viewEvents = MutableLiveData() private var runningTask: Disposable? = null private var runningJob: Job? = null - private val refreshCallback = object : androidx.databinding.Observable.OnPropertyChangedCallback() { - override fun onPropertyChanged(sender: androidx.databinding.Observable?, propertyId: Int) { + private val refreshCallback = object : Observable.OnPropertyChangedCallback() { + override fun onPropertyChanged(sender: Observable?, propertyId: Int) { requestRefresh() } } @@ -66,13 +62,6 @@ abstract class BaseViewModel( /** This should probably never be called manually, it's called manually via delegate. */ @Synchronized fun requestRefresh() { - if (useRx) { - if (runningTask?.isDisposed?.not() == true) { - return - } - runningTask = rxRefresh() - return - } if (runningJob?.isActive == true) { return } @@ -100,11 +89,6 @@ abstract class BaseViewModel( ViewActionEvent(action).publish() } - fun withPermissions(vararg permissions: String): Observable { - val subject = PublishSubject.create() - return subject.doOnSubscribeUi { RxPermissionEvent(permissions.toList(), subject).publish() } - } - fun withPermissions(vararg permissions: String, callback: (Boolean) -> Unit) { PermissionEvent(permissions.toList(), callback).publish() } @@ -137,7 +121,7 @@ abstract class BaseViewModel( private var callbacks: PropertyChangeRegistry? = null @Synchronized - override fun addOnPropertyChangedCallback(callback: BindingObservable.OnPropertyChangedCallback) { + override fun addOnPropertyChangedCallback(callback: Observable.OnPropertyChangedCallback) { if (callbacks == null) { callbacks = PropertyChangeRegistry() } @@ -145,7 +129,7 @@ abstract class BaseViewModel( } @Synchronized - override fun removeOnPropertyChangedCallback(callback: BindingObservable.OnPropertyChangedCallback) { + override fun removeOnPropertyChangedCallback(callback: Observable.OnPropertyChangedCallback) { callbacks?.remove(callback) } @@ -168,63 +152,4 @@ abstract class BaseViewModel( callbacks?.notifyCallbacks(this, fieldId, null) } - //region Rx - protected fun Observable.applyViewModel(viewModel: BaseViewModel, allowFinishing: Boolean = true) = - doOnSubscribe { viewModel.state = - State.LOADING - } - .doOnError { viewModel.state = - State.LOADING_FAILED - } - .doOnNext { if (allowFinishing) viewModel.state = - State.LOADED - } - - protected fun Single.applyViewModel(viewModel: BaseViewModel, allowFinishing: Boolean = true) = - doOnSubscribe { viewModel.state = - State.LOADING - } - .doOnError { viewModel.state = - State.LOADING_FAILED - } - .doOnSuccess { if (allowFinishing) viewModel.state = - State.LOADED - } - - protected fun Maybe.applyViewModel(viewModel: BaseViewModel, allowFinishing: Boolean = true) = - doOnSubscribe { viewModel.state = - State.LOADING - } - .doOnError { viewModel.state = - State.LOADING_FAILED - } - .doOnComplete { if (allowFinishing) viewModel.state = - State.LOADED - } - .doOnSuccess { if (allowFinishing) viewModel.state = - State.LOADED - } - - protected fun Flowable.applyViewModel(viewModel: BaseViewModel, allowFinishing: Boolean = true) = - doOnSubscribe { viewModel.state = - State.LOADING - } - .doOnError { viewModel.state = - State.LOADING_FAILED - } - .doOnNext { if (allowFinishing) viewModel.state = - State.LOADED - } - - protected fun Completable.applyViewModel(viewModel: BaseViewModel, allowFinishing: Boolean = true) = - doOnSubscribe { viewModel.state = - State.LOADING - } - .doOnError { viewModel.state = - State.LOADING_FAILED - } - .doOnComplete { if (allowFinishing) viewModel.state = - State.LOADED - } - //endregion } diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/flash/FlashViewModel.kt b/app/src/main/java/com/topjohnwu/magisk/ui/flash/FlashViewModel.kt index f3d71b2e6..69a9e8c38 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/flash/FlashViewModel.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ui/flash/FlashViewModel.kt @@ -1,6 +1,5 @@ package com.topjohnwu.magisk.ui.flash -import android.Manifest import android.content.res.Resources import android.net.Uri import android.view.MenuItem @@ -21,7 +20,9 @@ import com.topjohnwu.magisk.ui.base.BaseViewModel import com.topjohnwu.magisk.ui.base.diffListOf import com.topjohnwu.magisk.ui.base.itemBindingOf import com.topjohnwu.superuser.Shell +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import java.io.File import java.util.* @@ -96,25 +97,23 @@ class FlashViewModel( return true } - private fun savePressed() = withPermissions( - Manifest.permission.READ_EXTERNAL_STORAGE, - Manifest.permission.WRITE_EXTERNAL_STORAGE - ) - .map { now } - .map { it.toTime(timeFormatStandard) } - .map { Const.MAGISK_INSTALL_LOG_FILENAME.format(it) } - .map { File(Config.downloadDirectory, it) } - .map { file -> - file.bufferedWriter().use { writer -> - logItems.forEach { - writer.write(it) - writer.newLine() + private fun savePressed() = withExternalRW { + if (!it) + return@withExternalRW + viewModelScope.launch { + val name = Const.MAGISK_INSTALL_LOG_FILENAME.format(now.toTime(timeFormatStandard)) + val file = File(Config.downloadDirectory, name) + withContext(Dispatchers.IO) { + file.bufferedWriter().use { writer -> + logItems.forEach { + writer.write(it) + writer.newLine() + } } } - file.path + SnackbarEvent(file.path).publish() } - .subscribeK { SnackbarEvent(it).publish() } - .add() + } fun restartPressed() = reboot() } diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/hide/HideViewModel.kt b/app/src/main/java/com/topjohnwu/magisk/ui/hide/HideViewModel.kt index 7aa3fc040..1d9a5c5f3 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/hide/HideViewModel.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ui/hide/HideViewModel.kt @@ -3,10 +3,10 @@ package com.topjohnwu.magisk.ui.hide import android.content.pm.ApplicationInfo import androidx.databinding.Bindable import androidx.databinding.ObservableField +import androidx.lifecycle.viewModelScope import com.topjohnwu.magisk.BR import com.topjohnwu.magisk.core.utils.currentLocale import com.topjohnwu.magisk.data.repository.MagiskRepository -import com.topjohnwu.magisk.extensions.subscribeK import com.topjohnwu.magisk.extensions.value import com.topjohnwu.magisk.model.entity.HideAppInfo import com.topjohnwu.magisk.model.entity.HideTarget @@ -18,6 +18,9 @@ import com.topjohnwu.magisk.ui.base.BaseViewModel import com.topjohnwu.magisk.ui.base.Queryable import com.topjohnwu.magisk.ui.base.filterableListOf import com.topjohnwu.magisk.ui.base.itemBindingOf +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext class HideViewModel( private val magiskRepo: MagiskRepository @@ -50,19 +53,18 @@ class HideViewModel( val isFilterExpanded = ObservableField(false) - override fun rxRefresh() = magiskRepo.fetchApps() - .map { it to magiskRepo.fetchHideTargets().blockingGet() } - .map { pair -> pair.first.map { mergeAppTargets(it, pair.second) } } - .flattenAsFlowable { it } - .map { HideItem(it) } - .toList() - .map { it.sort() } - .map { it to items.calculateDiff(it) } - .applyViewModel(this) - .subscribeK { - items.update(it.first, it.second) - submitQuery() + override fun refresh() = viewModelScope.launch { + state = State.LOADING + val apps = magiskRepo.fetchApps() + val hides = magiskRepo.fetchHideTargets() + val (hidden, diff) = withContext(Dispatchers.Default) { + val hidden = apps.map { mergeAppTargets(it, hides) }.map { HideItem(it) }.sort() + hidden to items.calculateDiff(hidden) } + items.update(hidden, diff) + submitQuery() + state = State.LOADED + } // --- diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/home/HomeViewModel.kt b/app/src/main/java/com/topjohnwu/magisk/ui/home/HomeViewModel.kt index 7fe4a75be..3751027b2 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/home/HomeViewModel.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ui/home/HomeViewModel.kt @@ -1,8 +1,8 @@ package com.topjohnwu.magisk.ui.home -import android.Manifest import android.os.Build import androidx.databinding.ObservableField +import androidx.lifecycle.viewModelScope import com.topjohnwu.magisk.BuildConfig import com.topjohnwu.magisk.R import com.topjohnwu.magisk.core.Config @@ -11,9 +11,11 @@ import com.topjohnwu.magisk.core.base.BaseActivity import com.topjohnwu.magisk.core.download.RemoteFileService import com.topjohnwu.magisk.core.model.MagiskJson import com.topjohnwu.magisk.core.model.ManagerJson -import com.topjohnwu.magisk.core.model.UpdateInfo import com.topjohnwu.magisk.data.repository.MagiskRepository -import com.topjohnwu.magisk.extensions.* +import com.topjohnwu.magisk.extensions.await +import com.topjohnwu.magisk.extensions.packageName +import com.topjohnwu.magisk.extensions.res +import com.topjohnwu.magisk.extensions.value import com.topjohnwu.magisk.model.entity.internal.DownloadSubject.Manager import com.topjohnwu.magisk.model.entity.recycler.DeveloperItem import com.topjohnwu.magisk.model.entity.recycler.HomeItem @@ -26,6 +28,7 @@ import com.topjohnwu.magisk.model.events.dialog.UninstallDialog import com.topjohnwu.magisk.ui.base.BaseViewModel import com.topjohnwu.magisk.ui.base.itemBindingOf import com.topjohnwu.superuser.Shell +import kotlinx.coroutines.launch import me.tatarka.bindingcollectionadapter2.BR import kotlin.math.roundToInt @@ -72,27 +75,29 @@ class HomeViewModel( } } - override fun rxRefresh() = repoMagisk.fetchUpdate() - .onErrorReturn { null } - .subscribeK { it?.updateUI() } + override fun refresh() = viewModelScope.launch { + repoMagisk.fetchUpdate()?.apply { + stateMagisk.value = when { + !Info.env.isActive -> MagiskState.NOT_INSTALLED + magisk.isObsolete -> MagiskState.OBSOLETE + else -> MagiskState.UP_TO_DATE + } - private fun UpdateInfo.updateUI() { - stateMagisk.value = when { - !Info.env.isActive -> MagiskState.NOT_INSTALLED - magisk.isObsolete -> MagiskState.OBSOLETE - else -> MagiskState.UP_TO_DATE + stateManager.value = when { + !app.isUpdateChannelCorrect && isConnected.value -> MagiskState.NOT_INSTALLED + app.isObsolete -> MagiskState.OBSOLETE + else -> MagiskState.UP_TO_DATE + } + + stateMagiskRemoteVersion.value = + "${magisk.version} (${magisk.versionCode})" + stateManagerRemoteVersion.value = + "${app.version} (${app.versionCode}) (${stub.versionCode})" + + launch { + ensureEnv() + } } - - stateManager.value = when { - !app.isUpdateChannelCorrect && isConnected.value -> MagiskState.NOT_INSTALLED - app.isObsolete -> MagiskState.OBSOLETE - else -> MagiskState.UP_TO_DATE - } - - stateMagiskRemoteVersion.value = "${magisk.version} (${magisk.versionCode})" - stateManagerRemoteVersion.value = "${app.version} (${app.versionCode}) (${stub.versionCode})" - - ensureEnv() } val showTest = false @@ -109,19 +114,18 @@ class HomeViewModel( fun onManagerPressed() = ManagerInstallDialog().publish() - fun onMagiskPressed() = withPermissions( - Manifest.permission.READ_EXTERNAL_STORAGE, - Manifest.permission.WRITE_EXTERNAL_STORAGE - ).map { check(it);it } - .subscribeK { HomeFragmentDirections.actionHomeFragmentToInstallFragment().publish() } - .add() + fun onMagiskPressed() = withExternalRW { + if (it) { + HomeFragmentDirections.actionHomeFragmentToInstallFragment().publish() + } + } fun hideNotice() { Config.safetyNotice = false isNoticeVisible.value = false } - private fun ensureEnv() { + private suspend fun ensureEnv() { val invalidStates = listOf( MagiskState.NOT_INSTALLED, MagiskState.LOADING @@ -138,21 +142,18 @@ class HomeViewModel( return } - Shell.su("env_check") - .toSingle() - .map { it.exec() } - .filter { !it.isSuccess } - .subscribeK { - shownDialog = true - EnvFixDialog().publish() - } + val result = Shell.su("env_check").await() + if (!result.isSuccess) { + shownDialog = true + EnvFixDialog().publish() + } } private val MagiskJson.isObsolete get() = Info.env.isActive && Info.env.magiskVersionCode < versionCode - val ManagerJson.isUpdateChannelCorrect + private val ManagerJson.isUpdateChannelCorrect get() = versionCode > 0 - val ManagerJson.isObsolete + private val ManagerJson.isObsolete get() = BuildConfig.VERSION_CODE < versionCode } diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/log/LogViewModel.kt b/app/src/main/java/com/topjohnwu/magisk/ui/log/LogViewModel.kt index a2b7fb818..4fb1bfaf2 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/log/LogViewModel.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ui/log/LogViewModel.kt @@ -1,12 +1,12 @@ package com.topjohnwu.magisk.ui.log import androidx.databinding.ObservableField +import androidx.lifecycle.viewModelScope import com.topjohnwu.magisk.BR import com.topjohnwu.magisk.R import com.topjohnwu.magisk.core.Config import com.topjohnwu.magisk.core.Const import com.topjohnwu.magisk.data.repository.LogRepository -import com.topjohnwu.magisk.extensions.subscribeK import com.topjohnwu.magisk.extensions.value import com.topjohnwu.magisk.model.entity.recycler.LogItem import com.topjohnwu.magisk.model.entity.recycler.TextItem @@ -15,12 +15,10 @@ import com.topjohnwu.magisk.ui.base.BaseViewModel import com.topjohnwu.magisk.ui.base.diffListOf import com.topjohnwu.magisk.ui.base.itemBindingOf import com.topjohnwu.superuser.Shell -import io.reactivex.Completable -import io.reactivex.android.schedulers.AndroidSchedulers -import io.reactivex.disposables.Disposable -import io.reactivex.schedulers.Schedulers +import kotlinx.coroutines.* import timber.log.Timber import java.io.File +import java.io.IOException import java.util.* class LogViewModel( @@ -43,28 +41,21 @@ class LogViewModel( val consoleText = ObservableField(" ") - override fun rxRefresh(): Disposable { - val logs = repo.fetchLogs() - .map { it.map { LogItem(it) } } - .observeOn(Schedulers.computation()) - .map { it to items.calculateDiff(it) } - .observeOn(AndroidSchedulers.mainThread()) - .doOnSuccess { - items.firstOrNull()?.isTop = false - items.lastOrNull()?.isBottom = false - - items.update(it.first, it.second) - - items.firstOrNull()?.isTop = true - items.lastOrNull()?.isBottom = true + override fun refresh() = viewModelScope.launch { + consoleText.value = repo.fetchMagiskLogs() + val deferred = withContext(Dispatchers.Default) { + async { + val suLogs = repo.fetchSuLogs().map { LogItem(it) } + suLogs to items.calculateDiff(suLogs) } - .ignoreElement() - - val console = repo.fetchMagiskLogs() - .doOnSuccess { consoleText.value = it } - .ignoreElement() - - return Completable.merge(listOf(logs, console)).subscribeK() + } + delay(500) + val (suLogs, diff) = deferred.await() + items.firstOrNull()?.isTop = false + items.lastOrNull()?.isBottom = false + items.update(suLogs, diff) + items.firstOrNull()?.isTop = true + items.lastOrNull()?.isBottom = true } fun saveMagiskLog() { @@ -76,10 +67,10 @@ class LogViewModel( ) val logFile = File(Config.downloadDirectory, filename) - runCatching { + try { logFile.createNewFile() - }.onFailure { - Timber.e(it) + } catch (e: IOException) { + Timber.e(e) return } @@ -88,18 +79,14 @@ class LogViewModel( } } - fun clearMagiskLog() = repo.clearMagiskLogs() - .subscribeK { - SnackbarEvent(R.string.logs_cleared).publish() - requestRefresh() - } - .add() - - fun clearLog() = repo.clearLogs() - .subscribeK { - SnackbarEvent(R.string.logs_cleared).publish() - requestRefresh() - } - .add() + fun clearMagiskLog() = repo.clearMagiskLogs { + SnackbarEvent(R.string.logs_cleared).publish() + requestRefresh() + } + fun clearLog() = viewModelScope.launch { + repo.clearLogs() + SnackbarEvent(R.string.logs_cleared).publish() + requestRefresh() + } } diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/module/ModuleViewModel.kt b/app/src/main/java/com/topjohnwu/magisk/ui/module/ModuleViewModel.kt index a162ce471..c84399c97 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/module/ModuleViewModel.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ui/module/ModuleViewModel.kt @@ -47,7 +47,7 @@ class ModuleViewModel( private val repoName: RepoByNameDao, private val repoUpdated: RepoByUpdatedDao, private val repoUpdater: RepoUpdater -) : BaseViewModel(useRx = false), Queryable { +) : BaseViewModel(), Queryable { override val queryDelay = 1000L private var queryJob: Job? = null diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/superuser/SuperuserViewModel.kt b/app/src/main/java/com/topjohnwu/magisk/ui/superuser/SuperuserViewModel.kt index 2bd394c39..f7be9b8cd 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/superuser/SuperuserViewModel.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ui/superuser/SuperuserViewModel.kt @@ -3,6 +3,7 @@ package com.topjohnwu.magisk.ui.superuser import android.content.pm.PackageManager import android.content.res.Resources import androidx.databinding.ObservableArrayList +import androidx.lifecycle.viewModelScope import com.topjohnwu.magisk.BR import com.topjohnwu.magisk.R import com.topjohnwu.magisk.core.magiskdb.PolicyDao @@ -10,8 +11,6 @@ import com.topjohnwu.magisk.core.model.MagiskPolicy import com.topjohnwu.magisk.core.utils.BiometricHelper import com.topjohnwu.magisk.core.utils.currentLocale import com.topjohnwu.magisk.databinding.ComparableRvItem -import com.topjohnwu.magisk.extensions.applySchedulers -import com.topjohnwu.magisk.extensions.subscribeK import com.topjohnwu.magisk.extensions.toggle import com.topjohnwu.magisk.model.entity.recycler.PolicyItem import com.topjohnwu.magisk.model.entity.recycler.TappableHeadlineItem @@ -24,7 +23,9 @@ import com.topjohnwu.magisk.ui.base.BaseViewModel import com.topjohnwu.magisk.ui.base.adapterOf import com.topjohnwu.magisk.ui.base.diffListOf import com.topjohnwu.magisk.ui.base.itemBindingOf -import io.reactivex.Single +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import me.tatarka.bindingcollectionadapter2.collections.MergeObservableList class SuperuserViewModel( @@ -53,27 +54,23 @@ class SuperuserViewModel( // --- - override fun rxRefresh() = db.fetchAll() - .flattenAsFlowable { it } - .parallel() - .map { PolicyItem(it, it.applicationInfo.loadIcon(packageManager)) } - .sequential() - .sorted { o1, o2 -> - compareBy( + override fun refresh() = viewModelScope.launch { + state = State.LOADING + val (policies, diff) = withContext(Dispatchers.Default) { + val policies = db.fetchAll { + PolicyItem(it, it.applicationInfo.loadIcon(packageManager)) + }.sortedWith(compareBy( { it.item.appName.toLowerCase(currentLocale) }, { it.item.packageName } - ).compare(o1, o2) + )) + policies to itemsPolicies.calculateDiff(policies) } - .toList() - .map { it to itemsPolicies.calculateDiff(it) } - .applySchedulers() - .applyViewModel(this) - .subscribeK { - itemsPolicies.update(it.first, it.second) - if (itemsPolicies.isNotEmpty()) { - itemsHelpers.remove(itemNoData) - } + itemsPolicies.update(policies, diff) + if (itemsPolicies.isNotEmpty()) { + itemsHelpers.remove(itemNoData) } + state = State.LOADED + } // --- @@ -91,14 +88,13 @@ class SuperuserViewModel( SuperuserFragmentDirections.actionSuperuserFragmentToHideFragment().publish() fun deletePressed(item: PolicyItem) { - fun updateState() = deletePolicy(item.item) - .subscribeK { - itemsPolicies.removeAll { it.genericItemSameAs(item) } - if (itemsPolicies.isEmpty() && itemsHelpers.isEmpty()) { - itemsHelpers.add(itemNoData) - } + fun updateState() = viewModelScope.launch { + db.delete(item.item.uid) + itemsPolicies.removeAll { it.genericItemSameAs(item) } + if (itemsPolicies.isEmpty() && itemsHelpers.isEmpty()) { + itemsHelpers.add(itemNoData) } - .add() + } if (BiometricHelper.isEnabled) { BiometricDialog { @@ -114,34 +110,37 @@ class SuperuserViewModel( //--- - fun updatePolicy(it: PolicyUpdateEvent) = when (it) { - is PolicyUpdateEvent.Notification -> updatePolicy(it.item).map { - when { - it.notification -> R.string.su_snack_notif_on - else -> R.string.su_snack_notif_off - } to it.appName + fun updatePolicy(it: PolicyUpdateEvent) = viewModelScope.launch { + val snackStr = when (it) { + is PolicyUpdateEvent.Notification -> { + updatePolicy(it.item) + when { + it.item.notification -> R.string.su_snack_notif_on + else -> R.string.su_snack_notif_off + } + } + is PolicyUpdateEvent.Log -> { + updatePolicy(it.item) + when { + it.item.logging -> R.string.su_snack_log_on + else -> R.string.su_snack_log_off + } + } } - is PolicyUpdateEvent.Log -> updatePolicy(it.item).map { - when { - it.logging -> R.string.su_snack_log_on - else -> R.string.su_snack_log_off - } to it.appName - } - }.map { resources.getString(it.first, it.second) } - .subscribeK { SnackbarEvent(it).publish() } - .add() + SnackbarEvent(resources.getString(snackStr, it.item.appName)).publish() + } fun togglePolicy(item: PolicyItem, enable: Boolean) { fun updateState() { val policy = if (enable) MagiskPolicy.ALLOW else MagiskPolicy.DENY val app = item.item.copy(policy = policy) - updatePolicy(app) - .map { it.policy == MagiskPolicy.ALLOW } - .map { if (it) R.string.su_snack_grant else R.string.su_snack_deny } - .map { resources.getString(it).format(item.item.appName) } - .subscribeK { SnackbarEvent(it).publish() } - .add() + viewModelScope.launch { + updatePolicy(app) + val res = if (app.policy == MagiskPolicy.ALLOW) R.string.su_snack_grant + else R.string.su_snack_deny + SnackbarEvent(resources.getString(res).format(item.item.appName)) + } } if (BiometricHelper.isEnabled) { @@ -156,10 +155,6 @@ class SuperuserViewModel( //--- - private fun updatePolicy(policy: MagiskPolicy) = - db.update(policy).andThen(Single.just(policy)) - - private fun deletePolicy(policy: MagiskPolicy) = - db.delete(policy.uid).andThen(Single.just(policy)) + private suspend fun updatePolicy(policy: MagiskPolicy) = db.update(policy) } diff --git a/build.gradle.kts b/build.gradle.kts index 5ec7498f0..fc37df7d0 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -60,7 +60,7 @@ subprojects { plugins.hasPlugin("com.android.application")) { android.apply { compileSdkVersion(30) - buildToolsVersion = "30.0.0" + buildToolsVersion = "30.0.1" defaultConfig { if (minSdkVersion == null)