mirror of
https://github.com/topjohnwu/Magisk
synced 2024-11-13 20:54:12 +01:00
Remove more RxJava
This commit is contained in:
parent
4631077c49
commit
8647ba4729
@ -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")
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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 <reified Builder : Query.Builder> query(builder: Builder.() -> Unit = {}) =
|
||||
inline fun <reified Builder : Query.Builder> 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<String>.toMap() = map { it.split(Regex("\\|")) }
|
||||
.map { it.toMapInternal() }
|
||||
|
||||
private fun List<String>.toMapInternal() = map { it.split("=", limit = 2) }
|
||||
.filter { it.size == 2 }
|
||||
.map { Pair(it[0], it[1]) }
|
||||
.toMap()
|
||||
|
@ -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<Delete> {
|
||||
suspend fun deleteOutdated() = buildQuery<Delete> {
|
||||
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<Delete> {
|
||||
suspend fun delete(packageName: String) = buildQuery<Delete> {
|
||||
condition {
|
||||
equals("package_name", packageName)
|
||||
}
|
||||
}.ignoreElement()
|
||||
}.commit()
|
||||
|
||||
fun delete(uid: Int) = query<Delete> {
|
||||
suspend fun delete(uid: Int) = buildQuery<Delete> {
|
||||
condition {
|
||||
equals("uid", uid)
|
||||
}
|
||||
}.ignoreElement()
|
||||
}.commit()
|
||||
|
||||
fun fetch(uid: Int) = query<Select> {
|
||||
suspend fun fetch(uid: Int) = buildQuery<Select> {
|
||||
condition {
|
||||
equals("uid", uid)
|
||||
}
|
||||
}.map { it.first().toPolicySafe() }
|
||||
}.query().first().toPolicyOrNull()
|
||||
|
||||
fun update(policy: MagiskPolicy) = query<Replace> {
|
||||
suspend fun update(policy: MagiskPolicy) = buildQuery<Replace> {
|
||||
values(policy.toMap())
|
||||
}.ignoreElement()
|
||||
}.commit()
|
||||
|
||||
fun fetchAll() = query<Select> {
|
||||
suspend fun <R: Any> fetchAll(mapper: (MagiskPolicy) -> R) = buildQuery<Select> {
|
||||
condition {
|
||||
equals("uid/100000", Const.USER_ID)
|
||||
}
|
||||
}.map { it.mapNotNull { it.toPolicySafe() } }
|
||||
}.query {
|
||||
it.toPolicyOrNull()?.let(mapper)
|
||||
}
|
||||
|
||||
|
||||
private fun Map<String, String>.toPolicySafe(): MagiskPolicy? {
|
||||
private fun Map<String, String>.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
|
||||
}
|
||||
|
@ -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 <R : Any> query(crossinline mapper: (Map<String, String>) -> R?): List<R> =
|
||||
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 {
|
||||
|
@ -4,17 +4,19 @@ class SettingsDao : BaseDao() {
|
||||
|
||||
override val table = Table.SETTINGS
|
||||
|
||||
fun delete(key: String) = query<Delete> {
|
||||
suspend fun delete(key: String) = buildQuery<Delete> {
|
||||
condition { equals("key", key) }
|
||||
}.ignoreElement()
|
||||
}.commit()
|
||||
|
||||
fun put(key: String, value: Int) = query<Replace> {
|
||||
suspend fun put(key: String, value: Int) = buildQuery<Replace> {
|
||||
values("key" to key, "value" to value)
|
||||
}.ignoreElement()
|
||||
}.commit()
|
||||
|
||||
fun fetch(key: String, default: Int = -1) = query<Select> {
|
||||
suspend fun fetch(key: String, default: Int = -1) = buildQuery<Select> {
|
||||
fields("value")
|
||||
condition { equals("key", key) }
|
||||
}.map { it.firstOrNull()?.values?.firstOrNull()?.toIntOrNull() ?: default }
|
||||
}.query {
|
||||
it["value"]?.toIntOrNull()
|
||||
}.firstOrNull() ?: default
|
||||
|
||||
}
|
||||
|
@ -4,17 +4,19 @@ class StringDao : BaseDao() {
|
||||
|
||||
override val table = Table.STRINGS
|
||||
|
||||
fun delete(key: String) = query<Delete> {
|
||||
suspend fun delete(key: String) = buildQuery<Delete> {
|
||||
condition { equals("key", key) }
|
||||
}.ignoreElement()
|
||||
}.commit()
|
||||
|
||||
fun put(key: String, value: String) = query<Replace> {
|
||||
suspend fun put(key: String, value: String) = buildQuery<Replace> {
|
||||
values("key" to key, "value" to value)
|
||||
}.ignoreElement()
|
||||
}.commit()
|
||||
|
||||
fun fetch(key: String, default: String = "") = query<Select> {
|
||||
suspend fun fetch(key: String, default: String = "") = buildQuery<Select> {
|
||||
fields("value")
|
||||
condition { equals("key", key) }
|
||||
}.map { it.firstOrNull()?.values?.firstOrNull() ?: default }
|
||||
}.query {
|
||||
it["value"]
|
||||
}.firstOrNull() ?: default
|
||||
|
||||
}
|
||||
|
@ -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<LogRepository>()
|
||||
logRepo.insert(log).subscribeK(onError = { Timber.e(it) })
|
||||
GlobalScope.launch {
|
||||
logRepo.insert(log)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleNotify(context: Context, data: Bundle) {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<MagiskLog> {
|
||||
deleteOutdated()
|
||||
return fetch()
|
||||
}
|
||||
|
||||
@Query("SELECT * FROM logs ORDER BY time DESC")
|
||||
protected abstract fun fetch(): Single<MutableList<MagiskLog>>
|
||||
|
||||
@Insert
|
||||
abstract fun insert(log: MagiskLog): Completable
|
||||
protected abstract suspend fun fetch(): MutableList<MagiskLog>
|
||||
|
||||
@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)
|
||||
|
||||
}
|
||||
|
@ -13,16 +13,16 @@ interface GithubRawServices {
|
||||
//region topjohnwu/magisk_files
|
||||
|
||||
@GET("$MAGISK_FILES/master/stable.json")
|
||||
fun fetchStableUpdate(): Single<UpdateInfo>
|
||||
suspend fun fetchStableUpdate(): UpdateInfo
|
||||
|
||||
@GET("$MAGISK_FILES/master/beta.json")
|
||||
fun fetchBetaUpdate(): Single<UpdateInfo>
|
||||
suspend fun fetchBetaUpdate(): UpdateInfo
|
||||
|
||||
@GET("$MAGISK_FILES/canary/debug.json")
|
||||
fun fetchCanaryUpdate(): Single<UpdateInfo>
|
||||
suspend fun fetchCanaryUpdate(): UpdateInfo
|
||||
|
||||
@GET
|
||||
fun fetchCustomUpdate(@Url url: String): Single<UpdateInfo>
|
||||
suspend fun fetchCustomUpdate(@Url url: String): UpdateInfo
|
||||
|
||||
@GET("$MAGISK_FILES/{$REVISION}/snet.jar")
|
||||
@Streaming
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<String>() {
|
||||
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)
|
||||
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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<Context>(Protected).getSharedPreferences("su_timeout", 0) }
|
||||
single { PreferenceManager.getDefaultSharedPreferences(get<Context>(Protected)) }
|
||||
single { LocalBroadcastManager.getInstance(get()) }
|
||||
}
|
||||
|
||||
private fun createDEContext(context: Context): Context {
|
||||
|
@ -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() }
|
||||
|
@ -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 }
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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<ViewEvent>()
|
||||
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<Boolean> {
|
||||
val subject = PublishSubject.create<Boolean>()
|
||||
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 <T> Observable<T>.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 <T> Single<T>.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 <T> Maybe<T>.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 <T> Flowable<T>.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
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
// ---
|
||||
|
||||
|
@ -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
|
||||
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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<PolicyItem>(
|
||||
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)
|
||||
|
||||
}
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user