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