Merge remote-tracking branch 'john/master' into feature/redesign
# Conflicts: # app/build.gradle # app/src/main/AndroidManifest.xml # app/src/main/java/com/topjohnwu/magisk/model/events/ViewEvents.kt # app/src/main/java/com/topjohnwu/magisk/model/navigation/MagiskNavigationEvent.kt # app/src/main/java/com/topjohnwu/magisk/ui/SplashActivity.kt # app/src/main/java/com/topjohnwu/magisk/ui/settings/SettingsFragment.kt # app/src/main/java/com/topjohnwu/magisk/view/MagiskDialog.kt # app/src/main/res/layout/dialog_magisk_base.xml
This commit is contained in:
commit
6ccbc272c6
@ -33,7 +33,6 @@ Furthermore, Magisk provides a **Systemless Interface** to alter the system (or
|
|||||||
Default string resources for Magisk Manager are scattered throughout
|
Default string resources for Magisk Manager are scattered throughout
|
||||||
|
|
||||||
- `app/src/main/res/values/strings.xml`
|
- `app/src/main/res/values/strings.xml`
|
||||||
- `stub/src/main/res/values/strings.xml`
|
|
||||||
- `shared/src/main/res/values/strings.xml`
|
- `shared/src/main/res/values/strings.xml`
|
||||||
|
|
||||||
Translate each and place them in the respective locations (`<module>/src/main/res/values-<lang>/strings.xml`).
|
Translate each and place them in the respective locations (`<module>/src/main/res/values-<lang>/strings.xml`).
|
||||||
|
@ -60,14 +60,26 @@ dependencies {
|
|||||||
|
|
||||||
implementation 'com.github.topjohnwu:jtar:1.0.0'
|
implementation 'com.github.topjohnwu:jtar:1.0.0'
|
||||||
implementation 'com.jakewharton.timber:timber:4.7.1'
|
implementation 'com.jakewharton.timber:timber:4.7.1'
|
||||||
implementation 'com.github.skoumalcz:teanity:0.3.3'
|
|
||||||
implementation 'com.ncapdevi:frag-nav:3.2.0'
|
implementation 'com.ncapdevi:frag-nav:3.2.0'
|
||||||
implementation 'com.github.pwittchen:reactivenetwork-rx2:3.0.6'
|
implementation 'com.github.pwittchen:reactivenetwork-rx2:3.0.6'
|
||||||
|
|
||||||
def vMarkwon = '3.1.0'
|
implementation 'io.reactivex.rxjava2:rxjava:2.2.13'
|
||||||
implementation "ru.noties.markwon:core:${vMarkwon}"
|
implementation 'io.reactivex.rxjava2:rxkotlin:2.4.0'
|
||||||
implementation "ru.noties.markwon:html:${vMarkwon}"
|
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
|
||||||
implementation "ru.noties.markwon:image-svg:${vMarkwon}"
|
|
||||||
|
implementation "org.jetbrains.kotlin:kotlin-stdlib:${vKotlin}"
|
||||||
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${vKotlin}"
|
||||||
|
|
||||||
|
def vBAdapt = '3.1.1'
|
||||||
|
def bindingAdapter = 'me.tatarka.bindingcollectionadapter2:bindingcollectionadapter'
|
||||||
|
implementation "${bindingAdapter}:${vBAdapt}"
|
||||||
|
implementation "${bindingAdapter}-recyclerview:${vBAdapt}"
|
||||||
|
|
||||||
|
def vMarkwon = '4.1.1'
|
||||||
|
implementation "io.noties.markwon:core:${vMarkwon}"
|
||||||
|
implementation "io.noties.markwon:html:${vMarkwon}"
|
||||||
|
implementation "io.noties.markwon:image:${vMarkwon}"
|
||||||
|
implementation 'com.caverock:androidsvg:1.4'
|
||||||
|
|
||||||
def vLibsu = '2.5.1'
|
def vLibsu = '2.5.1'
|
||||||
implementation "com.github.topjohnwu.libsu:core:${vLibsu}"
|
implementation "com.github.topjohnwu.libsu:core:${vLibsu}"
|
||||||
@ -78,13 +90,13 @@ dependencies {
|
|||||||
implementation "org.koin:koin-android:${vKoin}"
|
implementation "org.koin:koin-android:${vKoin}"
|
||||||
implementation "org.koin:koin-androidx-viewmodel:${vKoin}"
|
implementation "org.koin:koin-androidx-viewmodel:${vKoin}"
|
||||||
|
|
||||||
def vRetrofit = '2.6.1'
|
def vRetrofit = '2.6.2'
|
||||||
implementation "com.squareup.retrofit2:retrofit:${vRetrofit}"
|
implementation "com.squareup.retrofit2:retrofit:${vRetrofit}"
|
||||||
implementation "com.squareup.retrofit2:converter-moshi:${vRetrofit}"
|
implementation "com.squareup.retrofit2:converter-moshi:${vRetrofit}"
|
||||||
implementation "com.squareup.retrofit2:converter-scalars:${vRetrofit}"
|
implementation "com.squareup.retrofit2:converter-scalars:${vRetrofit}"
|
||||||
implementation "com.squareup.retrofit2:adapter-rxjava2:${vRetrofit}"
|
implementation "com.squareup.retrofit2:adapter-rxjava2:${vRetrofit}"
|
||||||
|
|
||||||
def vOkHttp = '3.12.2'
|
def vOkHttp = '3.12.6'
|
||||||
implementation "com.squareup.okhttp3:okhttp:${vOkHttp}"
|
implementation "com.squareup.okhttp3:okhttp:${vOkHttp}"
|
||||||
implementation "com.squareup.okhttp3:logging-interceptor:${vOkHttp}"
|
implementation "com.squareup.okhttp3:logging-interceptor:${vOkHttp}"
|
||||||
|
|
||||||
@ -100,19 +112,21 @@ dependencies {
|
|||||||
replacedBy('com.github.topjohnwu:room-runtime')
|
replacedBy('com.github.topjohnwu:room-runtime')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
def vRoom = "2.1.0"
|
def vRoom = "2.2.0"
|
||||||
implementation "com.github.topjohnwu:room-runtime:${vRoom}"
|
implementation "com.github.topjohnwu:room-runtime:${vRoom}"
|
||||||
kapt "androidx.room:room-compiler:${vRoom}"
|
kapt "androidx.room:room-compiler:${vRoom}"
|
||||||
|
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:${kotlinVer}"
|
def vNav = "2.1.0"
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${kotlinVer}"
|
implementation "androidx.navigation:navigation-fragment-ktx:$vNav"
|
||||||
|
implementation "androidx.navigation:navigation-ui-ktx:$vNav"
|
||||||
|
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||||
implementation 'androidx.browser:browser:1.0.0'
|
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0-alpha03'
|
||||||
implementation 'androidx.preference:preference:1.1.0'
|
implementation 'androidx.preference:preference:1.1.0'
|
||||||
implementation 'androidx.recyclerview:recyclerview:1.1.0-beta04'
|
implementation 'androidx.recyclerview:recyclerview:1.0.0'
|
||||||
implementation 'androidx.work:work-runtime:2.2.0'
|
implementation 'androidx.work:work-runtime:2.2.0'
|
||||||
implementation 'androidx.transition:transition:1.2.0-rc01'
|
implementation 'androidx.transition:transition:1.2.0'
|
||||||
implementation 'androidx.multidex:multidex:2.0.1'
|
implementation 'androidx.multidex:multidex:2.0.1'
|
||||||
|
implementation 'androidx.core:core-ktx:1.1.0'
|
||||||
implementation 'com.google.android.material:material:1.1.0-beta01'
|
implementation 'com.google.android.material:material:1.1.0-beta01'
|
||||||
}
|
}
|
||||||
|
2
app/proguard-rules.pro
vendored
2
app/proguard-rules.pro
vendored
@ -29,7 +29,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
# DelegateWorker
|
# DelegateWorker
|
||||||
-keep,allowobfuscation class * extends com.topjohnwu.magisk.model.worker.DelegateWorker
|
-keep,allowobfuscation class * extends com.topjohnwu.magisk.base.DelegateWorker
|
||||||
|
|
||||||
# BootSigner
|
# BootSigner
|
||||||
-keepclassmembers class com.topjohnwu.signing.BootSigner { *; }
|
-keepclassmembers class com.topjohnwu.signing.BootSigner { *; }
|
||||||
|
@ -1,4 +1,22 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
** Special Requirements **
|
||||||
|
|
||||||
|
This AndroidManifest.xml will be copied into the stub
|
||||||
|
APK to allow APK delegation. This is why these special
|
||||||
|
requirements exist.
|
||||||
|
|
||||||
|
* Class names *
|
||||||
|
Class names a.a, a.c, a.e should not be changed as they are used
|
||||||
|
externally. All other class names can be changed.
|
||||||
|
|
||||||
|
* Resource IDs *
|
||||||
|
All resource IDs referred in AndroidManifest.xml is required to be
|
||||||
|
included into the "shared" module to make the ID match with stub.
|
||||||
|
|
||||||
|
-->
|
||||||
|
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
package="com.topjohnwu.magisk">
|
package="com.topjohnwu.magisk">
|
||||||
@ -11,25 +29,17 @@
|
|||||||
|
|
||||||
<application
|
<application
|
||||||
android:name="a.e"
|
android:name="a.e"
|
||||||
|
android:appComponentFactory="a.a"
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:theme="@style/MagiskTheme"
|
|
||||||
android:usesCleartextTraffic="true"
|
android:usesCleartextTraffic="true"
|
||||||
tools:ignore="UnusedAttribute,GoogleAppIndexingWarning">
|
tools:ignore="UnusedAttribute,GoogleAppIndexingWarning"
|
||||||
|
tools:replace="android:appComponentFactory">
|
||||||
|
|
||||||
<!-- Activities -->
|
<!-- Splash -->
|
||||||
|
|
||||||
<activity
|
|
||||||
android:name="a.b"
|
|
||||||
android:configChanges="orientation|screenSize"
|
|
||||||
android:exported="true" />
|
|
||||||
<activity
|
|
||||||
android:name="a.i"
|
|
||||||
android:exported="true"
|
|
||||||
android:theme="@style/Foundation.Default" />
|
|
||||||
<activity
|
<activity
|
||||||
android:name="a.c"
|
android:name="a.c"
|
||||||
android:configChanges="orientation|screenSize"
|
android:configChanges="orientation|screenSize"
|
||||||
android:exported="true"
|
|
||||||
android:theme="@style/SplashTheme">
|
android:theme="@style/SplashTheme">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
@ -37,6 +47,18 @@
|
|||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
|
<!-- Main -->
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name="a.b"
|
||||||
|
android:configChanges="orientation|screenSize"
|
||||||
|
android:exported="true" />
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name="a.i"
|
||||||
|
android:exported="true"
|
||||||
|
android:theme="@style/Foundation.Default" />
|
||||||
|
|
||||||
<activity-alias
|
<activity-alias
|
||||||
android:name="a.s"
|
android:name="a.s"
|
||||||
android:targetActivity="a.c">
|
android:targetActivity="a.c">
|
||||||
@ -46,11 +68,12 @@
|
|||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity-alias>
|
</activity-alias>
|
||||||
|
|
||||||
|
<!-- Flashing -->
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name="a.f"
|
android:name="a.f"
|
||||||
android:configChanges="keyboardHidden|orientation|screenSize"
|
android:configChanges="keyboardHidden|orientation|screenSize"
|
||||||
android:screenOrientation="nosensor"
|
android:screenOrientation="nosensor" />
|
||||||
android:theme="@style/MagiskTheme.Flashing" />
|
|
||||||
|
|
||||||
<!-- Superuser -->
|
<!-- Superuser -->
|
||||||
|
|
||||||
@ -58,8 +81,7 @@
|
|||||||
android:name="a.m"
|
android:name="a.m"
|
||||||
android:directBootAware="true"
|
android:directBootAware="true"
|
||||||
android:excludeFromRecents="true"
|
android:excludeFromRecents="true"
|
||||||
android:exported="false"
|
android:exported="false" />
|
||||||
android:theme="@style/MagiskTheme.SU" />
|
|
||||||
|
|
||||||
<!-- Receiver -->
|
<!-- Receiver -->
|
||||||
|
|
||||||
@ -78,9 +100,10 @@
|
|||||||
</intent-filter>
|
</intent-filter>
|
||||||
</receiver>
|
</receiver>
|
||||||
|
|
||||||
<!-- Service -->
|
<!-- DownloadService -->
|
||||||
|
|
||||||
<service android:name="a.j"
|
<service
|
||||||
|
android:name="a.j"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
|
||||||
<!-- Hardcode GMS version -->
|
<!-- Hardcode GMS version -->
|
||||||
|
@ -1,13 +1,19 @@
|
|||||||
package a;
|
package a;
|
||||||
|
|
||||||
|
import androidx.annotation.Keep;
|
||||||
|
import androidx.core.app.AppComponentFactory;
|
||||||
|
|
||||||
import com.topjohnwu.magisk.utils.PatchAPK;
|
import com.topjohnwu.magisk.utils.PatchAPK;
|
||||||
import com.topjohnwu.signing.BootSigner;
|
import com.topjohnwu.signing.BootSigner;
|
||||||
|
|
||||||
import androidx.annotation.Keep;
|
|
||||||
|
|
||||||
@Keep
|
@Keep
|
||||||
public class a extends BootSigner {
|
public class a extends AppComponentFactory {
|
||||||
|
|
||||||
public static boolean patchAPK(String in, String out, String pkg) {
|
public static boolean patchAPK(String in, String out, String pkg) {
|
||||||
return PatchAPK.patch(in, out, pkg);
|
return PatchAPK.patch(in, out, pkg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) throws Exception {
|
||||||
|
BootSigner.main(args);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,8 @@ import androidx.annotation.NonNull;
|
|||||||
import androidx.work.Worker;
|
import androidx.work.Worker;
|
||||||
import androidx.work.WorkerParameters;
|
import androidx.work.WorkerParameters;
|
||||||
|
|
||||||
import com.topjohnwu.magisk.model.worker.DelegateWorker;
|
import com.topjohnwu.magisk.base.DelegateWorker;
|
||||||
|
import com.topjohnwu.magisk.utils.ResourceMgrKt;
|
||||||
|
|
||||||
import java.lang.reflect.ParameterizedType;
|
import java.lang.reflect.ParameterizedType;
|
||||||
|
|
||||||
@ -18,7 +19,7 @@ public abstract class w<T extends DelegateWorker> extends Worker {
|
|||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
w(@NonNull Context context, @NonNull WorkerParameters workerParams) {
|
w(@NonNull Context context, @NonNull WorkerParameters workerParams) {
|
||||||
super(context, workerParams);
|
super(ResourceMgrKt.wrap(context, false), workerParams);
|
||||||
try {
|
try {
|
||||||
base = ((Class<T>) ((ParameterizedType) getClass().getGenericSuperclass())
|
base = ((Class<T>) ((ParameterizedType) getClass().getGenericSuperclass())
|
||||||
.getActualTypeArguments()[0]).newInstance();
|
.getActualTypeArguments()[0]).newInstance();
|
||||||
|
@ -13,9 +13,11 @@ import com.topjohnwu.magisk.data.database.RepoDatabase_Impl
|
|||||||
import com.topjohnwu.magisk.di.ActivityTracker
|
import com.topjohnwu.magisk.di.ActivityTracker
|
||||||
import com.topjohnwu.magisk.di.koinModules
|
import com.topjohnwu.magisk.di.koinModules
|
||||||
import com.topjohnwu.magisk.extensions.get
|
import com.topjohnwu.magisk.extensions.get
|
||||||
import com.topjohnwu.magisk.net.Networking
|
import com.topjohnwu.magisk.extensions.unwrap
|
||||||
import com.topjohnwu.magisk.utils.LocaleManager
|
import com.topjohnwu.magisk.utils.ResourceMgr
|
||||||
import com.topjohnwu.magisk.utils.RootUtils
|
import com.topjohnwu.magisk.utils.RootInit
|
||||||
|
import com.topjohnwu.magisk.utils.isRunningAsStub
|
||||||
|
import com.topjohnwu.magisk.utils.wrap
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
import org.koin.android.ext.koin.androidContext
|
import org.koin.android.ext.koin.androidContext
|
||||||
import org.koin.core.context.startKoin
|
import org.koin.core.context.startKoin
|
||||||
@ -27,7 +29,7 @@ open class App : Application() {
|
|||||||
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)
|
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)
|
||||||
Shell.Config.setFlags(Shell.FLAG_MOUNT_MASTER or Shell.FLAG_USE_MAGISK_BUSYBOX)
|
Shell.Config.setFlags(Shell.FLAG_MOUNT_MASTER or Shell.FLAG_USE_MAGISK_BUSYBOX)
|
||||||
Shell.Config.verboseLogging(BuildConfig.DEBUG)
|
Shell.Config.verboseLogging(BuildConfig.DEBUG)
|
||||||
Shell.Config.addInitializers(RootUtils::class.java)
|
Shell.Config.addInitializers(RootInit::class.java)
|
||||||
Shell.Config.setTimeout(2)
|
Shell.Config.setTimeout(2)
|
||||||
Room.setFactory {
|
Room.setFactory {
|
||||||
when (it) {
|
when (it) {
|
||||||
@ -39,24 +41,42 @@ open class App : Application() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun attachBaseContext(base: Context) {
|
override fun attachBaseContext(base: Context) {
|
||||||
super.attachBaseContext(base)
|
// Basic setup
|
||||||
if (BuildConfig.DEBUG)
|
if (BuildConfig.DEBUG)
|
||||||
MultiDex.install(base)
|
MultiDex.install(base)
|
||||||
Timber.plant(Timber.DebugTree())
|
Timber.plant(Timber.DebugTree())
|
||||||
|
|
||||||
|
// Some context magic
|
||||||
|
val app: Application
|
||||||
|
val impl: Context
|
||||||
|
if (base is Application) {
|
||||||
|
isRunningAsStub = true
|
||||||
|
app = base
|
||||||
|
impl = base.baseContext
|
||||||
|
} else {
|
||||||
|
app = this
|
||||||
|
impl = base
|
||||||
|
}
|
||||||
|
ResourceMgr.init(impl)
|
||||||
|
super.attachBaseContext(impl.wrap())
|
||||||
|
|
||||||
|
// Normal startup
|
||||||
startKoin {
|
startKoin {
|
||||||
androidContext(this@App)
|
androidContext(baseContext)
|
||||||
modules(koinModules)
|
modules(koinModules)
|
||||||
}
|
}
|
||||||
|
ResourceMgr.reload()
|
||||||
|
app.registerActivityLifecycleCallbacks(get<ActivityTracker>())
|
||||||
|
}
|
||||||
|
|
||||||
registerActivityLifecycleCallbacks(get<ActivityTracker>())
|
// This is required as some platforms expect ContextImpl
|
||||||
|
override fun getBaseContext(): Context {
|
||||||
Networking.init(base)
|
return super.getBaseContext().unwrap()
|
||||||
LocaleManager.setLocale(this)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onConfigurationChanged(newConfig: Configuration) {
|
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||||
super.onConfigurationChanged(newConfig)
|
ResourceMgr.reload(newConfig)
|
||||||
LocaleManager.setLocale(this)
|
if (!isRunningAsStub)
|
||||||
|
super.onConfigurationChanged(newConfig)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,8 +13,8 @@ object Const {
|
|||||||
|
|
||||||
// Versions
|
// Versions
|
||||||
const val SNET_EXT_VER = 13
|
const val SNET_EXT_VER = 13
|
||||||
const val SNET_REVISION = "5adbc435ce93ded953c30ebe587edfd50b5503bc"
|
const val SNET_REVISION = "a6c47f86f10b310358afa9dbe837037dd5d561df"
|
||||||
const val BOOTCTL_REVISION = "9c5dfc1b8245c0b5b524901ef0ff0f8335757b77"
|
const val BOOTCTL_REVISION = "a6c47f86f10b310358afa9dbe837037dd5d561df"
|
||||||
|
|
||||||
// Misc
|
// Misc
|
||||||
const val ANDROID_MANIFEST = "AndroidManifest.xml"
|
const val ANDROID_MANIFEST = "AndroidManifest.xml"
|
||||||
|
124
app/src/main/java/com/topjohnwu/magisk/base/BaseActivity.kt
Normal file
124
app/src/main/java/com/topjohnwu/magisk/base/BaseActivity.kt
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
package com.topjohnwu.magisk.base
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.content.res.Configuration
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.appcompat.app.AppCompatDelegate
|
||||||
|
import androidx.collection.SparseArrayCompat
|
||||||
|
import androidx.core.app.ActivityCompat
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.databinding.DataBindingUtil
|
||||||
|
import androidx.databinding.ViewDataBinding
|
||||||
|
import com.topjohnwu.magisk.BR
|
||||||
|
import com.topjohnwu.magisk.Config
|
||||||
|
import com.topjohnwu.magisk.R
|
||||||
|
import com.topjohnwu.magisk.base.viewmodel.BaseViewModel
|
||||||
|
import com.topjohnwu.magisk.extensions.set
|
||||||
|
import com.topjohnwu.magisk.model.events.EventHandler
|
||||||
|
import com.topjohnwu.magisk.model.permissions.PermissionRequestBuilder
|
||||||
|
import com.topjohnwu.magisk.utils.currentLocale
|
||||||
|
import com.topjohnwu.magisk.utils.wrap
|
||||||
|
import kotlin.random.Random
|
||||||
|
|
||||||
|
typealias RequestCallback = BaseActivity<*, *>.(Int, Intent?) -> Unit
|
||||||
|
|
||||||
|
abstract class BaseActivity<ViewModel : BaseViewModel, Binding : ViewDataBinding> :
|
||||||
|
AppCompatActivity(), EventHandler {
|
||||||
|
|
||||||
|
protected lateinit var binding: Binding
|
||||||
|
protected abstract val layoutRes: Int
|
||||||
|
protected abstract val viewModel: ViewModel
|
||||||
|
protected open val themeRes: Int = R.style.MagiskTheme
|
||||||
|
protected open val snackbarView get() = binding.root
|
||||||
|
|
||||||
|
private val resultCallbacks by lazy { SparseArrayCompat<RequestCallback>() }
|
||||||
|
|
||||||
|
init {
|
||||||
|
val theme = if (Config.darkTheme) {
|
||||||
|
AppCompatDelegate.MODE_NIGHT_YES
|
||||||
|
} else {
|
||||||
|
AppCompatDelegate.MODE_NIGHT_NO
|
||||||
|
}
|
||||||
|
AppCompatDelegate.setDefaultNightMode(theme)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun applyOverrideConfiguration(config: Configuration?) {
|
||||||
|
// Force applying our preferred local
|
||||||
|
config?.setLocale(currentLocale)
|
||||||
|
super.applyOverrideConfiguration(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun attachBaseContext(base: Context) {
|
||||||
|
super.attachBaseContext(base.wrap(false))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
setTheme(themeRes)
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
viewModel.viewEvents.observe(this, viewEventObserver)
|
||||||
|
|
||||||
|
binding = DataBindingUtil.setContentView<Binding>(this, layoutRes).apply {
|
||||||
|
setVariable(BR.viewModel, viewModel)
|
||||||
|
lifecycleOwner = this@BaseActivity
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun withPermissions(vararg permissions: String, builder: PermissionRequestBuilder.() -> Unit) {
|
||||||
|
val request = PermissionRequestBuilder().apply(builder).build()
|
||||||
|
val ungranted = permissions.filter {
|
||||||
|
ContextCompat.checkSelfPermission(this, it) != PackageManager.PERMISSION_GRANTED
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ungranted.isEmpty()) {
|
||||||
|
request.onSuccess()
|
||||||
|
} else {
|
||||||
|
val requestCode = Random.nextInt(256, 512)
|
||||||
|
resultCallbacks[requestCode] = { result, _ ->
|
||||||
|
if (result > 0)
|
||||||
|
request.onSuccess()
|
||||||
|
else
|
||||||
|
request.onFailure()
|
||||||
|
}
|
||||||
|
ActivityCompat.requestPermissions(this, ungranted.toTypedArray(), requestCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun withExternalRW(builder: PermissionRequestBuilder.() -> Unit) {
|
||||||
|
withPermissions(Manifest.permission.WRITE_EXTERNAL_STORAGE, builder = builder)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRequestPermissionsResult(
|
||||||
|
requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
|
||||||
|
var success = true
|
||||||
|
for (res in grantResults) {
|
||||||
|
if (res != PackageManager.PERMISSION_GRANTED) {
|
||||||
|
success = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resultCallbacks[requestCode]?.apply {
|
||||||
|
resultCallbacks.remove(requestCode)
|
||||||
|
invoke(this@BaseActivity, if (success) 1 else -1, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
|
super.onActivityResult(requestCode, resultCode, data)
|
||||||
|
resultCallbacks[requestCode]?.apply {
|
||||||
|
resultCallbacks.remove(requestCode)
|
||||||
|
invoke(this@BaseActivity, resultCode, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun startActivityForResult(intent: Intent, requestCode: Int, listener: RequestCallback) {
|
||||||
|
resultCallbacks[requestCode] = listener
|
||||||
|
startActivityForResult(intent, requestCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
50
app/src/main/java/com/topjohnwu/magisk/base/BaseFragment.kt
Normal file
50
app/src/main/java/com/topjohnwu/magisk/base/BaseFragment.kt
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
package com.topjohnwu.magisk.base
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.annotation.CallSuper
|
||||||
|
import androidx.databinding.DataBindingUtil
|
||||||
|
import androidx.databinding.ViewDataBinding
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import com.topjohnwu.magisk.BR
|
||||||
|
import com.topjohnwu.magisk.base.viewmodel.BaseViewModel
|
||||||
|
import com.topjohnwu.magisk.model.events.EventHandler
|
||||||
|
import com.topjohnwu.magisk.model.events.ViewEvent
|
||||||
|
|
||||||
|
abstract class BaseFragment<ViewModel : BaseViewModel, Binding : ViewDataBinding> :
|
||||||
|
Fragment(), EventHandler {
|
||||||
|
|
||||||
|
protected val activity get() = requireActivity() as BaseActivity<*, *>
|
||||||
|
protected lateinit var binding: Binding
|
||||||
|
protected abstract val layoutRes: Int
|
||||||
|
protected abstract val viewModel: ViewModel
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
viewModel.viewEvents.observe(this, viewEventObserver)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View? {
|
||||||
|
binding = DataBindingUtil.inflate<Binding>(inflater, layoutRes, container, false).apply {
|
||||||
|
setVariable(BR.viewModel, viewModel)
|
||||||
|
lifecycleOwner = this@BaseFragment
|
||||||
|
}
|
||||||
|
|
||||||
|
return binding.root
|
||||||
|
}
|
||||||
|
|
||||||
|
@CallSuper
|
||||||
|
override fun onEventDispatched(event: ViewEvent) {
|
||||||
|
super.onEventDispatched(event)
|
||||||
|
activity.onEventDispatched(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun onBackPressed(): Boolean = false
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,56 @@
|
|||||||
|
package com.topjohnwu.magisk.base
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.preference.*
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import org.koin.android.ext.android.inject
|
||||||
|
|
||||||
|
abstract class BasePreferenceFragment : PreferenceFragmentCompat(),
|
||||||
|
SharedPreferences.OnSharedPreferenceChangeListener {
|
||||||
|
|
||||||
|
protected val prefs: SharedPreferences by inject()
|
||||||
|
protected val activity get() = requireActivity() as BaseActivity<*, *>
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View? {
|
||||||
|
val v = super.onCreateView(inflater, container, savedInstanceState)
|
||||||
|
prefs.registerOnSharedPreferenceChangeListener(this)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
prefs.unregisterOnSharedPreferenceChangeListener(this)
|
||||||
|
super.onDestroyView()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setAllPreferencesToAvoidHavingExtraSpace(preference: Preference) {
|
||||||
|
preference.isIconSpaceReserved = false
|
||||||
|
if (preference is PreferenceGroup)
|
||||||
|
for (i in 0 until preference.preferenceCount)
|
||||||
|
setAllPreferencesToAvoidHavingExtraSpace(preference.getPreference(i))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setPreferenceScreen(preferenceScreen: PreferenceScreen?) {
|
||||||
|
if (preferenceScreen != null)
|
||||||
|
setAllPreferencesToAvoidHavingExtraSpace(preferenceScreen)
|
||||||
|
super.setPreferenceScreen(preferenceScreen)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateAdapter(preferenceScreen: PreferenceScreen?): RecyclerView.Adapter<*> =
|
||||||
|
object : PreferenceGroupAdapter(preferenceScreen) {
|
||||||
|
@SuppressLint("RestrictedApi")
|
||||||
|
override fun onPreferenceHierarchyChange(preference: Preference?) {
|
||||||
|
if (preference != null)
|
||||||
|
setAllPreferencesToAvoidHavingExtraSpace(preference)
|
||||||
|
super.onPreferenceHierarchyChange(preference)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
17
app/src/main/java/com/topjohnwu/magisk/base/BaseReceiver.kt
Normal file
17
app/src/main/java/com/topjohnwu/magisk/base/BaseReceiver.kt
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package com.topjohnwu.magisk.base
|
||||||
|
|
||||||
|
import android.content.BroadcastReceiver
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.ContextWrapper
|
||||||
|
import android.content.Intent
|
||||||
|
import com.topjohnwu.magisk.utils.wrap
|
||||||
|
import org.koin.core.KoinComponent
|
||||||
|
|
||||||
|
abstract class BaseReceiver : BroadcastReceiver(), KoinComponent {
|
||||||
|
|
||||||
|
final override fun onReceive(context: Context, intent: Intent?) {
|
||||||
|
onReceive(context.wrap() as ContextWrapper, intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract fun onReceive(context: ContextWrapper, intent: Intent?)
|
||||||
|
}
|
12
app/src/main/java/com/topjohnwu/magisk/base/BaseService.kt
Normal file
12
app/src/main/java/com/topjohnwu/magisk/base/BaseService.kt
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
package com.topjohnwu.magisk.base
|
||||||
|
|
||||||
|
import android.app.Service
|
||||||
|
import android.content.Context
|
||||||
|
import com.topjohnwu.magisk.utils.wrap
|
||||||
|
import org.koin.core.KoinComponent
|
||||||
|
|
||||||
|
abstract class BaseService : Service(), KoinComponent {
|
||||||
|
override fun attachBaseContext(base: Context) {
|
||||||
|
super.attachBaseContext(base.wrap())
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package com.topjohnwu.magisk.model.worker
|
package com.topjohnwu.magisk.base
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.Network
|
import android.net.Network
|
@ -1,24 +1,23 @@
|
|||||||
package com.topjohnwu.magisk.ui.base
|
package com.topjohnwu.magisk.base.viewmodel
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import com.github.pwittchen.reactivenetwork.library.rx2.ReactiveNetwork
|
import com.github.pwittchen.reactivenetwork.library.rx2.ReactiveNetwork
|
||||||
import com.skoumal.teanity.extensions.doOnSubscribeUi
|
import com.topjohnwu.magisk.extensions.doOnSubscribeUi
|
||||||
import com.skoumal.teanity.extensions.subscribeK
|
|
||||||
import com.skoumal.teanity.util.KObservableField
|
|
||||||
import com.skoumal.teanity.viewmodel.LoadingViewModel
|
|
||||||
import com.topjohnwu.magisk.extensions.get
|
import com.topjohnwu.magisk.extensions.get
|
||||||
|
import com.topjohnwu.magisk.extensions.subscribeK
|
||||||
import com.topjohnwu.magisk.model.events.BackPressEvent
|
import com.topjohnwu.magisk.model.events.BackPressEvent
|
||||||
import com.topjohnwu.magisk.model.events.PermissionEvent
|
import com.topjohnwu.magisk.model.events.PermissionEvent
|
||||||
import com.topjohnwu.magisk.model.events.ViewActionEvent
|
import com.topjohnwu.magisk.model.events.ViewActionEvent
|
||||||
|
import com.topjohnwu.magisk.utils.KObservableField
|
||||||
import io.reactivex.Observable
|
import io.reactivex.Observable
|
||||||
import io.reactivex.subjects.PublishSubject
|
import io.reactivex.subjects.PublishSubject
|
||||||
|
|
||||||
|
|
||||||
abstract class MagiskViewModel(
|
abstract class BaseViewModel(
|
||||||
initialState: State = State.LOADING
|
initialState: State = State.LOADING
|
||||||
) : LoadingViewModel(initialState) {
|
) : LoadingViewModel(initialState) {
|
||||||
|
|
||||||
val isConnected = KObservableField(true)
|
val isConnected = KObservableField(false)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
ReactiveNetwork.observeNetworkConnectivity(get())
|
ReactiveNetwork.observeNetworkConnectivity(get())
|
@ -0,0 +1,78 @@
|
|||||||
|
package com.topjohnwu.magisk.base.viewmodel
|
||||||
|
|
||||||
|
import androidx.databinding.Bindable
|
||||||
|
import com.topjohnwu.magisk.BR
|
||||||
|
import io.reactivex.*
|
||||||
|
|
||||||
|
abstract class LoadingViewModel(defaultState: State = State.LOADING) :
|
||||||
|
StatefulViewModel<LoadingViewModel.State>(defaultState) {
|
||||||
|
|
||||||
|
val loading @Bindable get() = state == State.LOADING
|
||||||
|
val loaded @Bindable get() = state == State.LOADED
|
||||||
|
val loadingFailed @Bindable get() = state == State.LOADING_FAILED
|
||||||
|
|
||||||
|
@Deprecated(
|
||||||
|
"Direct access is recommended since 0.2. This access method will be removed in 1.0",
|
||||||
|
ReplaceWith("state = State.LOADING", "com.topjohnwu.magisk.base.viewmodel.LoadingViewModel.State"),
|
||||||
|
DeprecationLevel.WARNING
|
||||||
|
)
|
||||||
|
fun setLoading() {
|
||||||
|
state = State.LOADING
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated(
|
||||||
|
"Direct access is recommended since 0.2. This access method will be removed in 1.0",
|
||||||
|
ReplaceWith("state = State.LOADED", "com.topjohnwu.magisk.base.viewmodel.LoadingViewModel.State"),
|
||||||
|
DeprecationLevel.WARNING
|
||||||
|
)
|
||||||
|
fun setLoaded() {
|
||||||
|
state = State.LOADED
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated(
|
||||||
|
"Direct access is recommended since 0.2. This access method will be removed in 1.0",
|
||||||
|
ReplaceWith("state = State.LOADING_FAILED", "com.topjohnwu.magisk.base.viewmodel.LoadingViewModel.State"),
|
||||||
|
DeprecationLevel.WARNING
|
||||||
|
)
|
||||||
|
fun setLoadingFailed() {
|
||||||
|
state = State.LOADING_FAILED
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun notifyStateChanged() {
|
||||||
|
notifyPropertyChanged(BR.loading)
|
||||||
|
notifyPropertyChanged(BR.loaded)
|
||||||
|
notifyPropertyChanged(BR.loadingFailed)
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class State {
|
||||||
|
LOADED, LOADING, LOADING_FAILED
|
||||||
|
}
|
||||||
|
|
||||||
|
//region Rx
|
||||||
|
protected fun <T> Observable<T>.applyViewModel(viewModel: LoadingViewModel, 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: LoadingViewModel, 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: LoadingViewModel, 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: LoadingViewModel, 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: LoadingViewModel, allowFinishing: Boolean = true) =
|
||||||
|
doOnSubscribe { viewModel.state = State.LOADING }
|
||||||
|
.doOnError { viewModel.state = State.LOADING_FAILED }
|
||||||
|
.doOnComplete { if (allowFinishing) viewModel.state = State.LOADED }
|
||||||
|
//endregion
|
||||||
|
}
|
@ -0,0 +1,46 @@
|
|||||||
|
package com.topjohnwu.magisk.base.viewmodel
|
||||||
|
|
||||||
|
import androidx.databinding.Observable
|
||||||
|
import androidx.databinding.PropertyChangeRegistry
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copy of [android.databinding.BaseObservable] which extends [ViewModel]
|
||||||
|
*/
|
||||||
|
abstract class ObservableViewModel : TeanityViewModel(), Observable {
|
||||||
|
|
||||||
|
@Transient
|
||||||
|
private var callbacks: PropertyChangeRegistry? = null
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
override fun addOnPropertyChangedCallback(callback: Observable.OnPropertyChangedCallback) {
|
||||||
|
if (callbacks == null) {
|
||||||
|
callbacks = PropertyChangeRegistry()
|
||||||
|
}
|
||||||
|
callbacks?.add(callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
override fun removeOnPropertyChangedCallback(callback: Observable.OnPropertyChangedCallback) {
|
||||||
|
callbacks?.remove(callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifies listeners that all properties of this instance have changed.
|
||||||
|
*/
|
||||||
|
@Synchronized
|
||||||
|
fun notifyChange() {
|
||||||
|
callbacks?.notifyCallbacks(this, 0, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifies listeners that a specific property has changed. The getter for the property
|
||||||
|
* that changes should be marked with [android.databinding.Bindable] to generate a field in
|
||||||
|
* `BR` to be used as `fieldId`.
|
||||||
|
*
|
||||||
|
* @param fieldId The generated BR id for the Bindable field.
|
||||||
|
*/
|
||||||
|
fun notifyPropertyChanged(fieldId: Int) {
|
||||||
|
callbacks?.notifyCallbacks(this, fieldId, null)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
package com.topjohnwu.magisk.base.viewmodel
|
||||||
|
|
||||||
|
abstract class StatefulViewModel<State : Enum<*>>(
|
||||||
|
val defaultState: State
|
||||||
|
) : ObservableViewModel() {
|
||||||
|
|
||||||
|
var state: State = defaultState
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
notifyStateChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun notifyStateChanged() = Unit
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
package com.topjohnwu.magisk.base.viewmodel
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import com.topjohnwu.magisk.model.events.SimpleViewEvent
|
||||||
|
import com.topjohnwu.magisk.model.events.ViewEvent
|
||||||
|
import io.reactivex.disposables.CompositeDisposable
|
||||||
|
import io.reactivex.disposables.Disposable
|
||||||
|
|
||||||
|
abstract class TeanityViewModel : ViewModel() {
|
||||||
|
|
||||||
|
private val disposables = CompositeDisposable()
|
||||||
|
private val _viewEvents = MutableLiveData<ViewEvent>()
|
||||||
|
val viewEvents: LiveData<ViewEvent> get() = _viewEvents
|
||||||
|
|
||||||
|
override fun onCleared() {
|
||||||
|
super.onCleared()
|
||||||
|
disposables.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <Event : ViewEvent> Event.publish() {
|
||||||
|
_viewEvents.value = this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Int.publish() {
|
||||||
|
_viewEvents.value = SimpleViewEvent(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Disposable.add() {
|
||||||
|
disposables.add(this)
|
||||||
|
}
|
||||||
|
}
|
@ -19,10 +19,10 @@ interface GithubRawServices {
|
|||||||
@GET("$MAGISK_FILES/master/beta.json")
|
@GET("$MAGISK_FILES/master/beta.json")
|
||||||
fun fetchBetaUpdate(): Single<UpdateInfo>
|
fun fetchBetaUpdate(): Single<UpdateInfo>
|
||||||
|
|
||||||
@GET("$MAGISK_FILES/master/canary_builds/release.json")
|
@GET("$MAGISK_FILES/canary/release.json")
|
||||||
fun fetchCanaryUpdate(): Single<UpdateInfo>
|
fun fetchCanaryUpdate(): Single<UpdateInfo>
|
||||||
|
|
||||||
@GET("$MAGISK_FILES/master/canary_builds/canary.json")
|
@GET("$MAGISK_FILES/canary/debug.json")
|
||||||
fun fetchCanaryDebugUpdate(): Single<UpdateInfo>
|
fun fetchCanaryDebugUpdate(): Single<UpdateInfo>
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
|
@ -0,0 +1,26 @@
|
|||||||
|
package com.topjohnwu.magisk.databinding
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
|
import androidx.core.view.isGone
|
||||||
|
import androidx.core.view.isInvisible
|
||||||
|
import androidx.databinding.BindingAdapter
|
||||||
|
|
||||||
|
@BindingAdapter("gone")
|
||||||
|
fun setGone(view: View, gone: Boolean) {
|
||||||
|
view.isGone = gone
|
||||||
|
}
|
||||||
|
|
||||||
|
@BindingAdapter("invisible")
|
||||||
|
fun setInvisible(view: View, invisible: Boolean) {
|
||||||
|
view.isInvisible = invisible
|
||||||
|
}
|
||||||
|
|
||||||
|
@BindingAdapter("goneUnless")
|
||||||
|
fun setGoneUnless(view: View, goneUnless: Boolean) {
|
||||||
|
setGone(view, goneUnless.not())
|
||||||
|
}
|
||||||
|
|
||||||
|
@BindingAdapter("invisibleUnless")
|
||||||
|
fun setInvisibleUnless(view: View, invisibleUnless: Boolean) {
|
||||||
|
setInvisible(view, invisibleUnless.not())
|
||||||
|
}
|
@ -0,0 +1,57 @@
|
|||||||
|
package com.topjohnwu.magisk.databinding
|
||||||
|
|
||||||
|
import android.graphics.drawable.GradientDrawable
|
||||||
|
import android.graphics.drawable.InsetDrawable
|
||||||
|
import androidx.databinding.BindingAdapter
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.topjohnwu.magisk.extensions.startEndToLeftRight
|
||||||
|
import com.topjohnwu.magisk.extensions.toPx
|
||||||
|
import com.topjohnwu.magisk.utils.KItemDecoration
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
|
@BindingAdapter(
|
||||||
|
"dividerColor",
|
||||||
|
"dividerHorizontal",
|
||||||
|
"dividerSize",
|
||||||
|
"dividerAfterLast",
|
||||||
|
"dividerMarginStart",
|
||||||
|
"dividerMarginEnd",
|
||||||
|
"dividerMarginTop",
|
||||||
|
"dividerMarginBottom",
|
||||||
|
requireAll = false
|
||||||
|
)
|
||||||
|
fun setDivider(
|
||||||
|
view: RecyclerView,
|
||||||
|
color: Int,
|
||||||
|
horizontal: Boolean,
|
||||||
|
_size: Float,
|
||||||
|
_afterLast: Boolean?,
|
||||||
|
marginStartF: Float,
|
||||||
|
marginEndF: Float,
|
||||||
|
marginTopF: Float,
|
||||||
|
marginBottomF: Float
|
||||||
|
) {
|
||||||
|
val orientation = if (horizontal) RecyclerView.HORIZONTAL else RecyclerView.VERTICAL
|
||||||
|
val size = if (_size > 0) _size.roundToInt() else 1.toPx()
|
||||||
|
val (width, height) = if (horizontal) size to 1 else 1 to size
|
||||||
|
val afterLast = _afterLast ?: true
|
||||||
|
|
||||||
|
val marginStart = marginStartF.roundToInt()
|
||||||
|
val marginEnd = marginEndF.roundToInt()
|
||||||
|
val marginTop = marginTopF.roundToInt()
|
||||||
|
val marginBottom = marginBottomF.roundToInt()
|
||||||
|
val (marginLeft, marginRight) = view.context.startEndToLeftRight(marginStart, marginEnd)
|
||||||
|
|
||||||
|
val drawable = GradientDrawable().apply {
|
||||||
|
setSize(width, height)
|
||||||
|
shape = GradientDrawable.RECTANGLE
|
||||||
|
setColor(color)
|
||||||
|
}.let {
|
||||||
|
InsetDrawable(it, marginLeft, marginTop, marginRight, marginBottom)
|
||||||
|
}
|
||||||
|
|
||||||
|
val decoration = KItemDecoration(view.context, orientation)
|
||||||
|
.setDeco(drawable)
|
||||||
|
.apply { showAfterLast = afterLast }
|
||||||
|
view.addItemDecoration(decoration)
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
package com.topjohnwu.magisk.databinding
|
||||||
|
|
||||||
|
import androidx.databinding.ViewDataBinding
|
||||||
|
import me.tatarka.bindingcollectionadapter2.BindingRecyclerViewAdapter
|
||||||
|
|
||||||
|
open class BindingBoundAdapter : BindingRecyclerViewAdapter<RvItem>() {
|
||||||
|
|
||||||
|
override fun onBindBinding(binding: ViewDataBinding, variableId: Int, layoutRes: Int, position: Int, item: RvItem) {
|
||||||
|
super.onBindBinding(binding, variableId, layoutRes, position, item)
|
||||||
|
|
||||||
|
item.onBindingBound(binding)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,48 @@
|
|||||||
|
package com.topjohnwu.magisk.databinding
|
||||||
|
|
||||||
|
import androidx.annotation.CallSuper
|
||||||
|
import androidx.databinding.ViewDataBinding
|
||||||
|
import com.topjohnwu.magisk.BR
|
||||||
|
import com.topjohnwu.magisk.utils.DiffObservableList
|
||||||
|
import me.tatarka.bindingcollectionadapter2.ItemBinding
|
||||||
|
|
||||||
|
abstract class RvItem {
|
||||||
|
|
||||||
|
abstract val layoutRes: Int
|
||||||
|
|
||||||
|
@CallSuper
|
||||||
|
open fun bind(binding: ItemBinding<*>) {
|
||||||
|
binding.set(BR.item, layoutRes)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This callback is useful if you want to manipulate your views directly.
|
||||||
|
* If you want to use this callback, you must set [me.tatarka.bindingcollectionadapter2.BindingRecyclerViewAdapter]
|
||||||
|
* on your RecyclerView and call it from there. You can use [BindingBoundAdapter] for your convenience.
|
||||||
|
*/
|
||||||
|
open fun onBindingBound(binding: ViewDataBinding) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class ComparableRvItem<in T> : RvItem() {
|
||||||
|
|
||||||
|
abstract fun itemSameAs(other: T): Boolean
|
||||||
|
abstract fun contentSameAs(other: T): Boolean
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
open fun genericItemSameAs(other: Any): Boolean = other::class == this::class && itemSameAs(other as T)
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
open fun genericContentSameAs(other: Any): Boolean = other::class == this::class && contentSameAs(other as T)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val callback = object : DiffObservableList.Callback<ComparableRvItem<*>> {
|
||||||
|
override fun areItemsTheSame(
|
||||||
|
oldItem: ComparableRvItem<*>,
|
||||||
|
newItem: ComparableRvItem<*>
|
||||||
|
) = oldItem.genericItemSameAs(newItem)
|
||||||
|
|
||||||
|
override fun areContentsTheSame(
|
||||||
|
oldItem: ComparableRvItem<*>,
|
||||||
|
newItem: ComparableRvItem<*>
|
||||||
|
) = oldItem.genericContentSameAs(newItem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -7,7 +7,7 @@ import android.content.Context
|
|||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import com.skoumal.teanity.rxbus.RxBus
|
import com.topjohnwu.magisk.utils.RxBus
|
||||||
import org.koin.core.qualifier.named
|
import org.koin.core.qualifier.named
|
||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
|
|
||||||
|
@ -1,11 +1,18 @@
|
|||||||
package com.topjohnwu.magisk.di
|
package com.topjohnwu.magisk.di
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import com.squareup.moshi.JsonAdapter
|
import com.squareup.moshi.JsonAdapter
|
||||||
import com.squareup.moshi.Moshi
|
import com.squareup.moshi.Moshi
|
||||||
import com.topjohnwu.magisk.BuildConfig
|
import com.topjohnwu.magisk.BuildConfig
|
||||||
import com.topjohnwu.magisk.Const
|
import com.topjohnwu.magisk.Const
|
||||||
import com.topjohnwu.magisk.data.network.GithubApiServices
|
import com.topjohnwu.magisk.data.network.GithubApiServices
|
||||||
import com.topjohnwu.magisk.data.network.GithubRawServices
|
import com.topjohnwu.magisk.data.network.GithubRawServices
|
||||||
|
import com.topjohnwu.magisk.net.Networking
|
||||||
|
import com.topjohnwu.magisk.net.NoSSLv3SocketFactory
|
||||||
|
import io.noties.markwon.Markwon
|
||||||
|
import io.noties.markwon.html.HtmlPlugin
|
||||||
|
import io.noties.markwon.image.ImagesPlugin
|
||||||
|
import io.noties.markwon.image.network.OkHttpNetworkSchemeHandler
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.logging.HttpLoggingInterceptor
|
import okhttp3.logging.HttpLoggingInterceptor
|
||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
@ -16,14 +23,15 @@ import retrofit2.converter.scalars.ScalarsConverterFactory
|
|||||||
import se.ansman.kotshi.KotshiJsonAdapterFactory
|
import se.ansman.kotshi.KotshiJsonAdapterFactory
|
||||||
|
|
||||||
val networkingModule = module {
|
val networkingModule = module {
|
||||||
single { createOkHttpClient() }
|
single { createOkHttpClient(get()) }
|
||||||
single { createMoshiConverterFactory() }
|
single { createRetrofit(get()) }
|
||||||
single { createRetrofit(get(), get()) }
|
|
||||||
single { createApiService<GithubRawServices>(get(), Const.Url.GITHUB_RAW_URL) }
|
single { createApiService<GithubRawServices>(get(), Const.Url.GITHUB_RAW_URL) }
|
||||||
single { createApiService<GithubApiServices>(get(), Const.Url.GITHUB_API_URL) }
|
single { createApiService<GithubApiServices>(get(), Const.Url.GITHUB_API_URL) }
|
||||||
|
single { createMarkwon(get(), get()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createOkHttpClient(): OkHttpClient {
|
@Suppress("DEPRECATION")
|
||||||
|
fun createOkHttpClient(context: Context): OkHttpClient {
|
||||||
val builder = OkHttpClient.Builder()
|
val builder = OkHttpClient.Builder()
|
||||||
|
|
||||||
if (BuildConfig.DEBUG) {
|
if (BuildConfig.DEBUG) {
|
||||||
@ -33,6 +41,10 @@ fun createOkHttpClient(): OkHttpClient {
|
|||||||
builder.addInterceptor(httpLoggingInterceptor)
|
builder.addInterceptor(httpLoggingInterceptor)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!Networking.init(context)) {
|
||||||
|
builder.sslSocketFactory(NoSSLv3SocketFactory())
|
||||||
|
}
|
||||||
|
|
||||||
return builder.build()
|
return builder.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -43,13 +55,10 @@ fun createMoshiConverterFactory(): MoshiConverterFactory {
|
|||||||
return MoshiConverterFactory.create(moshi)
|
return MoshiConverterFactory.create(moshi)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createRetrofit(
|
fun createRetrofit(okHttpClient: OkHttpClient): Retrofit.Builder {
|
||||||
okHttpClient: OkHttpClient,
|
|
||||||
converterFactory: MoshiConverterFactory
|
|
||||||
): Retrofit.Builder {
|
|
||||||
return Retrofit.Builder()
|
return Retrofit.Builder()
|
||||||
.addConverterFactory(ScalarsConverterFactory.create())
|
.addConverterFactory(ScalarsConverterFactory.create())
|
||||||
.addConverterFactory(converterFactory)
|
.addConverterFactory(createMoshiConverterFactory())
|
||||||
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
|
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
|
||||||
.client(okHttpClient)
|
.client(okHttpClient)
|
||||||
}
|
}
|
||||||
@ -62,4 +71,13 @@ inline fun <reified T> createApiService(retrofitBuilder: Retrofit.Builder, baseU
|
|||||||
.baseUrl(baseUrl)
|
.baseUrl(baseUrl)
|
||||||
.build()
|
.build()
|
||||||
.create(T::class.java)
|
.create(T::class.java)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun createMarkwon(context: Context, okHttpClient: OkHttpClient): Markwon {
|
||||||
|
return Markwon.builder(context)
|
||||||
|
.usePlugin(HtmlPlugin.create())
|
||||||
|
.usePlugin(ImagesPlugin.create {
|
||||||
|
it.addSchemeHandler(OkHttpNetworkSchemeHandler.create(okHttpClient))
|
||||||
|
})
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
@ -0,0 +1,57 @@
|
|||||||
|
package com.topjohnwu.magisk.extensions
|
||||||
|
|
||||||
|
import androidx.databinding.Observable
|
||||||
|
import androidx.databinding.ObservableBoolean
|
||||||
|
import androidx.databinding.ObservableField
|
||||||
|
import androidx.databinding.ObservableInt
|
||||||
|
|
||||||
|
fun <T> ObservableField<T>.addOnPropertyChangedCallback(
|
||||||
|
removeAfterChanged: Boolean = false,
|
||||||
|
callback: (T?) -> Unit
|
||||||
|
) {
|
||||||
|
addOnPropertyChangedCallback(object : Observable.OnPropertyChangedCallback() {
|
||||||
|
override fun onPropertyChanged(sender: Observable?, propertyId: Int) {
|
||||||
|
callback(get())
|
||||||
|
if (removeAfterChanged) removeOnPropertyChangedCallback(this)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ObservableInt.addOnPropertyChangedCallback(
|
||||||
|
removeAfterChanged: Boolean = false,
|
||||||
|
callback: (Int) -> Unit
|
||||||
|
) {
|
||||||
|
addOnPropertyChangedCallback(object : Observable.OnPropertyChangedCallback() {
|
||||||
|
override fun onPropertyChanged(sender: Observable?, propertyId: Int) {
|
||||||
|
callback(get())
|
||||||
|
if (removeAfterChanged) removeOnPropertyChangedCallback(this)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ObservableBoolean.addOnPropertyChangedCallback(
|
||||||
|
removeAfterChanged: Boolean = false,
|
||||||
|
callback: (Boolean) -> Unit
|
||||||
|
) {
|
||||||
|
addOnPropertyChangedCallback(object : Observable.OnPropertyChangedCallback() {
|
||||||
|
override fun onPropertyChanged(sender: Observable?, propertyId: Int) {
|
||||||
|
callback(get())
|
||||||
|
if (removeAfterChanged) removeOnPropertyChangedCallback(this)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <T> ObservableField<T>.update(block: (T?) -> Unit) {
|
||||||
|
set(get().apply(block))
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <T> ObservableField<T>.updateNonNull(block: (T) -> Unit) {
|
||||||
|
update {
|
||||||
|
it ?: return@update
|
||||||
|
block(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun ObservableInt.update(block: (Int) -> Unit) {
|
||||||
|
set(get().apply(block))
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
package com.topjohnwu.magisk.extensions
|
||||||
|
|
||||||
|
import android.content.res.Resources
|
||||||
|
import kotlin.math.ceil
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
|
fun Int.toDp(): Int = ceil(this / Resources.getSystem().displayMetrics.density).roundToInt()
|
||||||
|
|
||||||
|
fun Int.toPx(): Int = (this * Resources.getSystem().displayMetrics.density).roundToInt()
|
@ -0,0 +1,6 @@
|
|||||||
|
package com.topjohnwu.magisk.extensions
|
||||||
|
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
|
|
||||||
|
fun ui(body: () -> Unit) = Handler(Looper.getMainLooper()).post(body)
|
201
app/src/main/java/com/topjohnwu/magisk/extensions/RxJava.kt
Normal file
201
app/src/main/java/com/topjohnwu/magisk/extensions/RxJava.kt
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
package com.topjohnwu.magisk.extensions
|
||||||
|
|
||||||
|
import androidx.databinding.ObservableField
|
||||||
|
import com.topjohnwu.magisk.utils.KObservableField
|
||||||
|
import io.reactivex.*
|
||||||
|
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||||
|
import io.reactivex.disposables.Disposables
|
||||||
|
import io.reactivex.functions.BiFunction
|
||||||
|
import io.reactivex.schedulers.Schedulers
|
||||||
|
import androidx.databinding.Observable as BindingObservable
|
||||||
|
|
||||||
|
fun <T> Observable<T>.applySchedulers(
|
||||||
|
subscribeOn: Scheduler = Schedulers.io(),
|
||||||
|
observeOn: Scheduler = AndroidSchedulers.mainThread()
|
||||||
|
): Observable<T> = this.subscribeOn(subscribeOn).observeOn(observeOn)
|
||||||
|
|
||||||
|
fun <T> Flowable<T>.applySchedulers(
|
||||||
|
subscribeOn: Scheduler = Schedulers.io(),
|
||||||
|
observeOn: Scheduler = AndroidSchedulers.mainThread()
|
||||||
|
): Flowable<T> = this.subscribeOn(subscribeOn).observeOn(observeOn)
|
||||||
|
|
||||||
|
fun <T> Single<T>.applySchedulers(
|
||||||
|
subscribeOn: Scheduler = Schedulers.io(),
|
||||||
|
observeOn: Scheduler = AndroidSchedulers.mainThread()
|
||||||
|
): Single<T> = this.subscribeOn(subscribeOn).observeOn(observeOn)
|
||||||
|
|
||||||
|
fun <T> Maybe<T>.applySchedulers(
|
||||||
|
subscribeOn: Scheduler = Schedulers.io(),
|
||||||
|
observeOn: Scheduler = AndroidSchedulers.mainThread()
|
||||||
|
): Maybe<T> = this.subscribeOn(subscribeOn).observeOn(observeOn)
|
||||||
|
|
||||||
|
fun Completable.applySchedulers(
|
||||||
|
subscribeOn: Scheduler = Schedulers.io(),
|
||||||
|
observeOn: Scheduler = AndroidSchedulers.mainThread()
|
||||||
|
): Completable = this.subscribeOn(subscribeOn).observeOn(observeOn)
|
||||||
|
|
||||||
|
/*=== ALIASES FOR OBSERVABLES ===*/
|
||||||
|
|
||||||
|
typealias OnCompleteListener = () -> Unit
|
||||||
|
typealias OnSuccessListener<T> = (T) -> Unit
|
||||||
|
typealias OnErrorListener = (Throwable) -> Unit
|
||||||
|
|
||||||
|
/*=== ALIASES FOR OBSERVABLES ===*/
|
||||||
|
|
||||||
|
fun <T> Observable<T>.subscribeK(
|
||||||
|
onError: OnErrorListener = { it.printStackTrace() },
|
||||||
|
onComplete: OnCompleteListener = {},
|
||||||
|
onNext: OnSuccessListener<T> = {}
|
||||||
|
) = applySchedulers()
|
||||||
|
.subscribe(onNext, onError, onComplete)
|
||||||
|
|
||||||
|
fun <T> Single<T>.subscribeK(
|
||||||
|
onError: OnErrorListener = { it.printStackTrace() },
|
||||||
|
onNext: OnSuccessListener<T> = {}
|
||||||
|
) = applySchedulers()
|
||||||
|
.subscribe(onNext, onError)
|
||||||
|
|
||||||
|
fun <T> Maybe<T>.subscribeK(
|
||||||
|
onError: OnErrorListener = { it.printStackTrace() },
|
||||||
|
onComplete: OnCompleteListener = {},
|
||||||
|
onNext: OnSuccessListener<T> = {}
|
||||||
|
) = applySchedulers()
|
||||||
|
.subscribe(onNext, onError, onComplete)
|
||||||
|
|
||||||
|
fun <T> Flowable<T>.subscribeK(
|
||||||
|
onError: OnErrorListener = { it.printStackTrace() },
|
||||||
|
onComplete: OnCompleteListener = {},
|
||||||
|
onNext: OnSuccessListener<T> = {}
|
||||||
|
) = applySchedulers()
|
||||||
|
.subscribe(onNext, onError, onComplete)
|
||||||
|
|
||||||
|
fun Completable.subscribeK(
|
||||||
|
onError: OnErrorListener = { it.printStackTrace() },
|
||||||
|
onComplete: OnCompleteListener = {}
|
||||||
|
) = applySchedulers()
|
||||||
|
.subscribe(onComplete, onError)
|
||||||
|
|
||||||
|
|
||||||
|
fun <T> Observable<out T>.updateBy(
|
||||||
|
field: KObservableField<T?>
|
||||||
|
) = doOnNextUi { field.value = it }
|
||||||
|
.doOnErrorUi { field.value = null }
|
||||||
|
|
||||||
|
fun <T> Single<out T>.updateBy(
|
||||||
|
field: KObservableField<T?>
|
||||||
|
) = doOnSuccessUi { field.value = it }
|
||||||
|
.doOnErrorUi { field.value = null }
|
||||||
|
|
||||||
|
fun <T> Maybe<out T>.updateBy(
|
||||||
|
field: KObservableField<T?>
|
||||||
|
) = doOnSuccessUi { field.value = it }
|
||||||
|
.doOnErrorUi { field.value = null }
|
||||||
|
.doOnComplete { field.value = field.value }
|
||||||
|
|
||||||
|
fun <T> Flowable<out T>.updateBy(
|
||||||
|
field: KObservableField<T?>
|
||||||
|
) = doOnNextUi { field.value = it }
|
||||||
|
.doOnErrorUi { field.value = null }
|
||||||
|
|
||||||
|
fun Completable.updateBy(
|
||||||
|
field: KObservableField<Boolean>
|
||||||
|
) = doOnCompleteUi { field.value = true }
|
||||||
|
.doOnErrorUi { field.value = false }
|
||||||
|
|
||||||
|
|
||||||
|
fun <T> Observable<T>.doOnSubscribeUi(body: () -> Unit) =
|
||||||
|
doOnSubscribe { ui { body() } }
|
||||||
|
|
||||||
|
fun <T> Single<T>.doOnSubscribeUi(body: () -> Unit) =
|
||||||
|
doOnSubscribe { ui { body() } }
|
||||||
|
|
||||||
|
fun <T> Maybe<T>.doOnSubscribeUi(body: () -> Unit) =
|
||||||
|
doOnSubscribe { ui { body() } }
|
||||||
|
|
||||||
|
fun <T> Flowable<T>.doOnSubscribeUi(body: () -> Unit) =
|
||||||
|
doOnSubscribe { ui { body() } }
|
||||||
|
|
||||||
|
fun Completable.doOnSubscribeUi(body: () -> Unit) =
|
||||||
|
doOnSubscribe { ui { body() } }
|
||||||
|
|
||||||
|
|
||||||
|
fun <T> Observable<T>.doOnErrorUi(body: (Throwable) -> Unit) =
|
||||||
|
doOnError { ui { body(it) } }
|
||||||
|
|
||||||
|
fun <T> Single<T>.doOnErrorUi(body: (Throwable) -> Unit) =
|
||||||
|
doOnError { ui { body(it) } }
|
||||||
|
|
||||||
|
fun <T> Maybe<T>.doOnErrorUi(body: (Throwable) -> Unit) =
|
||||||
|
doOnError { ui { body(it) } }
|
||||||
|
|
||||||
|
fun <T> Flowable<T>.doOnErrorUi(body: (Throwable) -> Unit) =
|
||||||
|
doOnError { ui { body(it) } }
|
||||||
|
|
||||||
|
fun Completable.doOnErrorUi(body: (Throwable) -> Unit) =
|
||||||
|
doOnError { ui { body(it) } }
|
||||||
|
|
||||||
|
|
||||||
|
fun <T> Observable<T>.doOnNextUi(body: (T) -> Unit) =
|
||||||
|
doOnNext { ui { body(it) } }
|
||||||
|
|
||||||
|
fun <T> Flowable<T>.doOnNextUi(body: (T) -> Unit) =
|
||||||
|
doOnNext { ui { body(it) } }
|
||||||
|
|
||||||
|
fun <T> Single<T>.doOnSuccessUi(body: (T) -> Unit) =
|
||||||
|
doOnSuccess { ui { body(it) } }
|
||||||
|
|
||||||
|
fun <T> Maybe<T>.doOnSuccessUi(body: (T) -> Unit) =
|
||||||
|
doOnSuccess { ui { body(it) } }
|
||||||
|
|
||||||
|
fun <T> Maybe<T>.doOnCompleteUi(body: () -> Unit) =
|
||||||
|
doOnComplete { ui { body() } }
|
||||||
|
|
||||||
|
fun Completable.doOnCompleteUi(body: () -> Unit) =
|
||||||
|
doOnComplete { ui { body() } }
|
||||||
|
|
||||||
|
|
||||||
|
fun <T, R> Observable<List<T>>.mapList(
|
||||||
|
transformer: (T) -> R
|
||||||
|
) = flatMapIterable { it }
|
||||||
|
.map(transformer)
|
||||||
|
.toList()
|
||||||
|
|
||||||
|
fun <T, R> Single<List<T>>.mapList(
|
||||||
|
transformer: (T) -> R
|
||||||
|
) = flattenAsFlowable { it }
|
||||||
|
.map(transformer)
|
||||||
|
.toList()
|
||||||
|
|
||||||
|
fun <T, R> Maybe<List<T>>.mapList(
|
||||||
|
transformer: (T) -> R
|
||||||
|
) = flattenAsFlowable { it }
|
||||||
|
.map(transformer)
|
||||||
|
.toList()
|
||||||
|
|
||||||
|
fun <T, R> Flowable<List<T>>.mapList(
|
||||||
|
transformer: (T) -> R
|
||||||
|
) = flatMapIterable { it }
|
||||||
|
.map(transformer)
|
||||||
|
.toList()
|
||||||
|
|
||||||
|
fun <T> ObservableField<T>.toObservable(): Observable<T> {
|
||||||
|
val observableField = this
|
||||||
|
return Observable.create { emitter ->
|
||||||
|
observableField.get()?.let { emitter.onNext(it) }
|
||||||
|
|
||||||
|
val callback = object : BindingObservable.OnPropertyChangedCallback() {
|
||||||
|
override fun onPropertyChanged(sender: BindingObservable?, propertyId: Int) {
|
||||||
|
observableField.get()?.let { emitter.onNext(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
observableField.addOnPropertyChangedCallback(callback)
|
||||||
|
emitter.setDisposable(Disposables.fromAction {
|
||||||
|
observableField.removeOnPropertyChangedCallback(callback)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T : Any> T.toSingle() = Single.just(this)
|
||||||
|
|
||||||
|
fun <T1, T2, R> zip(t1: Single<T1>, t2: Single<T2>, zipper: (T1, T2) -> R) =
|
||||||
|
Single.zip(t1, t2, BiFunction<T1, T2, R> { rt1, rt2 -> zipper(rt1, rt2) })
|
126
app/src/main/java/com/topjohnwu/magisk/extensions/Snackbar.kt
Normal file
126
app/src/main/java/com/topjohnwu/magisk/extensions/Snackbar.kt
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
package com.topjohnwu.magisk.extensions
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.res.ColorStateList
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.annotation.ColorInt
|
||||||
|
import androidx.annotation.ColorRes
|
||||||
|
import androidx.annotation.StringRes
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.core.view.ViewCompat
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import com.google.android.material.snackbar.Snackbar
|
||||||
|
|
||||||
|
fun AppCompatActivity.snackbar(
|
||||||
|
view: View,
|
||||||
|
@StringRes messageRes: Int,
|
||||||
|
length: Int = Snackbar.LENGTH_SHORT,
|
||||||
|
f: Snackbar.() -> Unit = {}
|
||||||
|
) {
|
||||||
|
snackbar(view, getString(messageRes), length, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun AppCompatActivity.snackbar(
|
||||||
|
view: View,
|
||||||
|
message: String,
|
||||||
|
length: Int = Snackbar.LENGTH_SHORT,
|
||||||
|
f: Snackbar.() -> Unit = {}
|
||||||
|
) = Snackbar.make(view, message, length)
|
||||||
|
.apply(f)
|
||||||
|
.show()
|
||||||
|
|
||||||
|
fun Fragment.snackbar(
|
||||||
|
view: View,
|
||||||
|
@StringRes messageRes: Int,
|
||||||
|
length: Int = Snackbar.LENGTH_SHORT,
|
||||||
|
f: Snackbar.() -> Unit = {}
|
||||||
|
) {
|
||||||
|
snackbar(view, getString(messageRes), length, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Fragment.snackbar(
|
||||||
|
view: View,
|
||||||
|
message: String,
|
||||||
|
length: Int = Snackbar.LENGTH_SHORT,
|
||||||
|
f: Snackbar.() -> Unit = {}
|
||||||
|
) = Snackbar.make(view, message, length)
|
||||||
|
.apply(f)
|
||||||
|
.show()
|
||||||
|
|
||||||
|
fun Snackbar.action(init: KSnackbar.() -> Unit) = apply {
|
||||||
|
val config = KSnackbar().apply(init)
|
||||||
|
|
||||||
|
setAction(config.title(context), config.onClickListener)
|
||||||
|
|
||||||
|
when {
|
||||||
|
config.hasValidColor -> setActionTextColor(config.color(context) ?: return@apply)
|
||||||
|
config.hasValidColorStateList -> setActionTextColor(config.colorStateList(context) ?: return@apply)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class KSnackbar {
|
||||||
|
var colorRes: Int = -1
|
||||||
|
var colorStateListRes: Int = -1
|
||||||
|
|
||||||
|
var title: CharSequence = ""
|
||||||
|
var titleRes: Int = -1
|
||||||
|
|
||||||
|
internal var onClickListener: (View) -> Unit = {}
|
||||||
|
internal val hasValidColor get() = colorRes != -1
|
||||||
|
internal val hasValidColorStateList get() = colorStateListRes != -1
|
||||||
|
|
||||||
|
fun onClicked(listener: (View) -> Unit) {
|
||||||
|
onClickListener = listener
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun title(context: Context) = if (title.isBlank()) context.getString(titleRes) else title
|
||||||
|
internal fun colorStateList(context: Context) = context.colorStateListCompat(colorStateListRes)
|
||||||
|
internal fun color(context: Context) = context.colorCompat(colorRes)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated("Kotlin DSL version is preferred", ReplaceWith("action {}"))
|
||||||
|
fun Snackbar.action(
|
||||||
|
@StringRes actionRes: Int,
|
||||||
|
@ColorRes colorRes: Int? = null,
|
||||||
|
listener: (View) -> Unit
|
||||||
|
) {
|
||||||
|
view.resources.getString(actionRes)
|
||||||
|
colorRes?.let { ContextCompat.getColor(view.context, colorRes) }
|
||||||
|
action {}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated("Kotlin DSL version is preferred", ReplaceWith("action {}"))
|
||||||
|
fun Snackbar.action(action: String, @ColorInt color: Int? = null, listener: (View) -> Unit) {
|
||||||
|
setAction(action, listener)
|
||||||
|
color?.let { setActionTextColor(color) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Snackbar.textColorRes(@ColorRes colorRes: Int) {
|
||||||
|
textColor(context.colorCompat(colorRes) ?: return)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Snackbar.textColor(@ColorInt color: Int) {
|
||||||
|
val tv = view.findViewById<TextView>(com.google.android.material.R.id.snackbar_text)
|
||||||
|
tv.setTextColor(color)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Snackbar.backgroundColorRes(@ColorRes colorRes: Int) {
|
||||||
|
backgroundColor(context.colorCompat(colorRes) ?: return)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Snackbar.backgroundColor(@ColorInt color: Int) {
|
||||||
|
ViewCompat.setBackgroundTintList(
|
||||||
|
view,
|
||||||
|
ColorStateList.valueOf(color)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Snackbar.alert() {
|
||||||
|
textColor(0xF44336)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Snackbar.success() {
|
||||||
|
textColor(0x4CAF50)
|
||||||
|
}
|
@ -1,6 +1,8 @@
|
|||||||
package com.topjohnwu.magisk.extensions
|
package com.topjohnwu.magisk.extensions
|
||||||
|
|
||||||
|
import android.content.ComponentName
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.content.ContextWrapper
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.ApplicationInfo
|
import android.content.pm.ApplicationInfo
|
||||||
import android.content.pm.ComponentInfo
|
import android.content.pm.ComponentInfo
|
||||||
@ -8,13 +10,23 @@ import android.content.pm.PackageInfo
|
|||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.content.pm.PackageManager.*
|
import android.content.pm.PackageManager.*
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
|
import android.content.res.Resources
|
||||||
import android.database.Cursor
|
import android.database.Cursor
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import android.os.Build
|
||||||
import android.provider.OpenableColumns
|
import android.provider.OpenableColumns
|
||||||
|
import android.view.View
|
||||||
|
import androidx.annotation.ColorRes
|
||||||
|
import androidx.annotation.DrawableRes
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.core.net.toUri
|
||||||
|
import com.topjohnwu.magisk.utils.DynamicClassLoader
|
||||||
import com.topjohnwu.magisk.utils.FileProvider
|
import com.topjohnwu.magisk.utils.FileProvider
|
||||||
|
import com.topjohnwu.magisk.utils.Utils
|
||||||
import com.topjohnwu.magisk.utils.currentLocale
|
import com.topjohnwu.magisk.utils.currentLocale
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileNotFoundException
|
import java.io.FileNotFoundException
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
val packageName: String get() = get<Context>().packageName
|
val packageName: String get() = get<Context>().packageName
|
||||||
|
|
||||||
@ -85,6 +97,105 @@ fun Context.readUri(uri: Uri) =
|
|||||||
|
|
||||||
fun Intent.startActivity(context: Context) = context.startActivity(this)
|
fun Intent.startActivity(context: Context) = context.startActivity(this)
|
||||||
|
|
||||||
|
fun Intent.toCommand(args: MutableList<String>) {
|
||||||
|
if (action != null) {
|
||||||
|
args.add("-a")
|
||||||
|
args.add(action!!)
|
||||||
|
}
|
||||||
|
if (component != null) {
|
||||||
|
args.add("-n")
|
||||||
|
args.add(component!!.flattenToString())
|
||||||
|
}
|
||||||
|
if (data != null) {
|
||||||
|
args.add("-d")
|
||||||
|
args.add(dataString!!)
|
||||||
|
}
|
||||||
|
if (categories != null) {
|
||||||
|
for (cat in categories) {
|
||||||
|
args.add("-c")
|
||||||
|
args.add(cat)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (type != null) {
|
||||||
|
args.add("-t")
|
||||||
|
args.add(type!!)
|
||||||
|
}
|
||||||
|
val extras = extras
|
||||||
|
if (extras != null) {
|
||||||
|
loop@ for (key in extras.keySet()) {
|
||||||
|
val v = extras.get(key) ?: continue
|
||||||
|
var value: Any = v
|
||||||
|
val arg: String
|
||||||
|
when {
|
||||||
|
v is String -> arg = "--es"
|
||||||
|
v is Boolean -> arg = "--ez"
|
||||||
|
v is Int -> arg = "--ei"
|
||||||
|
v is Long -> arg = "--el"
|
||||||
|
v is Float -> arg = "--ef"
|
||||||
|
v is Uri -> arg = "--eu"
|
||||||
|
v is ComponentName -> {
|
||||||
|
arg = "--ecn"
|
||||||
|
value = v.flattenToString()
|
||||||
|
}
|
||||||
|
v is ArrayList<*> -> {
|
||||||
|
if (v.size <= 0)
|
||||||
|
/* Impossible to know the type due to type erasure */
|
||||||
|
continue@loop
|
||||||
|
|
||||||
|
arg = if (v[0] is Int)
|
||||||
|
"--eial"
|
||||||
|
else if (v[0] is Long)
|
||||||
|
"--elal"
|
||||||
|
else if (v[0] is Float)
|
||||||
|
"--efal"
|
||||||
|
else if (v[0] is String)
|
||||||
|
"--esal"
|
||||||
|
else
|
||||||
|
continue@loop /* Unsupported */
|
||||||
|
|
||||||
|
val sb = StringBuilder()
|
||||||
|
for (o in v) {
|
||||||
|
sb.append(o.toString().replace(",", "\\,"))
|
||||||
|
sb.append(',')
|
||||||
|
}
|
||||||
|
// Remove trailing comma
|
||||||
|
sb.deleteCharAt(sb.length - 1)
|
||||||
|
value = sb
|
||||||
|
}
|
||||||
|
v.javaClass.isArray -> {
|
||||||
|
arg = if (v is IntArray)
|
||||||
|
"--eia"
|
||||||
|
else if (v is LongArray)
|
||||||
|
"--ela"
|
||||||
|
else if (v is FloatArray)
|
||||||
|
"--efa"
|
||||||
|
else if (v is Array<*> && v.isArrayOf<String>())
|
||||||
|
"--esa"
|
||||||
|
else
|
||||||
|
continue@loop /* Unsupported */
|
||||||
|
|
||||||
|
val sb = StringBuilder()
|
||||||
|
val len = java.lang.reflect.Array.getLength(v)
|
||||||
|
for (i in 0 until len) {
|
||||||
|
sb.append(java.lang.reflect.Array.get(v, i)!!.toString().replace(",", "\\,"))
|
||||||
|
sb.append(',')
|
||||||
|
}
|
||||||
|
// Remove trailing comma
|
||||||
|
sb.deleteCharAt(sb.length - 1)
|
||||||
|
value = sb
|
||||||
|
}
|
||||||
|
else -> continue@loop
|
||||||
|
} /* Unsupported */
|
||||||
|
|
||||||
|
args.add(arg)
|
||||||
|
args.add(key)
|
||||||
|
args.add(value.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
args.add("-f")
|
||||||
|
args.add(flags.toString())
|
||||||
|
}
|
||||||
|
|
||||||
fun File.provide(context: Context = get()): Uri {
|
fun File.provide(context: Context = get()): Uri {
|
||||||
return FileProvider.getUriForFile(context, context.packageName + ".provider", this)
|
return FileProvider.getUriForFile(context, context.packageName + ".provider", this)
|
||||||
}
|
}
|
||||||
@ -119,3 +230,48 @@ fun ApplicationInfo.getLabel(pm: PackageManager): String {
|
|||||||
|
|
||||||
return loadLabel(pm).toString()
|
return loadLabel(pm).toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun Intent.exists(packageManager: PackageManager) = resolveActivity(packageManager) != null
|
||||||
|
|
||||||
|
fun Context.colorCompat(@ColorRes id: Int) = try {
|
||||||
|
ContextCompat.getColor(this, id)
|
||||||
|
} catch (e: Resources.NotFoundException) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Context.colorStateListCompat(@ColorRes id: Int) = try {
|
||||||
|
ContextCompat.getColorStateList(this, id)
|
||||||
|
} catch (e: Resources.NotFoundException) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Context.drawableCompat(@DrawableRes id: Int) = ContextCompat.getDrawable(this, id)
|
||||||
|
/**
|
||||||
|
* Pass [start] and [end] dimensions, function will return left and right
|
||||||
|
* with respect to RTL layout direction
|
||||||
|
*/
|
||||||
|
fun Context.startEndToLeftRight(start: Int, end: Int): Pair<Int, Int> {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 &&
|
||||||
|
resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_RTL
|
||||||
|
) {
|
||||||
|
return end to start
|
||||||
|
}
|
||||||
|
return start to end
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Context.openUrl(url: String) = Utils.openLink(this, url.toUri())
|
||||||
|
|
||||||
|
@Suppress("FunctionName")
|
||||||
|
inline fun <reified T> T.DynamicClassLoader(apk: File)
|
||||||
|
= DynamicClassLoader(apk, T::class.java.classLoader)
|
||||||
|
|
||||||
|
fun Context.unwrap() : Context {
|
||||||
|
var context = this
|
||||||
|
while (true) {
|
||||||
|
if (context is ContextWrapper)
|
||||||
|
context = context.baseContext
|
||||||
|
else
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return context
|
||||||
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package com.topjohnwu.magisk.extensions
|
package com.topjohnwu.magisk.extensions
|
||||||
|
|
||||||
import com.skoumal.teanity.util.KObservableField
|
import com.topjohnwu.magisk.utils.KObservableField
|
||||||
|
|
||||||
|
|
||||||
fun KObservableField<Boolean>.toggle() {
|
fun KObservableField<Boolean>.toggle() {
|
||||||
|
@ -2,8 +2,7 @@ package com.topjohnwu.magisk.extensions
|
|||||||
|
|
||||||
import androidx.collection.SparseArrayCompat
|
import androidx.collection.SparseArrayCompat
|
||||||
import androidx.databinding.ObservableList
|
import androidx.databinding.ObservableList
|
||||||
import com.skoumal.teanity.extensions.subscribeK
|
import com.topjohnwu.magisk.utils.DiffObservableList
|
||||||
import com.skoumal.teanity.util.DiffObservableList
|
|
||||||
import io.reactivex.disposables.Disposable
|
import io.reactivex.disposables.Disposable
|
||||||
|
|
||||||
fun <T> MutableList<T>.update(newList: List<T>) {
|
fun <T> MutableList<T>.update(newList: List<T>) {
|
||||||
@ -26,8 +25,8 @@ fun List<String>.toShellCmd(): String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun <T1, T2> ObservableList<T1>.sendUpdatesTo(
|
fun <T1, T2> ObservableList<T1>.sendUpdatesTo(
|
||||||
target: DiffObservableList<T2>,
|
target: DiffObservableList<T2>,
|
||||||
mapper: (List<T1>) -> List<T2>
|
mapper: (List<T1>) -> List<T2>
|
||||||
) = addOnListChangedCallback(object :
|
) = addOnListChangedCallback(object :
|
||||||
ObservableList.OnListChangedCallback<ObservableList<T1>>() {
|
ObservableList.OnListChangedCallback<ObservableList<T1>>() {
|
||||||
override fun onChanged(sender: ObservableList<T1>?) {
|
override fun onChanged(sender: ObservableList<T1>?) {
|
||||||
|
@ -1,9 +0,0 @@
|
|||||||
package com.topjohnwu.magisk.extensions
|
|
||||||
|
|
||||||
import io.reactivex.Single
|
|
||||||
import io.reactivex.functions.BiFunction
|
|
||||||
|
|
||||||
fun <T : Any> T.toSingle() = Single.just(this)
|
|
||||||
|
|
||||||
fun <T1, T2, R> zip(t1: Single<T1>, t2: Single<T2>, zipper: (T1, T2) -> R) =
|
|
||||||
Single.zip(t1, t2, BiFunction<T1, T2, R> { rt1, rt2 -> zipper(rt1, rt2) })
|
|
@ -2,7 +2,7 @@ package com.topjohnwu.magisk.model.binding
|
|||||||
|
|
||||||
import androidx.databinding.ViewDataBinding
|
import androidx.databinding.ViewDataBinding
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.skoumal.teanity.databinding.ComparableRvItem
|
import com.topjohnwu.magisk.databinding.ComparableRvItem
|
||||||
import com.topjohnwu.magisk.model.entity.recycler.LenientRvItem
|
import com.topjohnwu.magisk.model.entity.recycler.LenientRvItem
|
||||||
import me.tatarka.bindingcollectionadapter2.BindingRecyclerViewAdapter
|
import me.tatarka.bindingcollectionadapter2.BindingRecyclerViewAdapter
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@ import androidx.core.app.NotificationCompat
|
|||||||
import com.topjohnwu.magisk.ClassMap
|
import com.topjohnwu.magisk.ClassMap
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
import com.topjohnwu.magisk.extensions.chooser
|
import com.topjohnwu.magisk.extensions.chooser
|
||||||
|
import com.topjohnwu.magisk.extensions.exists
|
||||||
import com.topjohnwu.magisk.extensions.provide
|
import com.topjohnwu.magisk.extensions.provide
|
||||||
import com.topjohnwu.magisk.model.entity.internal.Configuration.*
|
import com.topjohnwu.magisk.model.entity.internal.Configuration.*
|
||||||
import com.topjohnwu.magisk.model.entity.internal.Configuration.Flash.Secondary
|
import com.topjohnwu.magisk.model.entity.internal.Configuration.Flash.Secondary
|
||||||
@ -17,6 +18,7 @@ import com.topjohnwu.magisk.model.entity.internal.DownloadSubject
|
|||||||
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject.*
|
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject.*
|
||||||
import com.topjohnwu.magisk.ui.flash.FlashActivity
|
import com.topjohnwu.magisk.ui.flash.FlashActivity
|
||||||
import com.topjohnwu.magisk.utils.APKInstall
|
import com.topjohnwu.magisk.utils.APKInstall
|
||||||
|
import org.koin.core.get
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import kotlin.random.Random.Default.nextInt
|
import kotlin.random.Random.Default.nextInt
|
||||||
|
|
||||||
@ -76,8 +78,14 @@ open class DownloadService : RemoteFileService() {
|
|||||||
|
|
||||||
private fun NotificationCompat.Builder.addActionsInternal(subject: Magisk)
|
private fun NotificationCompat.Builder.addActionsInternal(subject: Magisk)
|
||||||
= when (val conf = subject.configuration) {
|
= when (val conf = subject.configuration) {
|
||||||
Download -> addAction(0, R.string.download_open_parent, fileIntent(subject.file.parentFile!!))
|
Download -> this.apply {
|
||||||
.addAction(0, R.string.download_open_self, fileIntent(subject.file))
|
fileIntent(subject.file.parentFile!!)
|
||||||
|
.takeIf { it.exists(get()) }
|
||||||
|
?.let { addAction(0, R.string.download_open_parent, it.chooser()) }
|
||||||
|
fileIntent(subject.file)
|
||||||
|
.takeIf { it.exists(get()) }
|
||||||
|
?.let { addAction(0, R.string.download_open_self, it.chooser()) }
|
||||||
|
}
|
||||||
Uninstall -> setContentIntent(FlashActivity.uninstallIntent(context, subject.file))
|
Uninstall -> setContentIntent(FlashActivity.uninstallIntent(context, subject.file))
|
||||||
is Flash -> setContentIntent(FlashActivity.flashIntent(context, subject.file, conf is Secondary))
|
is Flash -> setContentIntent(FlashActivity.flashIntent(context, subject.file, conf is Secondary))
|
||||||
is Patch -> setContentIntent(FlashActivity.patchIntent(context, subject.file, conf.fileUri))
|
is Patch -> setContentIntent(FlashActivity.patchIntent(context, subject.file, conf.fileUri))
|
||||||
@ -86,8 +94,14 @@ open class DownloadService : RemoteFileService() {
|
|||||||
|
|
||||||
private fun NotificationCompat.Builder.addActionsInternal(subject: Module)
|
private fun NotificationCompat.Builder.addActionsInternal(subject: Module)
|
||||||
= when (subject.configuration) {
|
= when (subject.configuration) {
|
||||||
Download -> addAction(0, R.string.download_open_parent, fileIntent(subject.file.parentFile!!))
|
Download -> this.apply {
|
||||||
.addAction(0, R.string.download_open_self, fileIntent(subject.file))
|
fileIntent(subject.file.parentFile!!)
|
||||||
|
.takeIf { it.exists(get()) }
|
||||||
|
?.let { addAction(0, R.string.download_open_parent, it.chooser()) }
|
||||||
|
fileIntent(subject.file)
|
||||||
|
.takeIf { it.exists(get()) }
|
||||||
|
?.let { addAction(0, R.string.download_open_self, it.chooser()) }
|
||||||
|
}
|
||||||
is Flash -> setContentIntent(FlashActivity.installIntent(context, subject.file))
|
is Flash -> setContentIntent(FlashActivity.installIntent(context, subject.file))
|
||||||
else -> this
|
else -> this
|
||||||
}
|
}
|
||||||
@ -115,7 +129,6 @@ open class DownloadService : RemoteFileService() {
|
|||||||
.setDataAndType(file.provide(this), file.type)
|
.setDataAndType(file.provide(this), file.type)
|
||||||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||||
.chooser()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class Builder {
|
class Builder {
|
||||||
|
@ -5,13 +5,13 @@ import com.topjohnwu.magisk.BuildConfig
|
|||||||
import com.topjohnwu.magisk.ClassMap
|
import com.topjohnwu.magisk.ClassMap
|
||||||
import com.topjohnwu.magisk.Config
|
import com.topjohnwu.magisk.Config
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
|
import com.topjohnwu.magisk.extensions.DynamicClassLoader
|
||||||
import com.topjohnwu.magisk.model.entity.internal.Configuration.APK.Restore
|
import com.topjohnwu.magisk.model.entity.internal.Configuration.APK.Restore
|
||||||
import com.topjohnwu.magisk.model.entity.internal.Configuration.APK.Upgrade
|
import com.topjohnwu.magisk.model.entity.internal.Configuration.APK.Upgrade
|
||||||
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject
|
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject
|
||||||
import com.topjohnwu.magisk.ui.SplashActivity
|
import com.topjohnwu.magisk.ui.SplashActivity
|
||||||
import com.topjohnwu.magisk.utils.DynamicClassLoader
|
|
||||||
import com.topjohnwu.magisk.utils.PatchAPK
|
import com.topjohnwu.magisk.utils.PatchAPK
|
||||||
import com.topjohnwu.magisk.utils.RootUtils
|
import com.topjohnwu.magisk.utils.Utils
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@ -54,7 +54,7 @@ private fun RemoteFileService.restore(apk: File, id: Int) {
|
|||||||
if (Shell.su("pm install $apk").exec().isSuccess) {
|
if (Shell.su("pm install $apk").exec().isSuccess) {
|
||||||
val component = ComponentName(BuildConfig.APPLICATION_ID,
|
val component = ComponentName(BuildConfig.APPLICATION_ID,
|
||||||
ClassMap.get<Class<*>>(SplashActivity::class.java).name)
|
ClassMap.get<Class<*>>(SplashActivity::class.java).name)
|
||||||
RootUtils.rmAndLaunch(packageName, component)
|
Utils.rmAndLaunch(packageName, component)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,15 +1,16 @@
|
|||||||
package com.topjohnwu.magisk.model.download
|
package com.topjohnwu.magisk.model.download
|
||||||
|
|
||||||
import android.app.Notification
|
import android.app.Notification
|
||||||
import android.app.Service
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import androidx.core.app.NotificationManagerCompat
|
import androidx.core.app.NotificationManagerCompat
|
||||||
|
import com.topjohnwu.magisk.base.BaseService
|
||||||
|
import org.koin.core.KoinComponent
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.random.Random.Default.nextInt
|
import kotlin.random.Random.Default.nextInt
|
||||||
|
|
||||||
abstract class NotificationService : Service() {
|
abstract class NotificationService : BaseService(), KoinComponent {
|
||||||
|
|
||||||
abstract val defaultNotification: NotificationCompat.Builder
|
abstract val defaultNotification: NotificationCompat.Builder
|
||||||
|
|
||||||
|
@ -3,11 +3,11 @@ package com.topjohnwu.magisk.model.download
|
|||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import com.skoumal.teanity.extensions.subscribeK
|
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
import com.topjohnwu.magisk.data.network.GithubRawServices
|
import com.topjohnwu.magisk.data.network.GithubRawServices
|
||||||
import com.topjohnwu.magisk.di.NullActivity
|
import com.topjohnwu.magisk.di.NullActivity
|
||||||
import com.topjohnwu.magisk.extensions.get
|
import com.topjohnwu.magisk.extensions.get
|
||||||
|
import com.topjohnwu.magisk.extensions.subscribeK
|
||||||
import com.topjohnwu.magisk.extensions.writeTo
|
import com.topjohnwu.magisk.extensions.writeTo
|
||||||
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject
|
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject
|
||||||
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject.*
|
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject.*
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
package com.topjohnwu.magisk.model.entity
|
|
||||||
|
|
||||||
data class Version(val version: String, val versionCode: Int)
|
|
@ -48,7 +48,7 @@ class Module(path: String) : BaseModule() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (name.isEmpty()) {
|
if (name.isEmpty()) {
|
||||||
name = id;
|
name = id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,7 +65,7 @@ class Module(path: String) : BaseModule() {
|
|||||||
val module = Module(Const.MAGISK_PATH + "/" + file.name)
|
val module = Module(Const.MAGISK_PATH + "/" + file.name)
|
||||||
moduleList.add(module)
|
moduleList.add(module)
|
||||||
}
|
}
|
||||||
return moduleList
|
return moduleList.sortedBy { it.name }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
package com.topjohnwu.magisk.model.entity.recycler
|
package com.topjohnwu.magisk.model.entity.recycler
|
||||||
|
|
||||||
import com.skoumal.teanity.databinding.ComparableRvItem
|
|
||||||
import com.skoumal.teanity.extensions.addOnPropertyChangedCallback
|
|
||||||
import com.skoumal.teanity.rxbus.RxBus
|
|
||||||
import com.skoumal.teanity.util.DiffObservableList
|
|
||||||
import com.skoumal.teanity.util.KObservableField
|
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
|
import com.topjohnwu.magisk.databinding.ComparableRvItem
|
||||||
|
import com.topjohnwu.magisk.extensions.addOnPropertyChangedCallback
|
||||||
import com.topjohnwu.magisk.extensions.inject
|
import com.topjohnwu.magisk.extensions.inject
|
||||||
import com.topjohnwu.magisk.extensions.toggle
|
import com.topjohnwu.magisk.extensions.toggle
|
||||||
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.magisk.model.entity.state.IndeterminateState
|
import com.topjohnwu.magisk.model.entity.state.IndeterminateState
|
||||||
import com.topjohnwu.magisk.model.events.HideProcessEvent
|
import com.topjohnwu.magisk.model.events.HideProcessEvent
|
||||||
|
import com.topjohnwu.magisk.utils.DiffObservableList
|
||||||
|
import com.topjohnwu.magisk.utils.KObservableField
|
||||||
|
import com.topjohnwu.magisk.utils.RxBus
|
||||||
|
|
||||||
class HideRvItem(val item: HideAppInfo, targets: List<HideTarget>) :
|
class HideRvItem(val item: HideAppInfo, targets: List<HideTarget>) :
|
||||||
ComparableRvItem<HideRvItem>() {
|
ComparableRvItem<HideRvItem>() {
|
||||||
|
@ -2,7 +2,7 @@ package com.topjohnwu.magisk.model.entity.recycler
|
|||||||
|
|
||||||
import androidx.databinding.ViewDataBinding
|
import androidx.databinding.ViewDataBinding
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.skoumal.teanity.databinding.ComparableRvItem
|
import com.topjohnwu.magisk.databinding.ComparableRvItem
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This item addresses issues where enclosing recycler has to be invalidated or generally
|
* This item addresses issues where enclosing recycler has to be invalidated or generally
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
package com.topjohnwu.magisk.model.entity.recycler
|
package com.topjohnwu.magisk.model.entity.recycler
|
||||||
|
|
||||||
import com.skoumal.teanity.databinding.ComparableRvItem
|
|
||||||
import com.skoumal.teanity.util.DiffObservableList
|
|
||||||
import com.skoumal.teanity.util.KObservableField
|
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
|
import com.topjohnwu.magisk.databinding.ComparableRvItem
|
||||||
import com.topjohnwu.magisk.extensions.timeFormatMedium
|
import com.topjohnwu.magisk.extensions.timeFormatMedium
|
||||||
import com.topjohnwu.magisk.extensions.toTime
|
import com.topjohnwu.magisk.extensions.toTime
|
||||||
import com.topjohnwu.magisk.extensions.toggle
|
import com.topjohnwu.magisk.extensions.toggle
|
||||||
import com.topjohnwu.magisk.model.entity.MagiskLog
|
import com.topjohnwu.magisk.model.entity.MagiskLog
|
||||||
import com.topjohnwu.magisk.model.entity.WrappedMagiskLog
|
import com.topjohnwu.magisk.model.entity.WrappedMagiskLog
|
||||||
|
import com.topjohnwu.magisk.utils.DiffObservableList
|
||||||
|
import com.topjohnwu.magisk.utils.KObservableField
|
||||||
|
|
||||||
class LogRvItem : ComparableRvItem<LogRvItem>() {
|
class LogRvItem : ComparableRvItem<LogRvItem>() {
|
||||||
override val layoutRes: Int = R.layout.item_page_log
|
override val layoutRes: Int = R.layout.item_page_log
|
||||||
|
@ -2,14 +2,14 @@ package com.topjohnwu.magisk.model.entity.recycler
|
|||||||
|
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import com.skoumal.teanity.databinding.ComparableRvItem
|
|
||||||
import com.skoumal.teanity.extensions.addOnPropertyChangedCallback
|
|
||||||
import com.skoumal.teanity.util.KObservableField
|
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
|
import com.topjohnwu.magisk.databinding.ComparableRvItem
|
||||||
|
import com.topjohnwu.magisk.extensions.addOnPropertyChangedCallback
|
||||||
import com.topjohnwu.magisk.extensions.get
|
import com.topjohnwu.magisk.extensions.get
|
||||||
import com.topjohnwu.magisk.extensions.toggle
|
import com.topjohnwu.magisk.extensions.toggle
|
||||||
import com.topjohnwu.magisk.model.entity.module.Module
|
import com.topjohnwu.magisk.model.entity.module.Module
|
||||||
import com.topjohnwu.magisk.model.entity.module.Repo
|
import com.topjohnwu.magisk.model.entity.module.Repo
|
||||||
|
import com.topjohnwu.magisk.utils.KObservableField
|
||||||
|
|
||||||
class ModuleRvItem(val item: Module) : ComparableRvItem<ModuleRvItem>() {
|
class ModuleRvItem(val item: Module) : ComparableRvItem<ModuleRvItem>() {
|
||||||
|
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
package com.topjohnwu.magisk.model.entity.recycler
|
package com.topjohnwu.magisk.model.entity.recycler
|
||||||
|
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import com.skoumal.teanity.databinding.ComparableRvItem
|
|
||||||
import com.skoumal.teanity.extensions.addOnPropertyChangedCallback
|
|
||||||
import com.skoumal.teanity.rxbus.RxBus
|
|
||||||
import com.skoumal.teanity.util.KObservableField
|
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
|
import com.topjohnwu.magisk.databinding.ComparableRvItem
|
||||||
|
import com.topjohnwu.magisk.extensions.addOnPropertyChangedCallback
|
||||||
import com.topjohnwu.magisk.extensions.inject
|
import com.topjohnwu.magisk.extensions.inject
|
||||||
import com.topjohnwu.magisk.extensions.toggle
|
import com.topjohnwu.magisk.extensions.toggle
|
||||||
import com.topjohnwu.magisk.model.entity.MagiskPolicy
|
import com.topjohnwu.magisk.model.entity.MagiskPolicy
|
||||||
import com.topjohnwu.magisk.model.events.PolicyEnableEvent
|
import com.topjohnwu.magisk.model.events.PolicyEnableEvent
|
||||||
import com.topjohnwu.magisk.model.events.PolicyUpdateEvent
|
import com.topjohnwu.magisk.model.events.PolicyUpdateEvent
|
||||||
|
import com.topjohnwu.magisk.utils.KObservableField
|
||||||
|
import com.topjohnwu.magisk.utils.RxBus
|
||||||
|
|
||||||
class PolicyRvItem(val item: MagiskPolicy, val icon: Drawable) : ComparableRvItem<PolicyRvItem>() {
|
class PolicyRvItem(val item: MagiskPolicy, val icon: Drawable) : ComparableRvItem<PolicyRvItem>() {
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package com.topjohnwu.magisk.model.entity.recycler
|
package com.topjohnwu.magisk.model.entity.recycler
|
||||||
|
|
||||||
import com.skoumal.teanity.databinding.ComparableRvItem
|
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
|
import com.topjohnwu.magisk.databinding.ComparableRvItem
|
||||||
|
|
||||||
class SectionRvItem(val text: String) : ComparableRvItem<SectionRvItem>() {
|
class SectionRvItem(val text: String) : ComparableRvItem<SectionRvItem>() {
|
||||||
override val layoutRes: Int = R.layout.item_section
|
override val layoutRes: Int = R.layout.item_section
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package com.topjohnwu.magisk.model.entity.recycler
|
package com.topjohnwu.magisk.model.entity.recycler
|
||||||
|
|
||||||
import com.skoumal.teanity.databinding.ComparableRvItem
|
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
|
import com.topjohnwu.magisk.databinding.ComparableRvItem
|
||||||
|
|
||||||
class SpinnerRvItem(val item: String) : ComparableRvItem<SpinnerRvItem>() {
|
class SpinnerRvItem(val item: String) : ComparableRvItem<SpinnerRvItem>() {
|
||||||
|
|
||||||
|
@ -0,0 +1,25 @@
|
|||||||
|
package com.topjohnwu.magisk.model.events
|
||||||
|
|
||||||
|
internal interface EventHandler {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called for all [ViewEvent]s published by associated viewModel.
|
||||||
|
* For [SimpleViewEvent]s, both this and [onSimpleEventDispatched]
|
||||||
|
* methods are called - you can choose the way how you handle them.
|
||||||
|
*/
|
||||||
|
fun onEventDispatched(event: ViewEvent) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called for all [SimpleViewEvent]s published by associated viewModel.
|
||||||
|
* Both this and [onEventDispatched] methods are called - you can choose
|
||||||
|
* the way how you handle them.
|
||||||
|
*/
|
||||||
|
fun onSimpleEventDispatched(event: Int) {}
|
||||||
|
|
||||||
|
val viewEventObserver get() = ViewEventObserver {
|
||||||
|
onEventDispatched(it)
|
||||||
|
if (it is SimpleViewEvent) {
|
||||||
|
onSimpleEventDispatched(it.event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,10 +1,10 @@
|
|||||||
package com.topjohnwu.magisk.model.events
|
package com.topjohnwu.magisk.model.events
|
||||||
|
|
||||||
import com.skoumal.teanity.rxbus.RxBus
|
|
||||||
import com.topjohnwu.magisk.model.entity.MagiskPolicy
|
import com.topjohnwu.magisk.model.entity.MagiskPolicy
|
||||||
import com.topjohnwu.magisk.model.entity.recycler.HideProcessRvItem
|
import com.topjohnwu.magisk.model.entity.recycler.HideProcessRvItem
|
||||||
import com.topjohnwu.magisk.model.entity.recycler.ModuleRvItem
|
import com.topjohnwu.magisk.model.entity.recycler.ModuleRvItem
|
||||||
import com.topjohnwu.magisk.model.entity.recycler.PolicyRvItem
|
import com.topjohnwu.magisk.model.entity.recycler.PolicyRvItem
|
||||||
|
import com.topjohnwu.magisk.utils.RxBus
|
||||||
|
|
||||||
class HideProcessEvent(val item: HideProcessRvItem) : RxBus.Event
|
class HideProcessEvent(val item: HideProcessRvItem) : RxBus.Event
|
||||||
|
|
||||||
|
@ -0,0 +1,5 @@
|
|||||||
|
package com.topjohnwu.magisk.model.events
|
||||||
|
|
||||||
|
class SimpleViewEvent(
|
||||||
|
val event: Int
|
||||||
|
) : ViewEvent()
|
@ -0,0 +1,27 @@
|
|||||||
|
package com.topjohnwu.magisk.model.events
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.annotation.StringRes
|
||||||
|
import com.google.android.material.snackbar.Snackbar
|
||||||
|
|
||||||
|
class SnackbarEvent private constructor(
|
||||||
|
@StringRes private val messageRes: Int,
|
||||||
|
private val messageString: String?,
|
||||||
|
val length: Int,
|
||||||
|
val f: Snackbar.() -> Unit
|
||||||
|
) : ViewEvent() {
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@StringRes messageRes: Int,
|
||||||
|
length: Int = Snackbar.LENGTH_SHORT,
|
||||||
|
f: Snackbar.() -> Unit = {}
|
||||||
|
) : this(messageRes, null, length, f)
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
message: String,
|
||||||
|
length: Int = Snackbar.LENGTH_SHORT,
|
||||||
|
f: Snackbar.() -> Unit = {}
|
||||||
|
) : this(-1, message, length, f)
|
||||||
|
|
||||||
|
fun message(context: Context): String = messageString ?: context.getString(messageRes)
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
package com.topjohnwu.magisk.model.events
|
||||||
|
|
||||||
|
import androidx.lifecycle.Observer
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Observer for [ViewEvent]s, which automatically checks if event was handled
|
||||||
|
*/
|
||||||
|
class ViewEventObserver(private val onEventUnhandled: (ViewEvent) -> Unit) : Observer<ViewEvent> {
|
||||||
|
override fun onChanged(event: ViewEvent?) {
|
||||||
|
event?.let {
|
||||||
|
if (!it.handled) {
|
||||||
|
it.handled = true
|
||||||
|
onEventUnhandled(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -12,6 +12,16 @@ import com.topjohnwu.magisk.model.entity.module.Repo
|
|||||||
import com.topjohnwu.magisk.model.permissions.PermissionRequestBuilder
|
import com.topjohnwu.magisk.model.permissions.PermissionRequestBuilder
|
||||||
import io.reactivex.subjects.PublishSubject
|
import io.reactivex.subjects.PublishSubject
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class for passing events from ViewModels to Activities/Fragments
|
||||||
|
* Variable [handled] used so each event is handled only once
|
||||||
|
* (see https://medium.com/google-developers/livedata-with-snackbar-navigation-and-other-events-the-singleliveevent-case-ac2622673150)
|
||||||
|
* Use [ViewEventObserver] for observing these events
|
||||||
|
*/
|
||||||
|
abstract class ViewEvent {
|
||||||
|
|
||||||
|
var handled = false
|
||||||
|
}
|
||||||
|
|
||||||
data class OpenLinkEvent(val url: String) : ViewEvent()
|
data class OpenLinkEvent(val url: String) : ViewEvent()
|
||||||
|
|
||||||
@ -76,4 +86,4 @@ class BackPressEvent : ViewEvent(), ActivityExecutor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class DieEvent : ViewEvent()
|
class DieEvent : ViewEvent()
|
||||||
|
@ -5,12 +5,14 @@ import androidx.annotation.AnimRes
|
|||||||
import androidx.annotation.AnimatorRes
|
import androidx.annotation.AnimatorRes
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import com.skoumal.teanity.viewevents.NavigationDslMarker
|
import com.topjohnwu.magisk.model.events.ViewEvent
|
||||||
import com.skoumal.teanity.viewevents.ViewEvent
|
|
||||||
import com.topjohnwu.magisk.model.events.ActivityExecutor
|
import com.topjohnwu.magisk.model.events.ActivityExecutor
|
||||||
import com.topjohnwu.magisk.redesign.compat.CompatActivity
|
import com.topjohnwu.magisk.redesign.compat.CompatActivity
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
|
@DslMarker
|
||||||
|
annotation class NavigationDslMarker
|
||||||
|
|
||||||
class MagiskNavigationEvent(
|
class MagiskNavigationEvent(
|
||||||
val navDirections: MagiskNavDirectionsBuilder,
|
val navDirections: MagiskNavDirectionsBuilder,
|
||||||
val navOptions: MagiskNavOptions,
|
val navOptions: MagiskNavOptions,
|
||||||
|
@ -1,15 +1,14 @@
|
|||||||
package com.topjohnwu.magisk.model.receiver
|
package com.topjohnwu.magisk.model.receiver
|
||||||
|
|
||||||
import android.content.BroadcastReceiver
|
import android.content.ContextWrapper
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import com.topjohnwu.magisk.ClassMap
|
import com.topjohnwu.magisk.ClassMap
|
||||||
import com.topjohnwu.magisk.Config
|
import com.topjohnwu.magisk.Config
|
||||||
import com.topjohnwu.magisk.Const
|
import com.topjohnwu.magisk.Const
|
||||||
import com.topjohnwu.magisk.Info
|
import com.topjohnwu.magisk.Info
|
||||||
|
import com.topjohnwu.magisk.base.BaseReceiver
|
||||||
import com.topjohnwu.magisk.data.database.PolicyDao
|
import com.topjohnwu.magisk.data.database.PolicyDao
|
||||||
import com.topjohnwu.magisk.data.database.base.su
|
import com.topjohnwu.magisk.data.database.base.su
|
||||||
import com.topjohnwu.magisk.extensions.inject
|
|
||||||
import com.topjohnwu.magisk.extensions.reboot
|
import com.topjohnwu.magisk.extensions.reboot
|
||||||
import com.topjohnwu.magisk.model.download.DownloadService
|
import com.topjohnwu.magisk.model.download.DownloadService
|
||||||
import com.topjohnwu.magisk.model.entity.ManagerJson
|
import com.topjohnwu.magisk.model.entity.ManagerJson
|
||||||
@ -20,8 +19,9 @@ import com.topjohnwu.magisk.utils.SuLogger
|
|||||||
import com.topjohnwu.magisk.view.Notifications
|
import com.topjohnwu.magisk.view.Notifications
|
||||||
import com.topjohnwu.magisk.view.Shortcuts
|
import com.topjohnwu.magisk.view.Shortcuts
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
|
import org.koin.core.inject
|
||||||
|
|
||||||
open class GeneralReceiver : BroadcastReceiver() {
|
open class GeneralReceiver : BaseReceiver() {
|
||||||
|
|
||||||
private val policyDB: PolicyDao by inject()
|
private val policyDB: PolicyDao by inject()
|
||||||
|
|
||||||
@ -36,7 +36,7 @@ open class GeneralReceiver : BroadcastReceiver() {
|
|||||||
return intent.data?.encodedSchemeSpecificPart.orEmpty()
|
return intent.data?.encodedSchemeSpecificPart.orEmpty()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onReceive(context: Context, intent: Intent?) {
|
override fun onReceive(context: ContextWrapper, intent: Intent?) {
|
||||||
intent ?: return
|
intent ?: return
|
||||||
when (intent.action ?: return) {
|
when (intent.action ?: return) {
|
||||||
Intent.ACTION_REBOOT, Intent.ACTION_BOOT_COMPLETED -> {
|
Intent.ACTION_REBOOT, Intent.ACTION_BOOT_COMPLETED -> {
|
||||||
|
@ -3,9 +3,9 @@ package com.topjohnwu.magisk.model.update
|
|||||||
import androidx.work.ListenableWorker
|
import androidx.work.ListenableWorker
|
||||||
import com.topjohnwu.magisk.BuildConfig
|
import com.topjohnwu.magisk.BuildConfig
|
||||||
import com.topjohnwu.magisk.Info
|
import com.topjohnwu.magisk.Info
|
||||||
|
import com.topjohnwu.magisk.base.DelegateWorker
|
||||||
import com.topjohnwu.magisk.data.repository.MagiskRepository
|
import com.topjohnwu.magisk.data.repository.MagiskRepository
|
||||||
import com.topjohnwu.magisk.extensions.inject
|
import com.topjohnwu.magisk.extensions.inject
|
||||||
import com.topjohnwu.magisk.model.worker.DelegateWorker
|
|
||||||
import com.topjohnwu.magisk.view.Notifications
|
import com.topjohnwu.magisk.view.Notifications
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
|
|
||||||
|
@ -2,11 +2,11 @@ package com.topjohnwu.magisk.tasks
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import com.skoumal.teanity.extensions.subscribeK
|
|
||||||
import com.topjohnwu.magisk.Const
|
import com.topjohnwu.magisk.Const
|
||||||
import com.topjohnwu.magisk.extensions.fileName
|
import com.topjohnwu.magisk.extensions.fileName
|
||||||
import com.topjohnwu.magisk.extensions.inject
|
import com.topjohnwu.magisk.extensions.inject
|
||||||
import com.topjohnwu.magisk.extensions.readUri
|
import com.topjohnwu.magisk.extensions.readUri
|
||||||
|
import com.topjohnwu.magisk.extensions.subscribeK
|
||||||
import com.topjohnwu.magisk.utils.unzip
|
import com.topjohnwu.magisk.utils.unzip
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
import io.reactivex.Single
|
import io.reactivex.Single
|
||||||
|
@ -6,7 +6,6 @@ import android.os.Build
|
|||||||
import android.text.TextUtils
|
import android.text.TextUtils
|
||||||
import androidx.annotation.MainThread
|
import androidx.annotation.MainThread
|
||||||
import androidx.annotation.WorkerThread
|
import androidx.annotation.WorkerThread
|
||||||
import com.skoumal.teanity.extensions.subscribeK
|
|
||||||
import com.topjohnwu.magisk.Config
|
import com.topjohnwu.magisk.Config
|
||||||
import com.topjohnwu.magisk.Info
|
import com.topjohnwu.magisk.Info
|
||||||
import com.topjohnwu.magisk.data.network.GithubRawServices
|
import com.topjohnwu.magisk.data.network.GithubRawServices
|
||||||
@ -25,9 +24,11 @@ import org.kamranzafar.jtar.TarHeader
|
|||||||
import org.kamranzafar.jtar.TarInputStream
|
import org.kamranzafar.jtar.TarInputStream
|
||||||
import org.kamranzafar.jtar.TarOutputStream
|
import org.kamranzafar.jtar.TarOutputStream
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.io.*
|
import java.io.File
|
||||||
|
import java.io.FileOutputStream
|
||||||
|
import java.io.IOException
|
||||||
|
import java.io.InputStream
|
||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
import java.util.*
|
|
||||||
import java.util.zip.ZipEntry
|
import java.util.zip.ZipEntry
|
||||||
import java.util.zip.ZipInputStream
|
import java.util.zip.ZipInputStream
|
||||||
|
|
||||||
@ -40,7 +41,7 @@ abstract class MagiskInstaller {
|
|||||||
|
|
||||||
private val console: MutableList<String>
|
private val console: MutableList<String>
|
||||||
private val logs: MutableList<String>
|
private val logs: MutableList<String>
|
||||||
private var isTar = false
|
private var tarOut: TarOutputStream? = null
|
||||||
|
|
||||||
private val service: GithubRawServices by inject()
|
private val service: GithubRawServices by inject()
|
||||||
private val context: Context by inject()
|
private val context: Context by inject()
|
||||||
@ -151,7 +152,9 @@ abstract class MagiskInstaller {
|
|||||||
private fun handleTar(input: InputStream) {
|
private fun handleTar(input: InputStream) {
|
||||||
console.add("- Processing tar file")
|
console.add("- Processing tar file")
|
||||||
var vbmeta = false
|
var vbmeta = false
|
||||||
withStreams(TarInputStream(input), TarOutputStream(destFile)) { tarIn, tarOut ->
|
val tarOut = TarOutputStream(destFile)
|
||||||
|
this.tarOut = tarOut
|
||||||
|
TarInputStream(input).use { tarIn ->
|
||||||
lateinit var entry: TarEntry
|
lateinit var entry: TarEntry
|
||||||
while (tarIn.nextEntry?.let { entry = it } != null) {
|
while (tarIn.nextEntry?.let { entry = it } != null) {
|
||||||
if (entry.name.contains("boot.img") || entry.name.contains("recovery.img")) {
|
if (entry.name.contains("boot.img") || entry.name.contains("recovery.img")) {
|
||||||
@ -215,8 +218,7 @@ abstract class MagiskInstaller {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
it.reset()
|
it.reset()
|
||||||
if (Arrays.equals(magic, "ustar".toByteArray())) {
|
if (magic.contentEquals("ustar".toByteArray())) {
|
||||||
isTar = true
|
|
||||||
destFile = File(Config.downloadDirectory, "magisk_patched.tar")
|
destFile = File(Config.downloadDirectory, "magisk_patched.tar")
|
||||||
handleTar(it)
|
handleTar(it)
|
||||||
} else {
|
} else {
|
||||||
@ -293,15 +295,13 @@ abstract class MagiskInstaller {
|
|||||||
protected fun storeBoot(): Boolean {
|
protected fun storeBoot(): Boolean {
|
||||||
val patched = SuFile.open(installDir, "new-boot.img")
|
val patched = SuFile.open(installDir, "new-boot.img")
|
||||||
try {
|
try {
|
||||||
val os: OutputStream
|
val os = tarOut?.let {
|
||||||
if (isTar) {
|
it.putNextEntry(newEntry(
|
||||||
os = TarOutputStream(destFile, true)
|
|
||||||
os.putNextEntry(newEntry(
|
|
||||||
if (srcBoot.contains("recovery")) "recovery.img" else "boot.img",
|
if (srcBoot.contains("recovery")) "recovery.img" else "boot.img",
|
||||||
patched.length()))
|
patched.length()))
|
||||||
} else {
|
tarOut = null
|
||||||
os = destFile.outputStream()
|
it
|
||||||
}
|
} ?: destFile.outputStream()
|
||||||
patched.suInputStream().use { it.copyTo(os); os.close() }
|
patched.suInputStream().use { it.copyTo(os); os.close() }
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
console.add("! Failed to output to $destFile")
|
console.add("! Failed to output to $destFile")
|
||||||
|
@ -4,15 +4,24 @@ import android.content.Intent
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.core.view.GravityCompat
|
import androidx.core.view.GravityCompat
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import com.skoumal.teanity.extensions.addOnPropertyChangedCallback
|
import androidx.fragment.app.FragmentTransaction
|
||||||
|
import com.ncapdevi.fragnav.FragNavController
|
||||||
|
import com.ncapdevi.fragnav.FragNavTransactionOptions
|
||||||
import com.topjohnwu.magisk.ClassMap
|
import com.topjohnwu.magisk.ClassMap
|
||||||
import com.topjohnwu.magisk.Config
|
import com.topjohnwu.magisk.Config
|
||||||
import com.topjohnwu.magisk.Const.Key.OPEN_SECTION
|
import com.topjohnwu.magisk.Const.Key.OPEN_SECTION
|
||||||
import com.topjohnwu.magisk.Info
|
import com.topjohnwu.magisk.Info
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
|
import com.topjohnwu.magisk.base.BaseActivity
|
||||||
|
import com.topjohnwu.magisk.base.BaseFragment
|
||||||
import com.topjohnwu.magisk.databinding.ActivityMainBinding
|
import com.topjohnwu.magisk.databinding.ActivityMainBinding
|
||||||
|
import com.topjohnwu.magisk.extensions.addOnPropertyChangedCallback
|
||||||
|
import com.topjohnwu.magisk.extensions.snackbar
|
||||||
|
import com.topjohnwu.magisk.model.events.*
|
||||||
|
import com.topjohnwu.magisk.model.navigation.MagiskAnimBuilder
|
||||||
|
import com.topjohnwu.magisk.model.navigation.MagiskNavigationEvent
|
||||||
import com.topjohnwu.magisk.model.navigation.Navigation
|
import com.topjohnwu.magisk.model.navigation.Navigation
|
||||||
import com.topjohnwu.magisk.ui.base.MagiskActivity
|
import com.topjohnwu.magisk.model.navigation.Navigator
|
||||||
import com.topjohnwu.magisk.ui.hide.MagiskHideFragment
|
import com.topjohnwu.magisk.ui.hide.MagiskHideFragment
|
||||||
import com.topjohnwu.magisk.ui.home.HomeFragment
|
import com.topjohnwu.magisk.ui.home.HomeFragment
|
||||||
import com.topjohnwu.magisk.ui.log.LogFragment
|
import com.topjohnwu.magisk.ui.log.LogFragment
|
||||||
@ -23,15 +32,22 @@ import com.topjohnwu.magisk.ui.superuser.SuperuserFragment
|
|||||||
import com.topjohnwu.magisk.utils.Utils
|
import com.topjohnwu.magisk.utils.Utils
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||||
|
import timber.log.Timber
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
|
open class MainActivity : BaseActivity<MainViewModel, ActivityMainBinding>(), Navigator,
|
||||||
open class MainActivity : MagiskActivity<MainViewModel, ActivityMainBinding>() {
|
FragNavController.RootFragmentListener, FragNavController.TransactionListener {
|
||||||
|
|
||||||
override val layoutRes: Int = R.layout.activity_main
|
override val layoutRes: Int = R.layout.activity_main
|
||||||
override val viewModel: MainViewModel by viewModel()
|
override val viewModel: MainViewModel by viewModel()
|
||||||
override val navHostId: Int = R.id.main_nav_host
|
private val navHostId: Int = R.id.main_nav_host
|
||||||
override val defaultPosition: Int = 0
|
private val defaultPosition: Int = 0
|
||||||
|
|
||||||
|
private val navigationController by lazy {
|
||||||
|
FragNavController(supportFragmentManager, navHostId)
|
||||||
|
}
|
||||||
|
private val isRootFragment get() =
|
||||||
|
navigationController.currentStackIndex != defaultPosition
|
||||||
|
|
||||||
override val baseFragments: List<KClass<out Fragment>> = listOf(
|
override val baseFragments: List<KClass<out Fragment>> = listOf(
|
||||||
HomeFragment::class,
|
HomeFragment::class,
|
||||||
@ -43,10 +59,6 @@ open class MainActivity : MagiskActivity<MainViewModel, ActivityMainBinding>() {
|
|||||||
SettingsFragment::class
|
SettingsFragment::class
|
||||||
)
|
)
|
||||||
|
|
||||||
/*override fun getDarkTheme(): Int {
|
|
||||||
return R.style.AppTheme_Dark
|
|
||||||
}*/
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
if (!SplashActivity.DONE) {
|
if (!SplashActivity.DONE) {
|
||||||
startActivity(Intent(this, ClassMap[SplashActivity::class.java]))
|
startActivity(Intent(this, ClassMap[SplashActivity::class.java]))
|
||||||
@ -54,6 +66,13 @@ open class MainActivity : MagiskActivity<MainViewModel, ActivityMainBinding>() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
navigationController.apply {
|
||||||
|
rootFragmentListener = this@MainActivity
|
||||||
|
transactionListener = this@MainActivity
|
||||||
|
initialize(defaultPosition, savedInstanceState)
|
||||||
|
}
|
||||||
|
|
||||||
checkHideSection()
|
checkHideSection()
|
||||||
setSupportActionBar(binding.mainInclude.mainToolbar)
|
setSupportActionBar(binding.mainInclude.mainToolbar)
|
||||||
|
|
||||||
@ -68,6 +87,11 @@ open class MainActivity : MagiskActivity<MainViewModel, ActivityMainBinding>() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
|
super.onSaveInstanceState(outState)
|
||||||
|
navigationController.onSaveInstanceState(outState)
|
||||||
|
}
|
||||||
|
|
||||||
override fun setTitle(title: CharSequence?) {
|
override fun setTitle(title: CharSequence?) {
|
||||||
supportActionBar?.title = title
|
supportActionBar?.title = title
|
||||||
}
|
}
|
||||||
@ -76,25 +100,46 @@ open class MainActivity : MagiskActivity<MainViewModel, ActivityMainBinding>() {
|
|||||||
supportActionBar?.setTitle(titleId)
|
supportActionBar?.setTitle(titleId)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onTabTransaction(fragment: Fragment?, index: Int) {
|
|
||||||
val fragmentId = when (fragment) {
|
|
||||||
is HomeFragment -> R.id.magiskFragment
|
|
||||||
is SuperuserFragment -> R.id.superuserFragment
|
|
||||||
is MagiskHideFragment -> R.id.magiskHideFragment
|
|
||||||
is ModulesFragment -> R.id.modulesFragment
|
|
||||||
is ReposFragment -> R.id.reposFragment
|
|
||||||
is LogFragment -> R.id.logFragment
|
|
||||||
is SettingsFragment -> R.id.settings
|
|
||||||
else -> return
|
|
||||||
}
|
|
||||||
binding.navView.setCheckedItem(fragmentId)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onBackPressed() {
|
override fun onBackPressed() {
|
||||||
if (binding.drawerLayout.isDrawerOpen(binding.navView)) {
|
if (binding.drawerLayout.isDrawerOpen(binding.navView)) {
|
||||||
binding.drawerLayout.closeDrawer(binding.navView)
|
binding.drawerLayout.closeDrawer(binding.navView)
|
||||||
} else {
|
} else {
|
||||||
super.onBackPressed()
|
val fragment = navigationController.currentFrag as? BaseFragment<*, *>
|
||||||
|
|
||||||
|
if (fragment?.onBackPressed() == true) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
navigationController.popFragment()
|
||||||
|
} catch (e: UnsupportedOperationException) {
|
||||||
|
when {
|
||||||
|
isRootFragment -> {
|
||||||
|
val options = FragNavTransactionOptions.newBuilder()
|
||||||
|
.transition(FragmentTransaction.TRANSIT_FRAGMENT_CLOSE)
|
||||||
|
.build()
|
||||||
|
navigationController.switchTab(defaultPosition, options)
|
||||||
|
}
|
||||||
|
else -> super.onBackPressed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onEventDispatched(event: ViewEvent) {
|
||||||
|
super.onEventDispatched(event)
|
||||||
|
when (event) {
|
||||||
|
is SnackbarEvent -> snackbar(snackbarView, event.message(this), event.length, event.f)
|
||||||
|
is BackPressEvent -> onBackPressed()
|
||||||
|
is MagiskNavigationEvent -> navigateTo(event)
|
||||||
|
is ViewActionEvent -> event.action(this)
|
||||||
|
is PermissionEvent -> withPermissions(*event.permissions.toTypedArray()) {
|
||||||
|
onSuccess { event.callback.onNext(true) }
|
||||||
|
onFailure {
|
||||||
|
event.callback.onNext(false)
|
||||||
|
event.callback.onError(SecurityException("User refused permissions"))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,4 +165,84 @@ open class MainActivity : MagiskActivity<MainViewModel, ActivityMainBinding>() {
|
|||||||
menu.findItem(R.id.superuserFragment).isVisible =
|
menu.findItem(R.id.superuserFragment).isVisible =
|
||||||
Utils.showSuperUser()
|
Utils.showSuperUser()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun FragNavTransactionOptions.Builder.customAnimations(options: MagiskAnimBuilder) =
|
||||||
|
customAnimations(options.enter, options.exit, options.popEnter, options.popExit).apply {
|
||||||
|
if (!options.anySet) {
|
||||||
|
transition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override val numberOfRootFragments: Int get() = baseFragments.size
|
||||||
|
|
||||||
|
override fun getRootFragment(index: Int) = baseFragments[index].java.newInstance()
|
||||||
|
|
||||||
|
override fun onTabTransaction(fragment: Fragment?, index: Int) {
|
||||||
|
val fragmentId = when (fragment) {
|
||||||
|
is HomeFragment -> R.id.magiskFragment
|
||||||
|
is SuperuserFragment -> R.id.superuserFragment
|
||||||
|
is MagiskHideFragment -> R.id.magiskHideFragment
|
||||||
|
is ModulesFragment -> R.id.modulesFragment
|
||||||
|
is ReposFragment -> R.id.reposFragment
|
||||||
|
is LogFragment -> R.id.logFragment
|
||||||
|
is SettingsFragment -> R.id.settings
|
||||||
|
else -> return
|
||||||
|
}
|
||||||
|
binding.navView.setCheckedItem(fragmentId)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun navigateTo(event: MagiskNavigationEvent) {
|
||||||
|
val directions = event.navDirections
|
||||||
|
|
||||||
|
navigationController.defaultTransactionOptions = FragNavTransactionOptions.newBuilder()
|
||||||
|
.customAnimations(event.animOptions)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
navigationController.currentStack
|
||||||
|
?.indexOfFirst { it.javaClass == event.navOptions.popUpTo }
|
||||||
|
?.let { if (it == -1) null else it } // invalidate if class is not found
|
||||||
|
?.let { if (event.navOptions.inclusive) it + 1 else it }
|
||||||
|
?.let { navigationController.popFragments(it) }
|
||||||
|
|
||||||
|
when (directions.isActivity) {
|
||||||
|
true -> navigateToActivity(event)
|
||||||
|
else -> navigateToFragment(event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun navigateToActivity(event: MagiskNavigationEvent) {
|
||||||
|
val destination = event.navDirections.destination?.java ?: let {
|
||||||
|
Timber.e("Cannot navigate to null destination")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val options = event.navOptions
|
||||||
|
|
||||||
|
Intent(this, destination)
|
||||||
|
.putExtras(event.navDirections.args)
|
||||||
|
.apply {
|
||||||
|
if (options.singleTop) addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
|
||||||
|
if (options.clearTask) addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
|
||||||
|
}
|
||||||
|
.let { startActivity(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun navigateToFragment(event: MagiskNavigationEvent) {
|
||||||
|
val destination = event.navDirections.destination?.java ?: let {
|
||||||
|
Timber.e("Cannot navigate to null destination")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
when (val index = baseFragments.indexOfFirst { it.java.name == destination.name }) {
|
||||||
|
-1 -> destination.newInstance()
|
||||||
|
.apply { arguments = event.navDirections.args }
|
||||||
|
.let { navigationController.pushFragment(it) }
|
||||||
|
// When it's desired that fragments of same class are put on top of one another edit this
|
||||||
|
else -> navigationController.switchTab(index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFragmentTransaction(
|
||||||
|
fragment: Fragment?,
|
||||||
|
transactionType: FragNavController.TransactionType
|
||||||
|
) = Unit
|
||||||
}
|
}
|
||||||
|
@ -2,11 +2,11 @@ package com.topjohnwu.magisk.ui
|
|||||||
|
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
|
import com.topjohnwu.magisk.base.viewmodel.BaseViewModel
|
||||||
import com.topjohnwu.magisk.model.navigation.Navigation
|
import com.topjohnwu.magisk.model.navigation.Navigation
|
||||||
import com.topjohnwu.magisk.ui.base.MagiskViewModel
|
|
||||||
|
|
||||||
|
|
||||||
class MainViewModel : MagiskViewModel() {
|
class MainViewModel : BaseViewModel() {
|
||||||
|
|
||||||
fun navPressed() = Navigation.Main.OPEN_NAV.publish()
|
fun navPressed() = Navigation.Main.OPEN_NAV.publish()
|
||||||
|
|
||||||
|
@ -1,17 +1,24 @@
|
|||||||
package com.topjohnwu.magisk.ui
|
package com.topjohnwu.magisk.ui
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.text.TextUtils
|
import android.text.TextUtils
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
|
||||||
import com.topjohnwu.magisk.*
|
import com.topjohnwu.magisk.*
|
||||||
import com.topjohnwu.magisk.model.navigation.Navigation
|
import com.topjohnwu.magisk.model.navigation.Navigation
|
||||||
import com.topjohnwu.magisk.utils.Utils
|
import com.topjohnwu.magisk.utils.Utils
|
||||||
|
import com.topjohnwu.magisk.utils.wrap
|
||||||
import com.topjohnwu.magisk.view.Notifications
|
import com.topjohnwu.magisk.view.Notifications
|
||||||
import com.topjohnwu.magisk.view.Shortcuts
|
import com.topjohnwu.magisk.view.Shortcuts
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
|
|
||||||
open class SplashActivity : AppCompatActivity() {
|
open class SplashActivity : Activity() {
|
||||||
|
|
||||||
|
override fun attachBaseContext(base: Context) {
|
||||||
|
super.attachBaseContext(base.wrap())
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
@ -1,62 +0,0 @@
|
|||||||
package com.topjohnwu.magisk.ui.base
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.content.SharedPreferences
|
|
||||||
import android.os.Build
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import androidx.core.view.children
|
|
||||||
import androidx.core.view.isVisible
|
|
||||||
import androidx.preference.*
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
import com.topjohnwu.magisk.R
|
|
||||||
import org.koin.android.ext.android.inject
|
|
||||||
|
|
||||||
abstract class BasePreferenceFragment : PreferenceFragmentCompat(),
|
|
||||||
SharedPreferences.OnSharedPreferenceChangeListener {
|
|
||||||
|
|
||||||
protected val prefs: SharedPreferences by inject()
|
|
||||||
protected val activity get() = requireActivity() as MagiskActivity<*, *>
|
|
||||||
|
|
||||||
override fun onCreateView(
|
|
||||||
inflater: LayoutInflater,
|
|
||||||
container: ViewGroup?,
|
|
||||||
savedInstanceState: Bundle?
|
|
||||||
): View? {
|
|
||||||
val v = super.onCreateView(inflater, container, savedInstanceState)
|
|
||||||
prefs.registerOnSharedPreferenceChangeListener(this)
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDestroyView() {
|
|
||||||
prefs.unregisterOnSharedPreferenceChangeListener(this)
|
|
||||||
super.onDestroyView()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateAdapter(preferenceScreen: PreferenceScreen): RecyclerView.Adapter<*> {
|
|
||||||
return object : PreferenceGroupAdapter(preferenceScreen) {
|
|
||||||
@SuppressLint("RestrictedApi")
|
|
||||||
override fun onBindViewHolder(holder: PreferenceViewHolder, position: Int) {
|
|
||||||
super.onBindViewHolder(holder, position)
|
|
||||||
when (val preference = getItem(position)) {
|
|
||||||
is PreferenceCategory -> setZeroPaddingToLayoutChildren(holder.itemView)
|
|
||||||
else -> holder.itemView.findViewById<View>(R.id.icon_frame)?.isVisible =
|
|
||||||
preference.icon != null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setZeroPaddingToLayoutChildren(view: View) {
|
|
||||||
(view as? ViewGroup)?.children?.forEach {
|
|
||||||
setZeroPaddingToLayoutChildren(it)
|
|
||||||
} ?: return
|
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1)
|
|
||||||
view.setPaddingRelative(0, view.paddingTop, view.paddingEnd, view.paddingBottom)
|
|
||||||
else
|
|
||||||
view.setPadding(0, view.paddingTop, view.paddingRight, view.paddingBottom)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,242 +0,0 @@
|
|||||||
package com.topjohnwu.magisk.ui.base
|
|
||||||
|
|
||||||
import android.Manifest
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
|
||||||
import android.content.res.Configuration
|
|
||||||
import android.os.Bundle
|
|
||||||
import androidx.annotation.CallSuper
|
|
||||||
import androidx.appcompat.app.AppCompatDelegate
|
|
||||||
import androidx.collection.SparseArrayCompat
|
|
||||||
import androidx.core.net.toUri
|
|
||||||
import androidx.databinding.ViewDataBinding
|
|
||||||
import androidx.fragment.app.Fragment
|
|
||||||
import androidx.fragment.app.FragmentTransaction
|
|
||||||
import com.karumi.dexter.Dexter
|
|
||||||
import com.karumi.dexter.MultiplePermissionsReport
|
|
||||||
import com.karumi.dexter.PermissionToken
|
|
||||||
import com.karumi.dexter.listener.PermissionRequest
|
|
||||||
import com.karumi.dexter.listener.multi.MultiplePermissionsListener
|
|
||||||
import com.ncapdevi.fragnav.FragNavController
|
|
||||||
import com.ncapdevi.fragnav.FragNavTransactionOptions
|
|
||||||
import com.skoumal.teanity.view.TeanityActivity
|
|
||||||
import com.skoumal.teanity.viewevents.ViewEvent
|
|
||||||
import com.topjohnwu.magisk.Config
|
|
||||||
import com.topjohnwu.magisk.extensions.set
|
|
||||||
import com.topjohnwu.magisk.model.events.BackPressEvent
|
|
||||||
import com.topjohnwu.magisk.model.events.PermissionEvent
|
|
||||||
import com.topjohnwu.magisk.model.events.ViewActionEvent
|
|
||||||
import com.topjohnwu.magisk.model.navigation.MagiskAnimBuilder
|
|
||||||
import com.topjohnwu.magisk.model.navigation.MagiskNavigationEvent
|
|
||||||
import com.topjohnwu.magisk.model.navigation.Navigator
|
|
||||||
import com.topjohnwu.magisk.model.permissions.PermissionRequestBuilder
|
|
||||||
import com.topjohnwu.magisk.utils.LocaleManager
|
|
||||||
import com.topjohnwu.magisk.utils.Utils
|
|
||||||
import com.topjohnwu.magisk.utils.currentLocale
|
|
||||||
import timber.log.Timber
|
|
||||||
import kotlin.reflect.KClass
|
|
||||||
|
|
||||||
typealias RequestCallback = MagiskActivity<*, *>.(Int, Intent?) -> Unit
|
|
||||||
|
|
||||||
abstract class MagiskActivity<ViewModel : MagiskViewModel, Binding : ViewDataBinding> :
|
|
||||||
TeanityActivity<ViewModel, Binding>(), FragNavController.RootFragmentListener,
|
|
||||||
Navigator, FragNavController.TransactionListener {
|
|
||||||
|
|
||||||
override val numberOfRootFragments: Int get() = baseFragments.size
|
|
||||||
override val baseFragments: List<KClass<out Fragment>> = listOf()
|
|
||||||
private val resultCallbacks = SparseArrayCompat<RequestCallback>()
|
|
||||||
|
|
||||||
|
|
||||||
protected open val defaultPosition: Int = 0
|
|
||||||
|
|
||||||
protected val navigationController get() = if (navHostId == 0) null else _navigationController
|
|
||||||
private val _navigationController by lazy {
|
|
||||||
if (navHostId == 0) throw IllegalStateException("Did you forget to override \"navHostId\"?")
|
|
||||||
FragNavController(supportFragmentManager, navHostId)
|
|
||||||
}
|
|
||||||
|
|
||||||
private val isRootFragment
|
|
||||||
get() = navigationController?.let { it.currentStackIndex != defaultPosition } ?: false
|
|
||||||
|
|
||||||
init {
|
|
||||||
val theme = if (Config.darkTheme) {
|
|
||||||
AppCompatDelegate.MODE_NIGHT_YES
|
|
||||||
} else {
|
|
||||||
AppCompatDelegate.MODE_NIGHT_NO
|
|
||||||
}
|
|
||||||
AppCompatDelegate.setDefaultNightMode(theme)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun applyOverrideConfiguration(config: Configuration?) {
|
|
||||||
// Force applying our preferred local
|
|
||||||
config?.setLocale(currentLocale)
|
|
||||||
super.applyOverrideConfiguration(config)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun attachBaseContext(base: Context) {
|
|
||||||
super.attachBaseContext(LocaleManager.getLocaleContext(base))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
navigationController?.apply {
|
|
||||||
rootFragmentListener = this@MagiskActivity
|
|
||||||
transactionListener = this@MagiskActivity
|
|
||||||
initialize(defaultPosition, savedInstanceState)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onSaveInstanceState(outState: Bundle) {
|
|
||||||
super.onSaveInstanceState(outState)
|
|
||||||
navigationController?.onSaveInstanceState(outState)
|
|
||||||
}
|
|
||||||
|
|
||||||
@CallSuper
|
|
||||||
override fun onEventDispatched(event: ViewEvent) {
|
|
||||||
super.onEventDispatched(event)
|
|
||||||
when (event) {
|
|
||||||
is BackPressEvent -> onBackPressed()
|
|
||||||
is MagiskNavigationEvent -> navigateTo(event)
|
|
||||||
is ViewActionEvent -> event.action(this)
|
|
||||||
is PermissionEvent -> withPermissions(*event.permissions.toTypedArray()) {
|
|
||||||
onSuccess { event.callback.onNext(true) }
|
|
||||||
onFailure {
|
|
||||||
event.callback.onNext(false)
|
|
||||||
event.callback.onError(SecurityException("User refused permissions"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getRootFragment(index: Int) = baseFragments[index].java.newInstance()
|
|
||||||
|
|
||||||
override fun navigateTo(event: MagiskNavigationEvent) {
|
|
||||||
val directions = event.navDirections
|
|
||||||
|
|
||||||
navigationController?.defaultTransactionOptions = FragNavTransactionOptions.newBuilder()
|
|
||||||
.customAnimations(event.animOptions)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
navigationController?.currentStack
|
|
||||||
?.indexOfFirst { it.javaClass == event.navOptions.popUpTo }
|
|
||||||
?.let { if (it == -1) null else it } // invalidate if class is not found
|
|
||||||
?.let { if (event.navOptions.inclusive) it + 1 else it }
|
|
||||||
?.let { navigationController?.popFragments(it) }
|
|
||||||
|
|
||||||
when (directions.isActivity) {
|
|
||||||
true -> navigateToActivity(event)
|
|
||||||
else -> navigateToFragment(event)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun navigateToActivity(event: MagiskNavigationEvent) {
|
|
||||||
val destination = event.navDirections.destination?.java ?: let {
|
|
||||||
Timber.e("Cannot navigate to null destination")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
val options = event.navOptions
|
|
||||||
|
|
||||||
Intent(this, destination)
|
|
||||||
.putExtras(event.navDirections.args)
|
|
||||||
.apply {
|
|
||||||
if (options.singleTop) addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
|
|
||||||
if (options.clearTask) addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
|
|
||||||
}
|
|
||||||
.let { startActivity(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun navigateToFragment(event: MagiskNavigationEvent) {
|
|
||||||
val destination = event.navDirections.destination?.java ?: let {
|
|
||||||
Timber.e("Cannot navigate to null destination")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
when (val index = baseFragments.indexOfFirst { it.java.name == destination.name }) {
|
|
||||||
-1 -> destination.newInstance()
|
|
||||||
.apply { arguments = event.navDirections.args }
|
|
||||||
.let { navigationController?.pushFragment(it) }
|
|
||||||
// When it's desired that fragments of same class are put on top of one another edit this
|
|
||||||
else -> navigationController?.switchTab(index)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onBackPressed() {
|
|
||||||
val fragment = navigationController?.currentFrag as? MagiskFragment<*, *>
|
|
||||||
|
|
||||||
if (fragment?.onBackPressed() == true) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
navigationController?.popFragment() ?: throw UnsupportedOperationException()
|
|
||||||
} catch (e: UnsupportedOperationException) {
|
|
||||||
when {
|
|
||||||
isRootFragment -> {
|
|
||||||
val options = FragNavTransactionOptions.newBuilder()
|
|
||||||
.transition(FragmentTransaction.TRANSIT_FRAGMENT_CLOSE)
|
|
||||||
.build()
|
|
||||||
navigationController?.switchTab(defaultPosition, options)
|
|
||||||
}
|
|
||||||
else -> super.onBackPressed()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onFragmentTransaction(
|
|
||||||
fragment: Fragment?,
|
|
||||||
transactionType: FragNavController.TransactionType
|
|
||||||
) = Unit
|
|
||||||
|
|
||||||
override fun onTabTransaction(fragment: Fragment?, index: Int) = Unit
|
|
||||||
|
|
||||||
fun openUrl(url: String) = Utils.openLink(this, url.toUri())
|
|
||||||
|
|
||||||
fun withPermissions(vararg permissions: String, builder: PermissionRequestBuilder.() -> Unit) {
|
|
||||||
val request = PermissionRequestBuilder().apply(builder).build()
|
|
||||||
Dexter.withActivity(this)
|
|
||||||
.withPermissions(*permissions)
|
|
||||||
.withListener(object : MultiplePermissionsListener {
|
|
||||||
override fun onPermissionsChecked(report: MultiplePermissionsReport) {
|
|
||||||
if (report.areAllPermissionsGranted()) {
|
|
||||||
request.onSuccess()
|
|
||||||
} else {
|
|
||||||
request.onFailure()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onPermissionRationaleShouldBeShown(
|
|
||||||
permissions: MutableList<PermissionRequest>,
|
|
||||||
token: PermissionToken
|
|
||||||
) = token.continuePermissionRequest()
|
|
||||||
}).check()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun withExternalRW(builder: PermissionRequestBuilder.() -> Unit) {
|
|
||||||
withPermissions(Manifest.permission.WRITE_EXTERNAL_STORAGE, builder = builder)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun FragNavTransactionOptions.Builder.customAnimations(options: MagiskAnimBuilder) =
|
|
||||||
customAnimations(options.enter, options.exit, options.popEnter, options.popExit).apply {
|
|
||||||
if (!options.anySet) {
|
|
||||||
transition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
|
||||||
super.onActivityResult(requestCode, resultCode, data)
|
|
||||||
resultCallbacks[requestCode]?.apply {
|
|
||||||
resultCallbacks.remove(requestCode)
|
|
||||||
invoke(this@MagiskActivity, resultCode, data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun startActivityForResult(
|
|
||||||
intent: Intent,
|
|
||||||
requestCode: Int,
|
|
||||||
listener: RequestCallback
|
|
||||||
) {
|
|
||||||
resultCallbacks[requestCode] = listener
|
|
||||||
startActivityForResult(intent, requestCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,52 +0,0 @@
|
|||||||
package com.topjohnwu.magisk.ui.base
|
|
||||||
|
|
||||||
import androidx.annotation.CallSuper
|
|
||||||
import androidx.databinding.ViewDataBinding
|
|
||||||
import androidx.fragment.app.Fragment
|
|
||||||
import com.skoumal.teanity.view.TeanityFragment
|
|
||||||
import com.skoumal.teanity.viewevents.ViewEvent
|
|
||||||
import com.topjohnwu.magisk.model.events.BackPressEvent
|
|
||||||
import com.topjohnwu.magisk.model.events.PermissionEvent
|
|
||||||
import com.topjohnwu.magisk.model.events.ViewActionEvent
|
|
||||||
import com.topjohnwu.magisk.model.navigation.MagiskNavigationEvent
|
|
||||||
import com.topjohnwu.magisk.model.navigation.Navigator
|
|
||||||
import com.topjohnwu.magisk.model.permissions.PermissionRequestBuilder
|
|
||||||
import kotlin.reflect.KClass
|
|
||||||
|
|
||||||
|
|
||||||
abstract class MagiskFragment<ViewModel : MagiskViewModel, Binding : ViewDataBinding> :
|
|
||||||
TeanityFragment<ViewModel, Binding>(), Navigator {
|
|
||||||
|
|
||||||
protected val activity get() = requireActivity() as MagiskActivity<*, *>
|
|
||||||
|
|
||||||
// We don't need nested fragments
|
|
||||||
override val baseFragments: List<KClass<Fragment>> = listOf()
|
|
||||||
|
|
||||||
override fun navigateTo(event: MagiskNavigationEvent) = activity.navigateTo(event)
|
|
||||||
|
|
||||||
@CallSuper
|
|
||||||
override fun onEventDispatched(event: ViewEvent) {
|
|
||||||
super.onEventDispatched(event)
|
|
||||||
when (event) {
|
|
||||||
is BackPressEvent -> activity.onBackPressed()
|
|
||||||
is MagiskNavigationEvent -> navigateTo(event)
|
|
||||||
is ViewActionEvent -> event.action(requireActivity())
|
|
||||||
is PermissionEvent -> activity.withPermissions(*event.permissions.toTypedArray()) {
|
|
||||||
onSuccess { event.callback.onNext(true) }
|
|
||||||
onFailure {
|
|
||||||
event.callback.onNext(false)
|
|
||||||
event.callback.onError(SecurityException("User refused permissions"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun withPermissions(vararg permissions: String, builder: PermissionRequestBuilder.() -> Unit) {
|
|
||||||
activity.withPermissions(*permissions, builder = builder)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun openLink(url: String) = activity.openUrl(url)
|
|
||||||
|
|
||||||
open fun onBackPressed(): Boolean = false
|
|
||||||
|
|
||||||
}
|
|
@ -9,15 +9,21 @@ import androidx.core.net.toUri
|
|||||||
import com.topjohnwu.magisk.ClassMap
|
import com.topjohnwu.magisk.ClassMap
|
||||||
import com.topjohnwu.magisk.Const
|
import com.topjohnwu.magisk.Const
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
|
import com.topjohnwu.magisk.base.BaseActivity
|
||||||
import com.topjohnwu.magisk.databinding.ActivityFlashBinding
|
import com.topjohnwu.magisk.databinding.ActivityFlashBinding
|
||||||
import com.topjohnwu.magisk.ui.base.MagiskActivity
|
import com.topjohnwu.magisk.extensions.snackbar
|
||||||
|
import com.topjohnwu.magisk.model.events.BackPressEvent
|
||||||
|
import com.topjohnwu.magisk.model.events.PermissionEvent
|
||||||
|
import com.topjohnwu.magisk.model.events.SnackbarEvent
|
||||||
|
import com.topjohnwu.magisk.model.events.ViewEvent
|
||||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||||
import org.koin.core.parameter.parametersOf
|
import org.koin.core.parameter.parametersOf
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
open class FlashActivity : MagiskActivity<FlashViewModel, ActivityFlashBinding>() {
|
open class FlashActivity : BaseActivity<FlashViewModel, ActivityFlashBinding>() {
|
||||||
|
|
||||||
override val layoutRes: Int = R.layout.activity_flash
|
override val layoutRes: Int = R.layout.activity_flash
|
||||||
|
override val themeRes: Int = R.style.MagiskTheme_Flashing
|
||||||
override val viewModel: FlashViewModel by viewModel {
|
override val viewModel: FlashViewModel by viewModel {
|
||||||
val uri = intent.data ?: let { finish(); Uri.EMPTY }
|
val uri = intent.data ?: let { finish(); Uri.EMPTY }
|
||||||
val additionalUri = intent.getParcelableExtra(Const.Key.FLASH_DATA) ?: uri
|
val additionalUri = intent.getParcelableExtra(Const.Key.FLASH_DATA) ?: uri
|
||||||
@ -37,6 +43,21 @@ open class FlashActivity : MagiskActivity<FlashViewModel, ActivityFlashBinding>(
|
|||||||
super.onBackPressed()
|
super.onBackPressed()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onEventDispatched(event: ViewEvent) {
|
||||||
|
super.onEventDispatched(event)
|
||||||
|
when (event) {
|
||||||
|
is SnackbarEvent -> snackbar(snackbarView, event.message(this), event.length, event.f)
|
||||||
|
is BackPressEvent -> onBackPressed()
|
||||||
|
is PermissionEvent -> withPermissions(*event.permissions.toTypedArray()) {
|
||||||
|
onSuccess { event.callback.onNext(true) }
|
||||||
|
onFailure {
|
||||||
|
event.callback.onNext(false)
|
||||||
|
event.callback.onError(SecurityException("User refused permissions"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
private fun intent(context: Context) = Intent(context, ClassMap[FlashActivity::class.java])
|
private fun intent(context: Context) = Intent(context, ClassMap[FlashActivity::class.java])
|
||||||
|
@ -7,21 +7,20 @@ import android.net.Uri
|
|||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import androidx.core.os.postDelayed
|
import androidx.core.os.postDelayed
|
||||||
import androidx.databinding.ObservableArrayList
|
import androidx.databinding.ObservableArrayList
|
||||||
import com.skoumal.teanity.databinding.ComparableRvItem
|
|
||||||
import com.skoumal.teanity.extensions.subscribeK
|
|
||||||
import com.skoumal.teanity.util.DiffObservableList
|
|
||||||
import com.skoumal.teanity.util.KObservableField
|
|
||||||
import com.skoumal.teanity.viewevents.SnackbarEvent
|
|
||||||
import com.topjohnwu.magisk.BR
|
import com.topjohnwu.magisk.BR
|
||||||
import com.topjohnwu.magisk.Config
|
import com.topjohnwu.magisk.Config
|
||||||
import com.topjohnwu.magisk.Const
|
import com.topjohnwu.magisk.Const
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
|
import com.topjohnwu.magisk.base.viewmodel.BaseViewModel
|
||||||
|
import com.topjohnwu.magisk.databinding.ComparableRvItem
|
||||||
import com.topjohnwu.magisk.extensions.*
|
import com.topjohnwu.magisk.extensions.*
|
||||||
import com.topjohnwu.magisk.model.entity.recycler.ConsoleRvItem
|
import com.topjohnwu.magisk.model.entity.recycler.ConsoleRvItem
|
||||||
|
import com.topjohnwu.magisk.model.events.SnackbarEvent
|
||||||
import com.topjohnwu.magisk.model.flash.FlashResultListener
|
import com.topjohnwu.magisk.model.flash.FlashResultListener
|
||||||
import com.topjohnwu.magisk.model.flash.Flashing
|
import com.topjohnwu.magisk.model.flash.Flashing
|
||||||
import com.topjohnwu.magisk.model.flash.Patching
|
import com.topjohnwu.magisk.model.flash.Patching
|
||||||
import com.topjohnwu.magisk.ui.base.MagiskViewModel
|
import com.topjohnwu.magisk.utils.DiffObservableList
|
||||||
|
import com.topjohnwu.magisk.utils.KObservableField
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
import me.tatarka.bindingcollectionadapter2.ItemBinding
|
import me.tatarka.bindingcollectionadapter2.ItemBinding
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@ -32,7 +31,7 @@ class FlashViewModel(
|
|||||||
installer: Uri,
|
installer: Uri,
|
||||||
uri: Uri,
|
uri: Uri,
|
||||||
private val resources: Resources
|
private val resources: Resources
|
||||||
) : MagiskViewModel(), FlashResultListener {
|
) : BaseViewModel(), FlashResultListener {
|
||||||
|
|
||||||
val canShowReboot = Shell.rootAccess()
|
val canShowReboot = Shell.rootAccess()
|
||||||
val showRestartTitle = KObservableField(false)
|
val showRestartTitle = KObservableField(false)
|
||||||
|
@ -1,28 +1,28 @@
|
|||||||
package com.topjohnwu.magisk.ui.hide
|
package com.topjohnwu.magisk.ui.hide
|
||||||
|
|
||||||
import android.content.pm.ApplicationInfo
|
import android.content.pm.ApplicationInfo
|
||||||
import com.skoumal.teanity.databinding.ComparableRvItem
|
|
||||||
import com.skoumal.teanity.extensions.addOnPropertyChangedCallback
|
|
||||||
import com.skoumal.teanity.extensions.subscribeK
|
|
||||||
import com.skoumal.teanity.rxbus.RxBus
|
|
||||||
import com.skoumal.teanity.util.DiffObservableList
|
|
||||||
import com.skoumal.teanity.util.KObservableField
|
|
||||||
import com.topjohnwu.magisk.BR
|
import com.topjohnwu.magisk.BR
|
||||||
|
import com.topjohnwu.magisk.base.viewmodel.BaseViewModel
|
||||||
import com.topjohnwu.magisk.data.repository.MagiskRepository
|
import com.topjohnwu.magisk.data.repository.MagiskRepository
|
||||||
|
import com.topjohnwu.magisk.databinding.ComparableRvItem
|
||||||
|
import com.topjohnwu.magisk.extensions.addOnPropertyChangedCallback
|
||||||
|
import com.topjohnwu.magisk.extensions.subscribeK
|
||||||
import com.topjohnwu.magisk.extensions.toSingle
|
import com.topjohnwu.magisk.extensions.toSingle
|
||||||
import com.topjohnwu.magisk.extensions.update
|
import com.topjohnwu.magisk.extensions.update
|
||||||
import com.topjohnwu.magisk.model.entity.recycler.HideProcessRvItem
|
import com.topjohnwu.magisk.model.entity.recycler.HideProcessRvItem
|
||||||
import com.topjohnwu.magisk.model.entity.recycler.HideRvItem
|
import com.topjohnwu.magisk.model.entity.recycler.HideRvItem
|
||||||
import com.topjohnwu.magisk.model.entity.state.IndeterminateState
|
import com.topjohnwu.magisk.model.entity.state.IndeterminateState
|
||||||
import com.topjohnwu.magisk.model.events.HideProcessEvent
|
import com.topjohnwu.magisk.model.events.HideProcessEvent
|
||||||
import com.topjohnwu.magisk.ui.base.MagiskViewModel
|
import com.topjohnwu.magisk.utils.DiffObservableList
|
||||||
|
import com.topjohnwu.magisk.utils.KObservableField
|
||||||
|
import com.topjohnwu.magisk.utils.RxBus
|
||||||
import me.tatarka.bindingcollectionadapter2.OnItemBind
|
import me.tatarka.bindingcollectionadapter2.OnItemBind
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
class HideViewModel(
|
class HideViewModel(
|
||||||
private val magiskRepo: MagiskRepository,
|
private val magiskRepo: MagiskRepository,
|
||||||
rxBus: RxBus
|
rxBus: RxBus
|
||||||
) : MagiskViewModel() {
|
) : BaseViewModel() {
|
||||||
|
|
||||||
val query = KObservableField("")
|
val query = KObservableField("")
|
||||||
val isShowSystem = KObservableField(false)
|
val isShowSystem = KObservableField(false)
|
||||||
|
@ -6,11 +6,11 @@ import android.view.MenuItem
|
|||||||
import android.widget.SearchView
|
import android.widget.SearchView
|
||||||
import com.topjohnwu.magisk.Config
|
import com.topjohnwu.magisk.Config
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
|
import com.topjohnwu.magisk.base.BaseFragment
|
||||||
import com.topjohnwu.magisk.databinding.FragmentMagiskHideBinding
|
import com.topjohnwu.magisk.databinding.FragmentMagiskHideBinding
|
||||||
import com.topjohnwu.magisk.ui.base.MagiskFragment
|
|
||||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||||
|
|
||||||
class MagiskHideFragment : MagiskFragment<HideViewModel, FragmentMagiskHideBinding>(),
|
class MagiskHideFragment : BaseFragment<HideViewModel, FragmentMagiskHideBinding>(),
|
||||||
SearchView.OnQueryTextListener {
|
SearchView.OnQueryTextListener {
|
||||||
|
|
||||||
override val layoutRes: Int = R.layout.fragment_magisk_hide
|
override val layoutRes: Int = R.layout.fragment_magisk_hide
|
||||||
|
@ -1,31 +1,31 @@
|
|||||||
package com.topjohnwu.magisk.ui.home
|
package com.topjohnwu.magisk.ui.home
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import com.skoumal.teanity.extensions.subscribeK
|
|
||||||
import com.skoumal.teanity.viewevents.ViewEvent
|
|
||||||
import com.topjohnwu.magisk.BuildConfig
|
import com.topjohnwu.magisk.BuildConfig
|
||||||
import com.topjohnwu.magisk.Const
|
import com.topjohnwu.magisk.Const
|
||||||
import com.topjohnwu.magisk.Info
|
import com.topjohnwu.magisk.Info
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
|
import com.topjohnwu.magisk.base.BaseActivity
|
||||||
|
import com.topjohnwu.magisk.base.BaseFragment
|
||||||
import com.topjohnwu.magisk.data.repository.MagiskRepository
|
import com.topjohnwu.magisk.data.repository.MagiskRepository
|
||||||
import com.topjohnwu.magisk.databinding.FragmentMagiskBinding
|
import com.topjohnwu.magisk.databinding.FragmentMagiskBinding
|
||||||
import com.topjohnwu.magisk.extensions.inject
|
import com.topjohnwu.magisk.extensions.DynamicClassLoader
|
||||||
|
import com.topjohnwu.magisk.extensions.openUrl
|
||||||
|
import com.topjohnwu.magisk.extensions.subscribeK
|
||||||
import com.topjohnwu.magisk.extensions.writeTo
|
import com.topjohnwu.magisk.extensions.writeTo
|
||||||
import com.topjohnwu.magisk.model.events.*
|
import com.topjohnwu.magisk.model.events.*
|
||||||
import com.topjohnwu.magisk.ui.base.MagiskActivity
|
|
||||||
import com.topjohnwu.magisk.ui.base.MagiskFragment
|
|
||||||
import com.topjohnwu.magisk.utils.DynamicClassLoader
|
|
||||||
import com.topjohnwu.magisk.utils.SafetyNetHelper
|
import com.topjohnwu.magisk.utils.SafetyNetHelper
|
||||||
import com.topjohnwu.magisk.view.MarkDownWindow
|
import com.topjohnwu.magisk.view.MarkDownWindow
|
||||||
import com.topjohnwu.magisk.view.dialogs.*
|
import com.topjohnwu.magisk.view.dialogs.*
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
import dalvik.system.DexFile
|
import dalvik.system.DexFile
|
||||||
import io.reactivex.Completable
|
import io.reactivex.Completable
|
||||||
|
import org.koin.android.ext.android.inject
|
||||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.lang.reflect.InvocationHandler
|
import java.lang.reflect.InvocationHandler
|
||||||
|
|
||||||
class HomeFragment : MagiskFragment<HomeViewModel, FragmentMagiskBinding>(),
|
class HomeFragment : BaseFragment<HomeViewModel, FragmentMagiskBinding>(),
|
||||||
SafetyNetHelper.Callback {
|
SafetyNetHelper.Callback {
|
||||||
|
|
||||||
override val layoutRes: Int = R.layout.fragment_magisk
|
override val layoutRes: Int = R.layout.fragment_magisk
|
||||||
@ -33,13 +33,14 @@ class HomeFragment : MagiskFragment<HomeViewModel, FragmentMagiskBinding>(),
|
|||||||
|
|
||||||
private val magiskRepo: MagiskRepository by inject()
|
private val magiskRepo: MagiskRepository by inject()
|
||||||
private val EXT_APK by lazy { File("${activity.filesDir.parent}/snet", "snet.jar") }
|
private val EXT_APK by lazy { File("${activity.filesDir.parent}/snet", "snet.jar") }
|
||||||
|
private val EXT_DEX by lazy { File(EXT_APK.parent, "snet.dex") }
|
||||||
|
|
||||||
override fun onResponse(responseCode: Int) = viewModel.finishSafetyNetCheck(responseCode)
|
override fun onResponse(responseCode: Int) = viewModel.finishSafetyNetCheck(responseCode)
|
||||||
|
|
||||||
override fun onEventDispatched(event: ViewEvent) {
|
override fun onEventDispatched(event: ViewEvent) {
|
||||||
super.onEventDispatched(event)
|
super.onEventDispatched(event)
|
||||||
when (event) {
|
when (event) {
|
||||||
is OpenLinkEvent -> openLink(event.url)
|
is OpenLinkEvent -> activity.openUrl(event.url)
|
||||||
is ManagerInstallEvent -> installManager()
|
is ManagerInstallEvent -> installManager()
|
||||||
is MagiskInstallEvent -> installMagisk()
|
is MagiskInstallEvent -> installMagisk()
|
||||||
is UninstallEvent -> uninstall()
|
is UninstallEvent -> uninstall()
|
||||||
@ -62,7 +63,7 @@ class HomeFragment : MagiskFragment<HomeViewModel, FragmentMagiskBinding>(),
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
MagiskInstallDialog(requireActivity() as MagiskActivity<*, *>).show()
|
MagiskInstallDialog(requireActivity() as BaseActivity<*, *>).show()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun installManager() = ManagerInstallDialog(requireActivity()).show()
|
private fun installManager() = ManagerInstallDialog(requireActivity()).show()
|
||||||
@ -94,7 +95,7 @@ class HomeFragment : MagiskFragment<HomeViewModel, FragmentMagiskBinding>(),
|
|||||||
private fun updateSafetyNet(dieOnError: Boolean) {
|
private fun updateSafetyNet(dieOnError: Boolean) {
|
||||||
Completable.fromAction {
|
Completable.fromAction {
|
||||||
val loader = DynamicClassLoader(EXT_APK)
|
val loader = DynamicClassLoader(EXT_APK)
|
||||||
val dex = DexFile.loadDex(EXT_APK.path, EXT_APK.parent, 0)
|
val dex = DexFile.loadDex(EXT_APK.path, EXT_DEX.path, 0)
|
||||||
|
|
||||||
// Scan through the dex and find our helper class
|
// Scan through the dex and find our helper class
|
||||||
var helperClass: Class<*>? = null
|
var helperClass: Class<*>? = null
|
||||||
|
@ -1,21 +1,16 @@
|
|||||||
package com.topjohnwu.magisk.ui.home
|
package com.topjohnwu.magisk.ui.home
|
||||||
|
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import com.skoumal.teanity.extensions.addOnPropertyChangedCallback
|
|
||||||
import com.skoumal.teanity.extensions.doOnSubscribeUi
|
|
||||||
import com.skoumal.teanity.extensions.subscribeK
|
|
||||||
import com.skoumal.teanity.util.KObservableField
|
|
||||||
import com.topjohnwu.magisk.*
|
import com.topjohnwu.magisk.*
|
||||||
|
import com.topjohnwu.magisk.base.viewmodel.BaseViewModel
|
||||||
import com.topjohnwu.magisk.data.repository.MagiskRepository
|
import com.topjohnwu.magisk.data.repository.MagiskRepository
|
||||||
import com.topjohnwu.magisk.extensions.get
|
import com.topjohnwu.magisk.extensions.*
|
||||||
import com.topjohnwu.magisk.extensions.packageName
|
|
||||||
import com.topjohnwu.magisk.extensions.res
|
|
||||||
import com.topjohnwu.magisk.extensions.toggle
|
|
||||||
import com.topjohnwu.magisk.model.events.*
|
import com.topjohnwu.magisk.model.events.*
|
||||||
import com.topjohnwu.magisk.model.observer.Observer
|
import com.topjohnwu.magisk.model.observer.Observer
|
||||||
import com.topjohnwu.magisk.ui.base.MagiskViewModel
|
import com.topjohnwu.magisk.utils.KObservableField
|
||||||
import com.topjohnwu.magisk.utils.SafetyNetHelper
|
import com.topjohnwu.magisk.utils.SafetyNetHelper
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
|
import io.reactivex.Completable
|
||||||
|
|
||||||
enum class SafetyNetState {
|
enum class SafetyNetState {
|
||||||
LOADING, PASS, FAILED, IDLE
|
LOADING, PASS, FAILED, IDLE
|
||||||
@ -31,7 +26,7 @@ enum class MagiskItem {
|
|||||||
|
|
||||||
class HomeViewModel(
|
class HomeViewModel(
|
||||||
private val magiskRepo: MagiskRepository
|
private val magiskRepo: MagiskRepository
|
||||||
) : MagiskViewModel(State.LOADED) {
|
) : BaseViewModel(State.LOADED) {
|
||||||
|
|
||||||
val hasGMS = runCatching {
|
val hasGMS = runCatching {
|
||||||
get<PackageManager>().getPackageInfo("com.google.android.gms", 0); true
|
get<PackageManager>().getPackageInfo("com.google.android.gms", 0); true
|
||||||
@ -43,10 +38,7 @@ class HomeViewModel(
|
|||||||
val isKeepVerity = KObservableField(Info.keepVerity)
|
val isKeepVerity = KObservableField(Info.keepVerity)
|
||||||
val isRecovery = KObservableField(Info.recovery)
|
val isRecovery = KObservableField(Info.recovery)
|
||||||
|
|
||||||
private val _magiskState = KObservableField(MagiskState.LOADING)
|
val magiskState = KObservableField(MagiskState.LOADING)
|
||||||
val magiskState = Observer(_magiskState, isConnected) {
|
|
||||||
if (isConnected.value) _magiskState.value else MagiskState.UP_TO_DATE
|
|
||||||
}
|
|
||||||
val magiskStateText = Observer(magiskState) {
|
val magiskStateText = Observer(magiskState) {
|
||||||
when (magiskState.value) {
|
when (magiskState.value) {
|
||||||
MagiskState.NOT_INSTALLED -> R.string.magisk_version_error.res()
|
MagiskState.NOT_INSTALLED -> R.string.magisk_version_error.res()
|
||||||
@ -179,24 +171,28 @@ class HomeViewModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun refresh() {
|
fun refresh() {
|
||||||
refreshVersions()
|
|
||||||
|
|
||||||
magiskRepo.fetchUpdate()
|
|
||||||
.applyViewModel(this)
|
|
||||||
.doOnSubscribeUi {
|
|
||||||
_magiskState.value = MagiskState.LOADING
|
|
||||||
_managerState.value = MagiskState.LOADING
|
|
||||||
ctsState.value = SafetyNetState.IDLE
|
|
||||||
basicIntegrityState.value = SafetyNetState.IDLE
|
|
||||||
safetyNetTitle.value = R.string.safetyNet_check_text
|
|
||||||
}
|
|
||||||
.subscribeK {
|
|
||||||
updateSelf()
|
|
||||||
ensureEnv()
|
|
||||||
refreshVersions()
|
|
||||||
}
|
|
||||||
|
|
||||||
hasRoot.value = Shell.rootAccess()
|
hasRoot.value = Shell.rootAccess()
|
||||||
|
|
||||||
|
val fetchUpdate = if (isConnected.value)
|
||||||
|
magiskRepo.fetchUpdate().ignoreElement()
|
||||||
|
else
|
||||||
|
Completable.complete()
|
||||||
|
|
||||||
|
Completable.fromAction {
|
||||||
|
Info.loadMagiskInfo()
|
||||||
|
}.andThen(fetchUpdate)
|
||||||
|
.applyViewModel(this)
|
||||||
|
.doOnSubscribeUi {
|
||||||
|
magiskState.value = MagiskState.LOADING
|
||||||
|
_managerState.value = MagiskState.LOADING
|
||||||
|
ctsState.value = SafetyNetState.IDLE
|
||||||
|
basicIntegrityState.value = SafetyNetState.IDLE
|
||||||
|
safetyNetTitle.value = R.string.safetyNet_check_text
|
||||||
|
}.subscribeK {
|
||||||
|
updateSelf()
|
||||||
|
ensureEnv()
|
||||||
|
refreshVersions()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun refreshVersions() {
|
private fun refreshVersions() {
|
||||||
@ -211,7 +207,7 @@ class HomeViewModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun updateSelf() {
|
private fun updateSelf() {
|
||||||
_magiskState.value = when (Info.magiskVersionCode) {
|
magiskState.value = when (Info.magiskVersionCode) {
|
||||||
in Int.MIN_VALUE until 0 -> MagiskState.NOT_INSTALLED
|
in Int.MIN_VALUE until 0 -> MagiskState.NOT_INSTALLED
|
||||||
!in Info.remote.magisk.versionCode..Int.MAX_VALUE -> MagiskState.OBSOLETE
|
!in Info.remote.magisk.versionCode..Int.MAX_VALUE -> MagiskState.OBSOLETE
|
||||||
else -> MagiskState.UP_TO_DATE
|
else -> MagiskState.UP_TO_DATE
|
||||||
|
@ -6,14 +6,14 @@ import android.view.Menu
|
|||||||
import android.view.MenuInflater
|
import android.view.MenuInflater
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import com.skoumal.teanity.viewevents.ViewEvent
|
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
|
import com.topjohnwu.magisk.base.BaseFragment
|
||||||
import com.topjohnwu.magisk.databinding.FragmentLogBinding
|
import com.topjohnwu.magisk.databinding.FragmentLogBinding
|
||||||
import com.topjohnwu.magisk.model.events.PageChangedEvent
|
import com.topjohnwu.magisk.model.events.PageChangedEvent
|
||||||
import com.topjohnwu.magisk.ui.base.MagiskFragment
|
import com.topjohnwu.magisk.model.events.ViewEvent
|
||||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||||
|
|
||||||
class LogFragment : MagiskFragment<LogViewModel, FragmentLogBinding>() {
|
class LogFragment : BaseFragment<LogViewModel, FragmentLogBinding>() {
|
||||||
|
|
||||||
override val layoutRes: Int = R.layout.fragment_log
|
override val layoutRes: Int = R.layout.fragment_log
|
||||||
override val viewModel: LogViewModel by viewModel()
|
override val viewModel: LogViewModel by viewModel()
|
||||||
|
@ -1,25 +1,25 @@
|
|||||||
package com.topjohnwu.magisk.ui.log
|
package com.topjohnwu.magisk.ui.log
|
||||||
|
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
import com.skoumal.teanity.databinding.ComparableRvItem
|
|
||||||
import com.skoumal.teanity.extensions.addOnPropertyChangedCallback
|
|
||||||
import com.skoumal.teanity.extensions.doOnSubscribeUi
|
|
||||||
import com.skoumal.teanity.extensions.subscribeK
|
|
||||||
import com.skoumal.teanity.util.DiffObservableList
|
|
||||||
import com.skoumal.teanity.util.KObservableField
|
|
||||||
import com.skoumal.teanity.viewevents.SnackbarEvent
|
|
||||||
import com.topjohnwu.magisk.BR
|
import com.topjohnwu.magisk.BR
|
||||||
import com.topjohnwu.magisk.Config
|
import com.topjohnwu.magisk.Config
|
||||||
import com.topjohnwu.magisk.Const
|
import com.topjohnwu.magisk.Const
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
|
import com.topjohnwu.magisk.base.viewmodel.BaseViewModel
|
||||||
import com.topjohnwu.magisk.data.repository.LogRepository
|
import com.topjohnwu.magisk.data.repository.LogRepository
|
||||||
|
import com.topjohnwu.magisk.databinding.ComparableRvItem
|
||||||
|
import com.topjohnwu.magisk.extensions.addOnPropertyChangedCallback
|
||||||
|
import com.topjohnwu.magisk.extensions.doOnSubscribeUi
|
||||||
|
import com.topjohnwu.magisk.extensions.subscribeK
|
||||||
import com.topjohnwu.magisk.model.binding.BindingAdapter
|
import com.topjohnwu.magisk.model.binding.BindingAdapter
|
||||||
import com.topjohnwu.magisk.model.entity.recycler.ConsoleRvItem
|
import com.topjohnwu.magisk.model.entity.recycler.ConsoleRvItem
|
||||||
import com.topjohnwu.magisk.model.entity.recycler.LogItemRvItem
|
import com.topjohnwu.magisk.model.entity.recycler.LogItemRvItem
|
||||||
import com.topjohnwu.magisk.model.entity.recycler.LogRvItem
|
import com.topjohnwu.magisk.model.entity.recycler.LogRvItem
|
||||||
import com.topjohnwu.magisk.model.entity.recycler.MagiskLogRvItem
|
import com.topjohnwu.magisk.model.entity.recycler.MagiskLogRvItem
|
||||||
import com.topjohnwu.magisk.model.events.PageChangedEvent
|
import com.topjohnwu.magisk.model.events.PageChangedEvent
|
||||||
import com.topjohnwu.magisk.ui.base.MagiskViewModel
|
import com.topjohnwu.magisk.model.events.SnackbarEvent
|
||||||
|
import com.topjohnwu.magisk.utils.DiffObservableList
|
||||||
|
import com.topjohnwu.magisk.utils.KObservableField
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
import me.tatarka.bindingcollectionadapter2.BindingViewPagerAdapter
|
import me.tatarka.bindingcollectionadapter2.BindingViewPagerAdapter
|
||||||
import me.tatarka.bindingcollectionadapter2.OnItemBind
|
import me.tatarka.bindingcollectionadapter2.OnItemBind
|
||||||
@ -30,7 +30,7 @@ import java.util.*
|
|||||||
class LogViewModel(
|
class LogViewModel(
|
||||||
private val resources: Resources,
|
private val resources: Resources,
|
||||||
private val logRepo: LogRepository
|
private val logRepo: LogRepository
|
||||||
) : MagiskViewModel(), BindingViewPagerAdapter.PageTitles<ComparableRvItem<*>> {
|
) : BaseViewModel(), BindingViewPagerAdapter.PageTitles<ComparableRvItem<*>> {
|
||||||
|
|
||||||
val itemsAdapter = BindingAdapter()
|
val itemsAdapter = BindingAdapter()
|
||||||
val items = DiffObservableList(ComparableRvItem.callback)
|
val items = DiffObservableList(ComparableRvItem.callback)
|
||||||
|
@ -1,17 +1,12 @@
|
|||||||
package com.topjohnwu.magisk.ui.module
|
package com.topjohnwu.magisk.ui.module
|
||||||
|
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
import com.skoumal.teanity.databinding.ComparableRvItem
|
|
||||||
import com.skoumal.teanity.extensions.addOnPropertyChangedCallback
|
|
||||||
import com.skoumal.teanity.extensions.doOnSuccessUi
|
|
||||||
import com.skoumal.teanity.extensions.subscribeK
|
|
||||||
import com.skoumal.teanity.util.DiffObservableList
|
|
||||||
import com.skoumal.teanity.util.KObservableField
|
|
||||||
import com.topjohnwu.magisk.BR
|
import com.topjohnwu.magisk.BR
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
|
import com.topjohnwu.magisk.base.viewmodel.BaseViewModel
|
||||||
import com.topjohnwu.magisk.data.database.RepoDao
|
import com.topjohnwu.magisk.data.database.RepoDao
|
||||||
import com.topjohnwu.magisk.extensions.toSingle
|
import com.topjohnwu.magisk.databinding.ComparableRvItem
|
||||||
import com.topjohnwu.magisk.extensions.update
|
import com.topjohnwu.magisk.extensions.*
|
||||||
import com.topjohnwu.magisk.model.entity.module.Module
|
import com.topjohnwu.magisk.model.entity.module.Module
|
||||||
import com.topjohnwu.magisk.model.entity.recycler.ModuleRvItem
|
import com.topjohnwu.magisk.model.entity.recycler.ModuleRvItem
|
||||||
import com.topjohnwu.magisk.model.entity.recycler.RepoRvItem
|
import com.topjohnwu.magisk.model.entity.recycler.RepoRvItem
|
||||||
@ -20,7 +15,8 @@ import com.topjohnwu.magisk.model.events.InstallModuleEvent
|
|||||||
import com.topjohnwu.magisk.model.events.OpenChangelogEvent
|
import com.topjohnwu.magisk.model.events.OpenChangelogEvent
|
||||||
import com.topjohnwu.magisk.model.events.OpenFilePickerEvent
|
import com.topjohnwu.magisk.model.events.OpenFilePickerEvent
|
||||||
import com.topjohnwu.magisk.tasks.RepoUpdater
|
import com.topjohnwu.magisk.tasks.RepoUpdater
|
||||||
import com.topjohnwu.magisk.ui.base.MagiskViewModel
|
import com.topjohnwu.magisk.utils.DiffObservableList
|
||||||
|
import com.topjohnwu.magisk.utils.KObservableField
|
||||||
import io.reactivex.Single
|
import io.reactivex.Single
|
||||||
import io.reactivex.disposables.Disposable
|
import io.reactivex.disposables.Disposable
|
||||||
import me.tatarka.bindingcollectionadapter2.OnItemBind
|
import me.tatarka.bindingcollectionadapter2.OnItemBind
|
||||||
@ -29,7 +25,7 @@ class ModuleViewModel(
|
|||||||
private val resources: Resources,
|
private val resources: Resources,
|
||||||
private val repoUpdater: RepoUpdater,
|
private val repoUpdater: RepoUpdater,
|
||||||
private val repoDB: RepoDao
|
private val repoDB: RepoDao
|
||||||
) : MagiskViewModel() {
|
) : BaseViewModel() {
|
||||||
|
|
||||||
val query = KObservableField("")
|
val query = KObservableField("")
|
||||||
|
|
||||||
|
@ -8,19 +8,19 @@ import android.view.MenuInflater
|
|||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.skoumal.teanity.viewevents.ViewEvent
|
|
||||||
import com.topjohnwu.magisk.ClassMap
|
import com.topjohnwu.magisk.ClassMap
|
||||||
import com.topjohnwu.magisk.Const
|
import com.topjohnwu.magisk.Const
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
|
import com.topjohnwu.magisk.base.BaseFragment
|
||||||
import com.topjohnwu.magisk.databinding.FragmentModulesBinding
|
import com.topjohnwu.magisk.databinding.FragmentModulesBinding
|
||||||
import com.topjohnwu.magisk.extensions.reboot
|
import com.topjohnwu.magisk.extensions.reboot
|
||||||
import com.topjohnwu.magisk.model.events.OpenFilePickerEvent
|
import com.topjohnwu.magisk.model.events.OpenFilePickerEvent
|
||||||
import com.topjohnwu.magisk.ui.base.MagiskFragment
|
import com.topjohnwu.magisk.model.events.ViewEvent
|
||||||
import com.topjohnwu.magisk.ui.flash.FlashActivity
|
import com.topjohnwu.magisk.ui.flash.FlashActivity
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
import org.koin.androidx.viewmodel.ext.android.sharedViewModel
|
import org.koin.androidx.viewmodel.ext.android.sharedViewModel
|
||||||
|
|
||||||
class ModulesFragment : MagiskFragment<ModuleViewModel, FragmentModulesBinding>() {
|
class ModulesFragment : BaseFragment<ModuleViewModel, FragmentModulesBinding>() {
|
||||||
|
|
||||||
override val layoutRes: Int = R.layout.fragment_modules
|
override val layoutRes: Int = R.layout.fragment_modules
|
||||||
override val viewModel: ModuleViewModel by sharedViewModel()
|
override val viewModel: ModuleViewModel by sharedViewModel()
|
||||||
|
@ -6,9 +6,9 @@ import android.view.Menu
|
|||||||
import android.view.MenuInflater
|
import android.view.MenuInflater
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.widget.SearchView
|
import android.widget.SearchView
|
||||||
import com.skoumal.teanity.viewevents.ViewEvent
|
|
||||||
import com.topjohnwu.magisk.Config
|
import com.topjohnwu.magisk.Config
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
|
import com.topjohnwu.magisk.base.BaseFragment
|
||||||
import com.topjohnwu.magisk.databinding.FragmentReposBinding
|
import com.topjohnwu.magisk.databinding.FragmentReposBinding
|
||||||
import com.topjohnwu.magisk.model.download.DownloadService
|
import com.topjohnwu.magisk.model.download.DownloadService
|
||||||
import com.topjohnwu.magisk.model.entity.internal.Configuration
|
import com.topjohnwu.magisk.model.entity.internal.Configuration
|
||||||
@ -16,12 +16,12 @@ import com.topjohnwu.magisk.model.entity.internal.DownloadSubject
|
|||||||
import com.topjohnwu.magisk.model.entity.module.Repo
|
import com.topjohnwu.magisk.model.entity.module.Repo
|
||||||
import com.topjohnwu.magisk.model.events.InstallModuleEvent
|
import com.topjohnwu.magisk.model.events.InstallModuleEvent
|
||||||
import com.topjohnwu.magisk.model.events.OpenChangelogEvent
|
import com.topjohnwu.magisk.model.events.OpenChangelogEvent
|
||||||
import com.topjohnwu.magisk.ui.base.MagiskFragment
|
import com.topjohnwu.magisk.model.events.ViewEvent
|
||||||
import com.topjohnwu.magisk.view.MarkDownWindow
|
import com.topjohnwu.magisk.view.MarkDownWindow
|
||||||
import com.topjohnwu.magisk.view.dialogs.CustomAlertDialog
|
import com.topjohnwu.magisk.view.dialogs.CustomAlertDialog
|
||||||
import org.koin.androidx.viewmodel.ext.android.sharedViewModel
|
import org.koin.androidx.viewmodel.ext.android.sharedViewModel
|
||||||
|
|
||||||
class ReposFragment : MagiskFragment<ModuleViewModel, FragmentReposBinding>(),
|
class ReposFragment : BaseFragment<ModuleViewModel, FragmentReposBinding>(),
|
||||||
SearchView.OnQueryTextListener {
|
SearchView.OnQueryTextListener {
|
||||||
|
|
||||||
override val layoutRes: Int = R.layout.fragment_repos
|
override val layoutRes: Int = R.layout.fragment_repos
|
||||||
|
@ -13,21 +13,20 @@ import androidx.preference.ListPreference
|
|||||||
import androidx.preference.Preference
|
import androidx.preference.Preference
|
||||||
import androidx.preference.PreferenceCategory
|
import androidx.preference.PreferenceCategory
|
||||||
import androidx.preference.SwitchPreferenceCompat
|
import androidx.preference.SwitchPreferenceCompat
|
||||||
import com.skoumal.teanity.extensions.subscribeK
|
|
||||||
import com.skoumal.teanity.util.KObservableField
|
|
||||||
import com.topjohnwu.magisk.BuildConfig
|
import com.topjohnwu.magisk.BuildConfig
|
||||||
import com.topjohnwu.magisk.Config
|
import com.topjohnwu.magisk.Config
|
||||||
import com.topjohnwu.magisk.Const
|
import com.topjohnwu.magisk.Const
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
|
import com.topjohnwu.magisk.base.BasePreferenceFragment
|
||||||
import com.topjohnwu.magisk.data.database.RepoDao
|
import com.topjohnwu.magisk.data.database.RepoDao
|
||||||
import com.topjohnwu.magisk.databinding.CustomDownloadDialogBinding
|
import com.topjohnwu.magisk.databinding.CustomDownloadDialogBinding
|
||||||
|
import com.topjohnwu.magisk.extensions.subscribeK
|
||||||
import com.topjohnwu.magisk.extensions.toLangTag
|
import com.topjohnwu.magisk.extensions.toLangTag
|
||||||
import com.topjohnwu.magisk.model.download.DownloadService
|
import com.topjohnwu.magisk.model.download.DownloadService
|
||||||
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.magisk.model.observer.Observer
|
import com.topjohnwu.magisk.model.observer.Observer
|
||||||
import com.topjohnwu.magisk.net.Networking
|
import com.topjohnwu.magisk.net.Networking
|
||||||
import com.topjohnwu.magisk.ui.base.BasePreferenceFragment
|
|
||||||
import com.topjohnwu.magisk.utils.*
|
import com.topjohnwu.magisk.utils.*
|
||||||
import com.topjohnwu.magisk.view.dialogs.FingerprintAuthDialog
|
import com.topjohnwu.magisk.view.dialogs.FingerprintAuthDialog
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
@ -204,7 +203,7 @@ class SettingsFragment : BasePreferenceFragment() {
|
|||||||
Shell.su("magiskhide --disable").submit()
|
Shell.su("magiskhide --disable").submit()
|
||||||
}
|
}
|
||||||
Config.Key.LOCALE -> {
|
Config.Key.LOCALE -> {
|
||||||
LocaleManager.setLocale(activity.application)
|
ResourceMgr.reload()
|
||||||
activity.recreate()
|
activity.recreate()
|
||||||
}
|
}
|
||||||
Config.Key.CHECK_UPDATES -> Utils.scheduleUpdateCheck(activity)
|
Config.Key.CHECK_UPDATES -> Utils.scheduleUpdateCheck(activity)
|
||||||
@ -233,7 +232,7 @@ class SettingsFragment : BasePreferenceFragment() {
|
|||||||
val values = mutableListOf<String>()
|
val values = mutableListOf<String>()
|
||||||
|
|
||||||
names.add(
|
names.add(
|
||||||
LocaleManager.getString(defaultLocale, R.string.system_default)
|
ResourceMgr.getString(defaultLocale, R.string.system_default)
|
||||||
)
|
)
|
||||||
values.add("")
|
values.add("")
|
||||||
|
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
package com.topjohnwu.magisk.ui.superuser
|
package com.topjohnwu.magisk.ui.superuser
|
||||||
|
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
|
import com.topjohnwu.magisk.base.BaseFragment
|
||||||
import com.topjohnwu.magisk.databinding.FragmentSuperuserBinding
|
import com.topjohnwu.magisk.databinding.FragmentSuperuserBinding
|
||||||
import com.topjohnwu.magisk.ui.base.MagiskFragment
|
|
||||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||||
|
|
||||||
class SuperuserFragment :
|
class SuperuserFragment :
|
||||||
MagiskFragment<SuperuserViewModel, FragmentSuperuserBinding>() {
|
BaseFragment<SuperuserViewModel, FragmentSuperuserBinding>() {
|
||||||
|
|
||||||
override val layoutRes: Int = R.layout.fragment_superuser
|
override val layoutRes: Int = R.layout.fragment_superuser
|
||||||
override val viewModel: SuperuserViewModel by viewModel()
|
override val viewModel: SuperuserViewModel by viewModel()
|
||||||
|
@ -2,22 +2,22 @@ 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 com.skoumal.teanity.databinding.ComparableRvItem
|
|
||||||
import com.skoumal.teanity.extensions.applySchedulers
|
|
||||||
import com.skoumal.teanity.extensions.subscribeK
|
|
||||||
import com.skoumal.teanity.rxbus.RxBus
|
|
||||||
import com.skoumal.teanity.util.DiffObservableList
|
|
||||||
import com.skoumal.teanity.viewevents.SnackbarEvent
|
|
||||||
import com.topjohnwu.magisk.BR
|
import com.topjohnwu.magisk.BR
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
|
import com.topjohnwu.magisk.base.viewmodel.BaseViewModel
|
||||||
import com.topjohnwu.magisk.data.database.PolicyDao
|
import com.topjohnwu.magisk.data.database.PolicyDao
|
||||||
|
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.MagiskPolicy
|
import com.topjohnwu.magisk.model.entity.MagiskPolicy
|
||||||
import com.topjohnwu.magisk.model.entity.recycler.PolicyRvItem
|
import com.topjohnwu.magisk.model.entity.recycler.PolicyRvItem
|
||||||
import com.topjohnwu.magisk.model.events.PolicyEnableEvent
|
import com.topjohnwu.magisk.model.events.PolicyEnableEvent
|
||||||
import com.topjohnwu.magisk.model.events.PolicyUpdateEvent
|
import com.topjohnwu.magisk.model.events.PolicyUpdateEvent
|
||||||
import com.topjohnwu.magisk.ui.base.MagiskViewModel
|
import com.topjohnwu.magisk.model.events.SnackbarEvent
|
||||||
|
import com.topjohnwu.magisk.utils.DiffObservableList
|
||||||
import com.topjohnwu.magisk.utils.FingerprintHelper
|
import com.topjohnwu.magisk.utils.FingerprintHelper
|
||||||
|
import com.topjohnwu.magisk.utils.RxBus
|
||||||
import com.topjohnwu.magisk.view.dialogs.CustomAlertDialog
|
import com.topjohnwu.magisk.view.dialogs.CustomAlertDialog
|
||||||
import com.topjohnwu.magisk.view.dialogs.FingerprintAuthDialog
|
import com.topjohnwu.magisk.view.dialogs.FingerprintAuthDialog
|
||||||
import io.reactivex.Single
|
import io.reactivex.Single
|
||||||
@ -29,7 +29,7 @@ class SuperuserViewModel(
|
|||||||
private val packageManager: PackageManager,
|
private val packageManager: PackageManager,
|
||||||
private val resources: Resources,
|
private val resources: Resources,
|
||||||
rxBus: RxBus
|
rxBus: RxBus
|
||||||
) : MagiskViewModel() {
|
) : BaseViewModel() {
|
||||||
|
|
||||||
val items = DiffObservableList(ComparableRvItem.callback)
|
val items = DiffObservableList(ComparableRvItem.callback)
|
||||||
val itemBinding = ItemBinding.of<ComparableRvItem<*>> { itemBinding, _, item ->
|
val itemBinding = ItemBinding.of<ComparableRvItem<*>> { itemBinding, _, item ->
|
||||||
|
@ -5,19 +5,20 @@ import android.os.Build
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.text.TextUtils
|
import android.text.TextUtils
|
||||||
import android.view.Window
|
import android.view.Window
|
||||||
import com.skoumal.teanity.viewevents.ViewEvent
|
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
|
import com.topjohnwu.magisk.base.BaseActivity
|
||||||
import com.topjohnwu.magisk.databinding.ActivityRequestBinding
|
import com.topjohnwu.magisk.databinding.ActivityRequestBinding
|
||||||
import com.topjohnwu.magisk.model.entity.MagiskPolicy
|
import com.topjohnwu.magisk.model.entity.MagiskPolicy
|
||||||
import com.topjohnwu.magisk.model.events.DieEvent
|
import com.topjohnwu.magisk.model.events.DieEvent
|
||||||
|
import com.topjohnwu.magisk.model.events.ViewEvent
|
||||||
import com.topjohnwu.magisk.model.receiver.GeneralReceiver
|
import com.topjohnwu.magisk.model.receiver.GeneralReceiver
|
||||||
import com.topjohnwu.magisk.ui.base.MagiskActivity
|
|
||||||
import com.topjohnwu.magisk.utils.SuLogger
|
import com.topjohnwu.magisk.utils.SuLogger
|
||||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||||
|
|
||||||
open class SuRequestActivity : MagiskActivity<SuRequestViewModel, ActivityRequestBinding>() {
|
open class SuRequestActivity : BaseActivity<SuRequestViewModel, ActivityRequestBinding>() {
|
||||||
|
|
||||||
override val layoutRes: Int = R.layout.activity_request
|
override val layoutRes: Int = R.layout.activity_request
|
||||||
|
override val themeRes: Int = R.style.MagiskTheme_SU
|
||||||
override val viewModel: SuRequestViewModel by viewModel()
|
override val viewModel: SuRequestViewModel by viewModel()
|
||||||
|
|
||||||
override fun onBackPressed() {
|
override fun onBackPressed() {
|
||||||
|
@ -9,21 +9,21 @@ import android.graphics.drawable.Drawable
|
|||||||
import android.hardware.fingerprint.FingerprintManager
|
import android.hardware.fingerprint.FingerprintManager
|
||||||
import android.os.CountDownTimer
|
import android.os.CountDownTimer
|
||||||
import android.text.TextUtils
|
import android.text.TextUtils
|
||||||
import com.skoumal.teanity.databinding.ComparableRvItem
|
|
||||||
import com.skoumal.teanity.extensions.addOnPropertyChangedCallback
|
|
||||||
import com.skoumal.teanity.util.DiffObservableList
|
|
||||||
import com.skoumal.teanity.util.KObservableField
|
|
||||||
import com.topjohnwu.magisk.BuildConfig
|
import com.topjohnwu.magisk.BuildConfig
|
||||||
import com.topjohnwu.magisk.Config
|
import com.topjohnwu.magisk.Config
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
|
import com.topjohnwu.magisk.base.viewmodel.BaseViewModel
|
||||||
import com.topjohnwu.magisk.data.database.PolicyDao
|
import com.topjohnwu.magisk.data.database.PolicyDao
|
||||||
|
import com.topjohnwu.magisk.databinding.ComparableRvItem
|
||||||
|
import com.topjohnwu.magisk.extensions.addOnPropertyChangedCallback
|
||||||
import com.topjohnwu.magisk.extensions.now
|
import com.topjohnwu.magisk.extensions.now
|
||||||
import com.topjohnwu.magisk.model.entity.MagiskPolicy
|
import com.topjohnwu.magisk.model.entity.MagiskPolicy
|
||||||
import com.topjohnwu.magisk.model.entity.recycler.SpinnerRvItem
|
import com.topjohnwu.magisk.model.entity.recycler.SpinnerRvItem
|
||||||
import com.topjohnwu.magisk.model.entity.toPolicy
|
import com.topjohnwu.magisk.model.entity.toPolicy
|
||||||
import com.topjohnwu.magisk.model.events.DieEvent
|
import com.topjohnwu.magisk.model.events.DieEvent
|
||||||
import com.topjohnwu.magisk.ui.base.MagiskViewModel
|
import com.topjohnwu.magisk.utils.DiffObservableList
|
||||||
import com.topjohnwu.magisk.utils.FingerprintHelper
|
import com.topjohnwu.magisk.utils.FingerprintHelper
|
||||||
|
import com.topjohnwu.magisk.utils.KObservableField
|
||||||
import com.topjohnwu.magisk.utils.SuConnector
|
import com.topjohnwu.magisk.utils.SuConnector
|
||||||
import me.tatarka.bindingcollectionadapter2.BindingListViewAdapter
|
import me.tatarka.bindingcollectionadapter2.BindingListViewAdapter
|
||||||
import me.tatarka.bindingcollectionadapter2.ItemBinding
|
import me.tatarka.bindingcollectionadapter2.ItemBinding
|
||||||
@ -36,7 +36,7 @@ class SuRequestViewModel(
|
|||||||
private val policyDB: PolicyDao,
|
private val policyDB: PolicyDao,
|
||||||
private val timeoutPrefs: SharedPreferences,
|
private val timeoutPrefs: SharedPreferences,
|
||||||
private val resources: Resources
|
private val resources: Resources
|
||||||
) : MagiskViewModel() {
|
) : BaseViewModel() {
|
||||||
|
|
||||||
val icon = KObservableField<Drawable?>(null)
|
val icon = KObservableField<Drawable?>(null)
|
||||||
val title = KObservableField("")
|
val title = KObservableField("")
|
||||||
|
@ -21,9 +21,9 @@ import androidx.recyclerview.widget.RecyclerView
|
|||||||
import androidx.viewpager.widget.ViewPager
|
import androidx.viewpager.widget.ViewPager
|
||||||
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||||
import com.google.android.material.navigation.NavigationView
|
import com.google.android.material.navigation.NavigationView
|
||||||
import com.skoumal.teanity.extensions.subscribeK
|
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
import com.topjohnwu.magisk.extensions.replaceRandomWithSpecial
|
import com.topjohnwu.magisk.extensions.replaceRandomWithSpecial
|
||||||
|
import com.topjohnwu.magisk.extensions.subscribeK
|
||||||
import com.topjohnwu.magisk.model.entity.state.IndeterminateState
|
import com.topjohnwu.magisk.model.entity.state.IndeterminateState
|
||||||
import io.reactivex.Observable
|
import io.reactivex.Observable
|
||||||
import io.reactivex.disposables.Disposable
|
import io.reactivex.disposables.Disposable
|
||||||
|
@ -0,0 +1,234 @@
|
|||||||
|
package com.topjohnwu.magisk.utils
|
||||||
|
|
||||||
|
import androidx.annotation.MainThread
|
||||||
|
import androidx.databinding.ListChangeRegistry
|
||||||
|
import androidx.databinding.ObservableList
|
||||||
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
|
import androidx.recyclerview.widget.ListUpdateCallback
|
||||||
|
import java.util.*
|
||||||
|
import kotlin.collections.ArrayList
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param callback The callback that controls the behavior of the DiffObservableList.
|
||||||
|
* @param detectMoves True if DiffUtil should try to detect moved items, false otherwise.
|
||||||
|
*/
|
||||||
|
open class DiffObservableList<T>(
|
||||||
|
private val callback: Callback<T>,
|
||||||
|
private val detectMoves: Boolean = true
|
||||||
|
) : AbstractList<T>(), ObservableList<T> {
|
||||||
|
|
||||||
|
private val LIST_LOCK = Object()
|
||||||
|
private var list: MutableList<T> = ArrayList()
|
||||||
|
private val listeners = ListChangeRegistry()
|
||||||
|
private val listCallback = ObservableListUpdateCallback()
|
||||||
|
|
||||||
|
override val size: Int get() = list.size
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the list of update operations that can convert this list into the given one.
|
||||||
|
*
|
||||||
|
* @param newItems The items that this list will be set to.
|
||||||
|
* @return A DiffResult that contains the information about the edit sequence to covert this
|
||||||
|
* list into the given one.
|
||||||
|
*/
|
||||||
|
fun calculateDiff(newItems: List<T>): DiffUtil.DiffResult {
|
||||||
|
val frozenList = synchronized(LIST_LOCK) {
|
||||||
|
ArrayList(list)
|
||||||
|
}
|
||||||
|
return doCalculateDiff(frozenList, newItems)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun doCalculateDiff(oldItems: List<T>, newItems: List<T>?): DiffUtil.DiffResult {
|
||||||
|
return DiffUtil.calculateDiff(object : DiffUtil.Callback() {
|
||||||
|
override fun getOldListSize() = oldItems.size
|
||||||
|
|
||||||
|
override fun getNewListSize() = newItems?.size ?: 0
|
||||||
|
|
||||||
|
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
|
||||||
|
val oldItem = oldItems[oldItemPosition]
|
||||||
|
val newItem = newItems!![newItemPosition]
|
||||||
|
return callback.areItemsTheSame(oldItem, newItem)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
|
||||||
|
val oldItem = oldItems[oldItemPosition]
|
||||||
|
val newItem = newItems!![newItemPosition]
|
||||||
|
return callback.areContentsTheSame(oldItem, newItem)
|
||||||
|
}
|
||||||
|
}, detectMoves)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the contents of this list to the given one using the DiffResults to dispatch change
|
||||||
|
* notifications.
|
||||||
|
*
|
||||||
|
* @param newItems The items to set this list to.
|
||||||
|
* @param diffResult The diff results to dispatch change notifications.
|
||||||
|
*/
|
||||||
|
@MainThread
|
||||||
|
fun update(newItems: List<T>, diffResult: DiffUtil.DiffResult) {
|
||||||
|
synchronized(LIST_LOCK) {
|
||||||
|
list = newItems.toMutableList()
|
||||||
|
}
|
||||||
|
diffResult.dispatchUpdatesTo(listCallback)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets this list to the given items. This is a convenience method for calling [ ][.calculateDiff] followed by [.update].
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* **Warning!** If the lists are large this operation may be too slow for the main thread. In
|
||||||
|
* that case, you should call [.calculateDiff] on a background thread and then
|
||||||
|
* [.update] on the main thread.
|
||||||
|
*
|
||||||
|
* @param newItems The items to set this list to.
|
||||||
|
*/
|
||||||
|
@MainThread
|
||||||
|
fun update(newItems: List<T>) {
|
||||||
|
val diffResult = doCalculateDiff(list, newItems)
|
||||||
|
list = newItems.toMutableList()
|
||||||
|
diffResult.dispatchUpdatesTo(listCallback)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun addOnListChangedCallback(listener: ObservableList.OnListChangedCallback<out ObservableList<T>>) {
|
||||||
|
listeners.add(listener)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun removeOnListChangedCallback(listener: ObservableList.OnListChangedCallback<out ObservableList<T>>) {
|
||||||
|
listeners.remove(listener)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun get(index: Int): T {
|
||||||
|
return list[index]
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun add(element: T): Boolean {
|
||||||
|
list.add(element)
|
||||||
|
notifyAdd(size - 1, 1)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun add(index: Int, element: T) {
|
||||||
|
list.add(index, element)
|
||||||
|
notifyAdd(index, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun addAll(elements: Collection<T>): Boolean {
|
||||||
|
val oldSize = size
|
||||||
|
val added = list.addAll(elements)
|
||||||
|
if (added) {
|
||||||
|
notifyAdd(oldSize, size - oldSize)
|
||||||
|
}
|
||||||
|
return added
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun addAll(index: Int, elements: Collection<T>): Boolean {
|
||||||
|
val added = list.addAll(index, elements)
|
||||||
|
if (added) {
|
||||||
|
notifyAdd(index, elements.size)
|
||||||
|
}
|
||||||
|
return added
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun clear() {
|
||||||
|
val oldSize = size
|
||||||
|
list.clear()
|
||||||
|
if (oldSize != 0) {
|
||||||
|
notifyRemove(0, oldSize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun remove(element: T): Boolean {
|
||||||
|
val index = indexOf(element)
|
||||||
|
return if (index >= 0) {
|
||||||
|
removeAt(index)
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun removeAt(index: Int): T {
|
||||||
|
val element = list.removeAt(index)
|
||||||
|
notifyRemove(index, 1)
|
||||||
|
return element
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeLast(): T? {
|
||||||
|
if (size > 0) {
|
||||||
|
val index = size - 1
|
||||||
|
return removeAt(index)
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun set(index: Int, element: T): T {
|
||||||
|
val old = list.set(index, element)
|
||||||
|
listeners.notifyChanged(this, index, 1)
|
||||||
|
return old
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun notifyAdd(start: Int, count: Int) {
|
||||||
|
listeners.notifyInserted(this, start, count)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun notifyRemove(start: Int, count: Int) {
|
||||||
|
listeners.notifyRemoved(this, start, count)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Callback class used by DiffUtil while calculating the diff between two lists.
|
||||||
|
*/
|
||||||
|
interface Callback<T> {
|
||||||
|
/**
|
||||||
|
* Called by the DiffUtil to decide whether two object represent the same Item.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* For example, if your items have unique ids, this method should check their id equality.
|
||||||
|
*
|
||||||
|
* @param oldItem The old item.
|
||||||
|
* @param newItem The new item.
|
||||||
|
* @return True if the two items represent the same object or false if they are different.
|
||||||
|
*/
|
||||||
|
fun areItemsTheSame(oldItem: T, newItem: T): Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called by the DiffUtil when it wants to check whether two items have the same data.
|
||||||
|
* DiffUtil uses this information to detect if the contents of an item has changed.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* DiffUtil uses this method to check equality instead of [Object.equals] so
|
||||||
|
* that you can change its behavior depending on your UI.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* This method is called only if [.areItemsTheSame] returns `true` for
|
||||||
|
* these items.
|
||||||
|
*
|
||||||
|
* @param oldItem The old item.
|
||||||
|
* @param newItem The new item which replaces the old item.
|
||||||
|
* @return True if the contents of the items are the same or false if they are different.
|
||||||
|
*/
|
||||||
|
fun areContentsTheSame(oldItem: T, newItem: T): Boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
inner class ObservableListUpdateCallback : ListUpdateCallback {
|
||||||
|
override fun onChanged(position: Int, count: Int, payload: Any?) {
|
||||||
|
listeners.notifyChanged(this@DiffObservableList, position, count)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onMoved(fromPosition: Int, toPosition: Int) {
|
||||||
|
listeners.notifyMoved(this@DiffObservableList, fromPosition, toPosition, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onInserted(position: Int, count: Int) {
|
||||||
|
modCount += 1
|
||||||
|
listeners.notifyInserted(this@DiffObservableList, position, count)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRemoved(position: Int, count: Int) {
|
||||||
|
modCount += 1
|
||||||
|
listeners.notifyRemoved(this@DiffObservableList, position, count)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -1,61 +0,0 @@
|
|||||||
package com.topjohnwu.magisk.utils
|
|
||||||
|
|
||||||
import dalvik.system.DexClassLoader
|
|
||||||
import java.io.File
|
|
||||||
import java.io.IOException
|
|
||||||
import java.net.URL
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
@Suppress("FunctionName")
|
|
||||||
inline fun <reified T> T.DynamicClassLoader(apk: File) = DynamicClassLoader(apk, T::class.java.classLoader)
|
|
||||||
|
|
||||||
class DynamicClassLoader(apk: File, parent: ClassLoader?)
|
|
||||||
: DexClassLoader(apk.path, apk.parent, null, parent) {
|
|
||||||
|
|
||||||
private val base by lazy { Any::class.java.classLoader!! }
|
|
||||||
|
|
||||||
@Throws(ClassNotFoundException::class)
|
|
||||||
override fun loadClass(name: String, resolve: Boolean) : Class<*>
|
|
||||||
= findLoadedClass(name) ?: runCatching {
|
|
||||||
base.loadClass(name)
|
|
||||||
}.getOrElse {
|
|
||||||
runCatching {
|
|
||||||
findClass(name)
|
|
||||||
}.getOrElse { err ->
|
|
||||||
runCatching {
|
|
||||||
parent.loadClass(name)
|
|
||||||
}.getOrElse { throw err }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getResource(name: String) = base.getResource(name)
|
|
||||||
?: findResource(name)
|
|
||||||
?: parent?.getResource(name)
|
|
||||||
|
|
||||||
@Throws(IOException::class)
|
|
||||||
override fun getResources(name: String): Enumeration<URL> {
|
|
||||||
val resources = mutableListOf(
|
|
||||||
base.getResources(name),
|
|
||||||
findResources(name), parent.getResources(name))
|
|
||||||
return object : Enumeration<URL> {
|
|
||||||
override fun hasMoreElements(): Boolean {
|
|
||||||
while (true) {
|
|
||||||
if (resources.isEmpty())
|
|
||||||
return false
|
|
||||||
if (!resources[0].hasMoreElements()) {
|
|
||||||
resources.removeAt(0)
|
|
||||||
} else {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun nextElement(): URL {
|
|
||||||
if (!hasMoreElements())
|
|
||||||
throw NoSuchElementException()
|
|
||||||
return resources[0].nextElement()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
117
app/src/main/java/com/topjohnwu/magisk/utils/KItemDecoration.kt
Normal file
117
app/src/main/java/com/topjohnwu/magisk/utils/KItemDecoration.kt
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
package com.topjohnwu.magisk.utils
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Canvas
|
||||||
|
import android.graphics.Rect
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
|
import android.view.View
|
||||||
|
import androidx.annotation.DrawableRes
|
||||||
|
import androidx.core.view.get
|
||||||
|
import androidx.recyclerview.widget.DividerItemDecoration
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.topjohnwu.magisk.extensions.drawableCompat
|
||||||
|
|
||||||
|
class KItemDecoration(
|
||||||
|
private val context: Context,
|
||||||
|
@RecyclerView.Orientation private val orientation: Int
|
||||||
|
) :
|
||||||
|
RecyclerView.ItemDecoration() {
|
||||||
|
|
||||||
|
private val bounds = Rect()
|
||||||
|
private var divider: Drawable? = null
|
||||||
|
var showAfterLast = true
|
||||||
|
|
||||||
|
fun setDeco(@DrawableRes drawable: Int) = apply {
|
||||||
|
setDeco(context.drawableCompat(drawable))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setDeco(drawable: Drawable?) = apply {
|
||||||
|
divider = drawable
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDraw(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) {
|
||||||
|
parent.layoutManager ?: return
|
||||||
|
|
||||||
|
divider?.let {
|
||||||
|
if (orientation == DividerItemDecoration.VERTICAL) {
|
||||||
|
drawVertical(canvas, parent, it)
|
||||||
|
} else {
|
||||||
|
drawHorizontal(canvas, parent, it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun drawVertical(canvas: Canvas, parent: RecyclerView, drawable: Drawable) {
|
||||||
|
canvas.save()
|
||||||
|
val left: Int
|
||||||
|
val right: Int
|
||||||
|
if (parent.clipToPadding) {
|
||||||
|
left = parent.paddingLeft
|
||||||
|
right = parent.width - parent.paddingRight
|
||||||
|
canvas.clipRect(
|
||||||
|
left, parent.paddingTop, right,
|
||||||
|
parent.height - parent.paddingBottom
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
left = 0
|
||||||
|
right = parent.width
|
||||||
|
}
|
||||||
|
|
||||||
|
val to = if (showAfterLast) parent.childCount else parent.childCount - 1
|
||||||
|
|
||||||
|
(0 until to)
|
||||||
|
.map { parent[it] }
|
||||||
|
.forEach { child ->
|
||||||
|
parent.getDecoratedBoundsWithMargins(child, bounds)
|
||||||
|
val bottom = bounds.bottom + Math.round(child.translationY)
|
||||||
|
val top = bottom - drawable.intrinsicHeight
|
||||||
|
drawable.setBounds(left, top, right, bottom)
|
||||||
|
drawable.draw(canvas)
|
||||||
|
}
|
||||||
|
canvas.restore()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun drawHorizontal(canvas: Canvas, parent: RecyclerView, drawable: Drawable) {
|
||||||
|
canvas.save()
|
||||||
|
val top: Int
|
||||||
|
val bottom: Int
|
||||||
|
if (parent.clipToPadding) {
|
||||||
|
top = parent.paddingTop
|
||||||
|
bottom = parent.height - parent.paddingBottom
|
||||||
|
canvas.clipRect(
|
||||||
|
parent.paddingLeft, top,
|
||||||
|
parent.width - parent.paddingRight, bottom
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
top = 0
|
||||||
|
bottom = parent.height
|
||||||
|
}
|
||||||
|
|
||||||
|
val to = if (showAfterLast) parent.childCount else parent.childCount - 1
|
||||||
|
|
||||||
|
(0 until to)
|
||||||
|
.map { parent[it] }
|
||||||
|
.forEach { child ->
|
||||||
|
parent.layoutManager!!.getDecoratedBoundsWithMargins(child, bounds)
|
||||||
|
val right = bounds.right + Math.round(child.translationX)
|
||||||
|
val left = right - drawable.intrinsicWidth
|
||||||
|
drawable.setBounds(left, top, right, bottom)
|
||||||
|
drawable.draw(canvas)
|
||||||
|
}
|
||||||
|
canvas.restore()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
|
||||||
|
if (parent.getChildAdapterPosition(view) == state.itemCount - 1) {
|
||||||
|
outRect.setEmpty()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (orientation == RecyclerView.VERTICAL) {
|
||||||
|
outRect.set(0, 0, 0, divider?.intrinsicHeight ?: 0)
|
||||||
|
} else {
|
||||||
|
outRect.set(0, 0, divider?.intrinsicWidth ?: 0, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,49 @@
|
|||||||
|
package com.topjohnwu.magisk.utils
|
||||||
|
|
||||||
|
import androidx.databinding.Observable
|
||||||
|
import androidx.databinding.ObservableField
|
||||||
|
import java.io.Serializable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Kotlin version of [ObservableField].
|
||||||
|
* You can define if wrapped type is Nullable or not.
|
||||||
|
* You can use kotlin get/set syntax for value
|
||||||
|
*/
|
||||||
|
class KObservableField<T> : ObservableField<T>, Serializable {
|
||||||
|
|
||||||
|
var value: T
|
||||||
|
set(value) {
|
||||||
|
if (field != value) {
|
||||||
|
field = value
|
||||||
|
notifyChange()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(init: T) {
|
||||||
|
value = init
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(init: T, vararg dependencies: Observable) : super(*dependencies) {
|
||||||
|
value = init
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated(
|
||||||
|
message = "Needed for data binding, use KObservableField.value syntax from code",
|
||||||
|
replaceWith = ReplaceWith("value")
|
||||||
|
)
|
||||||
|
override fun get(): T {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated(
|
||||||
|
message = "Needed for data binding, use KObservableField.value = ... syntax from code",
|
||||||
|
replaceWith = ReplaceWith("value = newValue")
|
||||||
|
)
|
||||||
|
override fun set(newValue: T) {
|
||||||
|
value = newValue
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return "KObservableField(value=$value)"
|
||||||
|
}
|
||||||
|
}
|
@ -1,68 +0,0 @@
|
|||||||
package com.topjohnwu.magisk.utils
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.ContextWrapper
|
|
||||||
import android.content.res.Configuration
|
|
||||||
import android.content.res.Resources
|
|
||||||
import androidx.annotation.StringRes
|
|
||||||
import com.topjohnwu.magisk.Config
|
|
||||||
import com.topjohnwu.magisk.R
|
|
||||||
import com.topjohnwu.magisk.extensions.get
|
|
||||||
import com.topjohnwu.magisk.extensions.inject
|
|
||||||
import com.topjohnwu.magisk.extensions.langTagToLocale
|
|
||||||
import com.topjohnwu.superuser.internal.InternalUtils
|
|
||||||
import io.reactivex.Single
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
var currentLocale = Locale.getDefault()!!
|
|
||||||
private set
|
|
||||||
|
|
||||||
val defaultLocale = Locale.getDefault()!!
|
|
||||||
|
|
||||||
val availableLocales = Single.fromCallable {
|
|
||||||
val compareId = R.string.app_changelog
|
|
||||||
val res: Resources by inject()
|
|
||||||
mutableListOf<Locale>().apply {
|
|
||||||
// Add default locale
|
|
||||||
add(Locale.ENGLISH)
|
|
||||||
|
|
||||||
// Add some special locales
|
|
||||||
add(Locale.TAIWAN)
|
|
||||||
add(Locale("pt", "BR"))
|
|
||||||
|
|
||||||
// Other locales
|
|
||||||
val otherLocales = res.assets.locales
|
|
||||||
.map { it.langTagToLocale() }
|
|
||||||
.distinctBy { LocaleManager.getString(it, compareId) }
|
|
||||||
|
|
||||||
listOf("", "").toTypedArray()
|
|
||||||
|
|
||||||
addAll(otherLocales)
|
|
||||||
}.sortedWith(Comparator { a, b ->
|
|
||||||
a.getDisplayName(a).toLowerCase(a)
|
|
||||||
.compareTo(b.getDisplayName(b).toLowerCase(b))
|
|
||||||
})
|
|
||||||
}.cache()!!
|
|
||||||
|
|
||||||
object LocaleManager {
|
|
||||||
|
|
||||||
fun setLocale(wrapper: ContextWrapper) {
|
|
||||||
val localeConfig = Config.locale
|
|
||||||
currentLocale = when {
|
|
||||||
localeConfig.isEmpty() -> defaultLocale
|
|
||||||
else -> localeConfig.langTagToLocale()
|
|
||||||
}
|
|
||||||
Locale.setDefault(currentLocale)
|
|
||||||
InternalUtils.replaceBaseContext(wrapper, getLocaleContext(wrapper, currentLocale))
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getLocaleContext(context: Context, locale: Locale = currentLocale): Context {
|
|
||||||
val config = Configuration(context.resources.configuration)
|
|
||||||
config.setLocale(locale)
|
|
||||||
return context.createConfigurationContext(config)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getString(locale: Locale, @StringRes id: Int): String {
|
|
||||||
return getLocaleContext(get(), locale).getString(id)
|
|
||||||
}
|
|
||||||
}
|
|
@ -3,8 +3,8 @@ package com.topjohnwu.magisk.utils
|
|||||||
import android.content.ComponentName
|
import android.content.ComponentName
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import com.skoumal.teanity.extensions.subscribeK
|
|
||||||
import com.topjohnwu.magisk.*
|
import com.topjohnwu.magisk.*
|
||||||
|
import com.topjohnwu.magisk.extensions.subscribeK
|
||||||
import com.topjohnwu.magisk.ui.SplashActivity
|
import com.topjohnwu.magisk.ui.SplashActivity
|
||||||
import com.topjohnwu.magisk.view.Notifications
|
import com.topjohnwu.magisk.view.Notifications
|
||||||
import com.topjohnwu.signing.JarMap
|
import com.topjohnwu.signing.JarMap
|
||||||
@ -102,7 +102,7 @@ object PatchAPK {
|
|||||||
|
|
||||||
Config.suManager = pkg
|
Config.suManager = pkg
|
||||||
Config.export()
|
Config.export()
|
||||||
RootUtils.rmAndLaunch(BuildConfig.APPLICATION_ID,
|
Utils.rmAndLaunch(BuildConfig.APPLICATION_ID,
|
||||||
ComponentName(pkg, ClassMap.get<Class<*>>(SplashActivity::class.java).name))
|
ComponentName(pkg, ClassMap.get<Class<*>>(SplashActivity::class.java).name))
|
||||||
|
|
||||||
return true
|
return true
|
||||||
|
126
app/src/main/java/com/topjohnwu/magisk/utils/ResourceMgr.kt
Normal file
126
app/src/main/java/com/topjohnwu/magisk/utils/ResourceMgr.kt
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
@file:Suppress("DEPRECATION")
|
||||||
|
|
||||||
|
package com.topjohnwu.magisk.utils
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.ContextWrapper
|
||||||
|
import android.content.res.AssetManager
|
||||||
|
import android.content.res.Configuration
|
||||||
|
import android.content.res.Resources
|
||||||
|
import androidx.annotation.StringRes
|
||||||
|
import com.topjohnwu.magisk.Config
|
||||||
|
import com.topjohnwu.magisk.R
|
||||||
|
import com.topjohnwu.magisk.extensions.langTagToLocale
|
||||||
|
import io.reactivex.Single
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
var isRunningAsStub = false
|
||||||
|
|
||||||
|
var currentLocale: Locale = Locale.getDefault()
|
||||||
|
private set
|
||||||
|
|
||||||
|
@SuppressLint("ConstantLocale")
|
||||||
|
val defaultLocale: Locale = Locale.getDefault()
|
||||||
|
|
||||||
|
val availableLocales = Single.fromCallable {
|
||||||
|
val compareId = R.string.app_changelog
|
||||||
|
mutableListOf<Locale>().apply {
|
||||||
|
// Add default locale
|
||||||
|
add(Locale.ENGLISH)
|
||||||
|
|
||||||
|
// Add some special locales
|
||||||
|
add(Locale.TAIWAN)
|
||||||
|
add(Locale("pt", "BR"))
|
||||||
|
|
||||||
|
val config = Configuration()
|
||||||
|
val metrics = ResourceMgr.resource.displayMetrics
|
||||||
|
val res = Resources(ResourceMgr.resource.assets, metrics, config)
|
||||||
|
|
||||||
|
// Other locales
|
||||||
|
val otherLocales = ResourceMgr.resource.assets.locales
|
||||||
|
.map { it.langTagToLocale() }
|
||||||
|
.distinctBy {
|
||||||
|
config.setLocale(it)
|
||||||
|
res.updateConfiguration(config, metrics)
|
||||||
|
res.getString(compareId)
|
||||||
|
}
|
||||||
|
|
||||||
|
listOf("", "").toTypedArray()
|
||||||
|
|
||||||
|
addAll(otherLocales)
|
||||||
|
}.sortedWith(Comparator { a, b ->
|
||||||
|
a.getDisplayName(a).toLowerCase(a)
|
||||||
|
.compareTo(b.getDisplayName(b).toLowerCase(b))
|
||||||
|
})
|
||||||
|
}.cache()!!
|
||||||
|
|
||||||
|
private val addAssetPath by lazy {
|
||||||
|
AssetManager::class.java.getMethod("addAssetPath", String::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun AssetManager.addAssetPath(path: String) {
|
||||||
|
addAssetPath.invoke(this, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Context.wrap(global: Boolean = true): Context
|
||||||
|
= if (!global) ResourceMgr.ResContext(this) else ResourceMgr.GlobalResContext(this)
|
||||||
|
|
||||||
|
object ResourceMgr {
|
||||||
|
|
||||||
|
lateinit var resource: Resources
|
||||||
|
private lateinit var resApk: String
|
||||||
|
|
||||||
|
fun init(context: Context) {
|
||||||
|
resource = context.resources
|
||||||
|
if (isRunningAsStub)
|
||||||
|
resApk = DynAPK.current(context).path
|
||||||
|
}
|
||||||
|
|
||||||
|
// Override locale and inject resources from dynamic APK
|
||||||
|
private fun Resources.patch(config: Configuration = Configuration(configuration)): Resources {
|
||||||
|
config.setLocale(currentLocale)
|
||||||
|
updateConfiguration(config, displayMetrics)
|
||||||
|
if (isRunningAsStub)
|
||||||
|
assets.addAssetPath(resApk)
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun reload(config: Configuration = Configuration(resource.configuration)) {
|
||||||
|
val localeConfig = Config.locale
|
||||||
|
currentLocale = when {
|
||||||
|
localeConfig.isEmpty() -> defaultLocale
|
||||||
|
else -> localeConfig.langTagToLocale()
|
||||||
|
}
|
||||||
|
Locale.setDefault(currentLocale)
|
||||||
|
resource.patch(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getString(locale: Locale, @StringRes id: Int): String {
|
||||||
|
val config = Configuration()
|
||||||
|
config.setLocale(locale)
|
||||||
|
return Resources(resource.assets, resource.displayMetrics, config).getString(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
open class GlobalResContext(base: Context) : ContextWrapper(base) {
|
||||||
|
open val mRes: Resources get() = resource
|
||||||
|
private val loader by lazy { javaClass.classLoader!! }
|
||||||
|
|
||||||
|
override fun getResources(): Resources {
|
||||||
|
return mRes
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getClassLoader(): ClassLoader {
|
||||||
|
return loader
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createConfigurationContext(config: Configuration): Context {
|
||||||
|
return ResContext(super.createConfigurationContext(config))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ResContext(base: Context) : GlobalResContext(base) {
|
||||||
|
override val mRes by lazy { base.resources.patch() }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
40
app/src/main/java/com/topjohnwu/magisk/utils/RootInit.kt
Normal file
40
app/src/main/java/com/topjohnwu/magisk/utils/RootInit.kt
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
package com.topjohnwu.magisk.utils
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import com.topjohnwu.magisk.Const
|
||||||
|
import com.topjohnwu.magisk.Info
|
||||||
|
import com.topjohnwu.magisk.R
|
||||||
|
import com.topjohnwu.magisk.extensions.rawResource
|
||||||
|
import com.topjohnwu.superuser.Shell
|
||||||
|
import com.topjohnwu.superuser.ShellUtils
|
||||||
|
import com.topjohnwu.superuser.io.SuFile
|
||||||
|
|
||||||
|
class RootInit : Shell.Initializer() {
|
||||||
|
|
||||||
|
override fun onInit(context: Context, shell: Shell): Boolean {
|
||||||
|
return init(context.wrap(), shell)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun init(context: Context, shell: Shell): Boolean {
|
||||||
|
val job = shell.newJob()
|
||||||
|
if (shell.isRoot) {
|
||||||
|
job.add(context.rawResource(R.raw.util_functions))
|
||||||
|
.add(context.rawResource(R.raw.utils))
|
||||||
|
Const.MAGISK_DISABLE_FILE = SuFile("/cache/.disable_magisk")
|
||||||
|
Info.loadMagiskInfo()
|
||||||
|
} else {
|
||||||
|
job.add(context.rawResource(R.raw.nonroot_utils))
|
||||||
|
}
|
||||||
|
|
||||||
|
job.add("mount_partitions",
|
||||||
|
"get_flags",
|
||||||
|
"run_migrations",
|
||||||
|
"export BOOTMODE=true")
|
||||||
|
.exec()
|
||||||
|
|
||||||
|
Info.keepVerity = ShellUtils.fastCmd("echo \$KEEPVERITY").toBoolean()
|
||||||
|
Info.keepEnc = ShellUtils.fastCmd("echo \$KEEPFORCEENCRYPT").toBoolean()
|
||||||
|
Info.recovery = ShellUtils.fastCmd("echo \$RECOVERYMODE").toBoolean()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
@ -1,158 +0,0 @@
|
|||||||
package com.topjohnwu.magisk.utils
|
|
||||||
|
|
||||||
import android.content.ComponentName
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
|
||||||
import android.net.Uri
|
|
||||||
import com.topjohnwu.magisk.Const
|
|
||||||
import com.topjohnwu.magisk.Info
|
|
||||||
import com.topjohnwu.magisk.R
|
|
||||||
import com.topjohnwu.magisk.extensions.rawResource
|
|
||||||
import com.topjohnwu.magisk.extensions.toShellCmd
|
|
||||||
import com.topjohnwu.superuser.Shell
|
|
||||||
import com.topjohnwu.superuser.ShellUtils
|
|
||||||
import com.topjohnwu.superuser.io.SuFile
|
|
||||||
import java.util.*
|
|
||||||
import java.lang.reflect.Array as RArray
|
|
||||||
|
|
||||||
fun Intent.toCommand(args: MutableList<String>) {
|
|
||||||
if (action != null) {
|
|
||||||
args.add("-a")
|
|
||||||
args.add(action!!)
|
|
||||||
}
|
|
||||||
if (component != null) {
|
|
||||||
args.add("-n")
|
|
||||||
args.add(component!!.flattenToString())
|
|
||||||
}
|
|
||||||
if (data != null) {
|
|
||||||
args.add("-d")
|
|
||||||
args.add(dataString!!)
|
|
||||||
}
|
|
||||||
if (categories != null) {
|
|
||||||
for (cat in categories) {
|
|
||||||
args.add("-c")
|
|
||||||
args.add(cat)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (type != null) {
|
|
||||||
args.add("-t")
|
|
||||||
args.add(type!!)
|
|
||||||
}
|
|
||||||
val extras = extras
|
|
||||||
if (extras != null) {
|
|
||||||
loop@ for (key in extras.keySet()) {
|
|
||||||
val v = extras.get(key) ?: continue
|
|
||||||
var value: Any = v
|
|
||||||
val arg: String
|
|
||||||
when {
|
|
||||||
v is String -> arg = "--es"
|
|
||||||
v is Boolean -> arg = "--ez"
|
|
||||||
v is Int -> arg = "--ei"
|
|
||||||
v is Long -> arg = "--el"
|
|
||||||
v is Float -> arg = "--ef"
|
|
||||||
v is Uri -> arg = "--eu"
|
|
||||||
v is ComponentName -> {
|
|
||||||
arg = "--ecn"
|
|
||||||
value = v.flattenToString()
|
|
||||||
}
|
|
||||||
v is ArrayList<*> -> {
|
|
||||||
if (v.size <= 0)
|
|
||||||
/* Impossible to know the type due to type erasure */
|
|
||||||
continue@loop
|
|
||||||
|
|
||||||
arg = if (v[0] is Int)
|
|
||||||
"--eial"
|
|
||||||
else if (v[0] is Long)
|
|
||||||
"--elal"
|
|
||||||
else if (v[0] is Float)
|
|
||||||
"--efal"
|
|
||||||
else if (v[0] is String)
|
|
||||||
"--esal"
|
|
||||||
else
|
|
||||||
continue@loop /* Unsupported */
|
|
||||||
|
|
||||||
val sb = StringBuilder()
|
|
||||||
for (o in v) {
|
|
||||||
sb.append(o.toString().replace(",", "\\,"))
|
|
||||||
sb.append(',')
|
|
||||||
}
|
|
||||||
// Remove trailing comma
|
|
||||||
sb.deleteCharAt(sb.length - 1)
|
|
||||||
value = sb
|
|
||||||
}
|
|
||||||
v.javaClass.isArray -> {
|
|
||||||
arg = if (v is IntArray)
|
|
||||||
"--eia"
|
|
||||||
else if (v is LongArray)
|
|
||||||
"--ela"
|
|
||||||
else if (v is FloatArray)
|
|
||||||
"--efa"
|
|
||||||
else if (v is Array<*> && v.isArrayOf<String>())
|
|
||||||
"--esa"
|
|
||||||
else
|
|
||||||
continue@loop /* Unsupported */
|
|
||||||
|
|
||||||
val sb = StringBuilder()
|
|
||||||
val len = RArray.getLength(v)
|
|
||||||
for (i in 0 until len) {
|
|
||||||
sb.append(RArray.get(v, i)!!.toString().replace(",", "\\,"))
|
|
||||||
sb.append(',')
|
|
||||||
}
|
|
||||||
// Remove trailing comma
|
|
||||||
sb.deleteCharAt(sb.length - 1)
|
|
||||||
value = sb
|
|
||||||
}
|
|
||||||
else -> continue@loop
|
|
||||||
} /* Unsupported */
|
|
||||||
|
|
||||||
args.add(arg)
|
|
||||||
args.add(key)
|
|
||||||
args.add(value.toString())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
args.add("-f")
|
|
||||||
args.add(flags.toString())
|
|
||||||
}
|
|
||||||
|
|
||||||
fun startActivity(intent: Intent) {
|
|
||||||
if (intent.component == null)
|
|
||||||
return
|
|
||||||
val args = ArrayList<String>()
|
|
||||||
args.add("am")
|
|
||||||
args.add("start")
|
|
||||||
intent.toCommand(args)
|
|
||||||
Shell.su(args.toShellCmd()).exec()
|
|
||||||
}
|
|
||||||
|
|
||||||
class RootUtils : Shell.Initializer() {
|
|
||||||
|
|
||||||
override fun onInit(context: Context, shell: Shell): Boolean {
|
|
||||||
val job = shell.newJob()
|
|
||||||
if (shell.isRoot) {
|
|
||||||
job.add(context.rawResource(R.raw.util_functions))
|
|
||||||
.add(context.rawResource(R.raw.utils))
|
|
||||||
Const.MAGISK_DISABLE_FILE = SuFile("/cache/.disable_magisk")
|
|
||||||
Info.loadMagiskInfo()
|
|
||||||
} else {
|
|
||||||
job.add(context.rawResource(R.raw.nonroot_utils))
|
|
||||||
}
|
|
||||||
|
|
||||||
job.add("mount_partitions",
|
|
||||||
"get_flags",
|
|
||||||
"run_migrations",
|
|
||||||
"export BOOTMODE=true")
|
|
||||||
.exec()
|
|
||||||
|
|
||||||
Info.keepVerity = ShellUtils.fastCmd("echo \$KEEPVERITY").toBoolean()
|
|
||||||
Info.keepEnc = ShellUtils.fastCmd("echo \$KEEPFORCEENCRYPT").toBoolean()
|
|
||||||
Info.recovery = ShellUtils.fastCmd("echo \$RECOVERYMODE").toBoolean()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
|
|
||||||
fun rmAndLaunch(rm: String, component: ComponentName) {
|
|
||||||
Shell.su("(rm_launch $rm ${component.flattenToString()})").exec()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
36
app/src/main/java/com/topjohnwu/magisk/utils/RxBus.kt
Normal file
36
app/src/main/java/com/topjohnwu/magisk/utils/RxBus.kt
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
package com.topjohnwu.magisk.utils
|
||||||
|
|
||||||
|
import io.reactivex.Observable
|
||||||
|
import io.reactivex.subjects.PublishSubject
|
||||||
|
|
||||||
|
class RxBus {
|
||||||
|
|
||||||
|
private val _bus = PublishSubject.create<Event>()
|
||||||
|
|
||||||
|
val bus: Observable<Event> get() = _bus
|
||||||
|
|
||||||
|
fun post(event: Event) {
|
||||||
|
_bus.onNext(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun post(event: Int) {
|
||||||
|
_bus.onNext(SimpleEvent(event))
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <reified T : Event> register(noinline predicate: (T) -> Boolean = { true }): Observable<T> {
|
||||||
|
return bus
|
||||||
|
.ofType(T::class.java)
|
||||||
|
.filter(predicate)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun register(eventId: Int): Observable<Int> {
|
||||||
|
return bus
|
||||||
|
.ofType(SimpleEvent::class.java)
|
||||||
|
.map { it.eventId }
|
||||||
|
.filter { it == eventId }
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Event
|
||||||
|
|
||||||
|
private class SimpleEvent(val eventId: Int) : Event
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
package com.topjohnwu.magisk.utils
|
package com.topjohnwu.magisk.utils
|
||||||
|
|
||||||
|
import android.content.ComponentName
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
@ -72,4 +73,8 @@ object Utils {
|
|||||||
if ((exists() && isDirectory) || mkdirs()) this else null
|
if ((exists() && isDirectory) || mkdirs()) this else null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun rmAndLaunch(rm: String, component: ComponentName) {
|
||||||
|
Shell.su("(rm_launch $rm ${component.flattenToString()})").exec()
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -4,23 +4,22 @@ import android.content.Context
|
|||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import com.skoumal.teanity.extensions.subscribeK
|
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
import com.topjohnwu.magisk.data.repository.StringRepository
|
import com.topjohnwu.magisk.data.repository.StringRepository
|
||||||
import com.topjohnwu.magisk.extensions.inject
|
import com.topjohnwu.magisk.extensions.subscribeK
|
||||||
|
import io.noties.markwon.Markwon
|
||||||
import io.reactivex.Completable
|
import io.reactivex.Completable
|
||||||
import io.reactivex.Single
|
import io.reactivex.Single
|
||||||
import ru.noties.markwon.Markwon
|
import org.koin.core.KoinComponent
|
||||||
import ru.noties.markwon.html.HtmlPlugin
|
import org.koin.core.inject
|
||||||
import ru.noties.markwon.image.ImagesPlugin
|
|
||||||
import ru.noties.markwon.image.svg.SvgPlugin
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
object MarkDownWindow {
|
object MarkDownWindow : KoinComponent {
|
||||||
|
|
||||||
private val stringRepo: StringRepository by inject()
|
private val stringRepo: StringRepository by inject()
|
||||||
|
private val markwon: Markwon by inject()
|
||||||
|
|
||||||
fun show(activity: Context, title: String?, url: String) {
|
fun show(activity: Context, title: String?, url: String) {
|
||||||
show(activity, title, stringRepo.getString(url))
|
show(activity, title, stringRepo.getString(url))
|
||||||
@ -35,25 +34,14 @@ object MarkDownWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun show(activity: Context, title: String?, content: Single<String>) {
|
fun show(activity: Context, title: String?, content: Single<String>) {
|
||||||
val markwon = Markwon.builder(activity)
|
|
||||||
.usePlugin(HtmlPlugin.create())
|
|
||||||
.usePlugin(ImagesPlugin.create(activity))
|
|
||||||
.usePlugin(SvgPlugin.create(activity.resources))
|
|
||||||
.build()
|
|
||||||
val mv = LayoutInflater.from(activity).inflate(R.layout.markdown_window, null)
|
val mv = LayoutInflater.from(activity).inflate(R.layout.markdown_window, null)
|
||||||
val tv = mv.findViewById<TextView>(R.id.md_txt)
|
val tv = mv.findViewById<TextView>(R.id.md_txt)
|
||||||
|
|
||||||
content.map {
|
content.map {
|
||||||
runCatching {
|
markwon.setMarkdown(tv, it)
|
||||||
markwon.setMarkdown(tv, it)
|
|
||||||
}.onFailure {
|
|
||||||
Timber.e(it)
|
|
||||||
// Always wrap the actual exception as it could be ExceptionInInitializerError,
|
|
||||||
// which is a fatal error and RxJava will send it to the global handler and crash
|
|
||||||
throw MarkwonException(it)
|
|
||||||
}
|
|
||||||
}.ignoreElement().onErrorResumeNext {
|
}.ignoreElement().onErrorResumeNext {
|
||||||
// Nothing we can actually do other than show error message
|
// Nothing we can actually do other than show error message
|
||||||
|
Timber.e(it)
|
||||||
tv.setText(R.string.download_file_error)
|
tv.setText(R.string.download_file_error)
|
||||||
Completable.complete()
|
Completable.complete()
|
||||||
}.subscribeK {
|
}.subscribeK {
|
||||||
@ -64,6 +52,4 @@ object MarkDownWindow {
|
|||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class MarkwonException(e: Throwable): Exception(e)
|
|
||||||
}
|
}
|
||||||
|
@ -8,9 +8,9 @@ import com.topjohnwu.magisk.Info
|
|||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
import com.topjohnwu.magisk.extensions.cachedFile
|
import com.topjohnwu.magisk.extensions.cachedFile
|
||||||
import com.topjohnwu.magisk.extensions.reboot
|
import com.topjohnwu.magisk.extensions.reboot
|
||||||
|
import com.topjohnwu.magisk.net.Networking
|
||||||
import com.topjohnwu.magisk.tasks.MagiskInstaller
|
import com.topjohnwu.magisk.tasks.MagiskInstaller
|
||||||
import com.topjohnwu.magisk.utils.Utils
|
import com.topjohnwu.magisk.utils.Utils
|
||||||
import com.topjohnwu.magisk.net.Networking
|
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
import com.topjohnwu.superuser.ShellUtils
|
import com.topjohnwu.superuser.ShellUtils
|
||||||
import com.topjohnwu.superuser.internal.UiThreadHandler
|
import com.topjohnwu.superuser.internal.UiThreadHandler
|
||||||
|
@ -7,13 +7,13 @@ import android.widget.Toast
|
|||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import com.topjohnwu.magisk.Const
|
import com.topjohnwu.magisk.Const
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
|
import com.topjohnwu.magisk.base.BaseActivity
|
||||||
import com.topjohnwu.magisk.model.download.DownloadService
|
import com.topjohnwu.magisk.model.download.DownloadService
|
||||||
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.magisk.ui.base.MagiskActivity
|
|
||||||
import com.topjohnwu.magisk.utils.Utils
|
import com.topjohnwu.magisk.utils.Utils
|
||||||
|
|
||||||
internal class InstallMethodDialog(activity: MagiskActivity<*, *>, options: List<String>) :
|
internal class InstallMethodDialog(activity: BaseActivity<*, *>, options: List<String>) :
|
||||||
AlertDialog.Builder(activity) {
|
AlertDialog.Builder(activity) {
|
||||||
|
|
||||||
init {
|
init {
|
||||||
@ -28,11 +28,11 @@ internal class InstallMethodDialog(activity: MagiskActivity<*, *>, options: List
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun flash(activity: MagiskActivity<*, *>) = DownloadService(activity) {
|
private fun flash(activity: BaseActivity<*, *>) = DownloadService(activity) {
|
||||||
subject = DownloadSubject.Magisk(Configuration.Flash.Primary)
|
subject = DownloadSubject.Magisk(Configuration.Flash.Primary)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun patchBoot(activity: MagiskActivity<*, *>) = activity.withExternalRW {
|
private fun patchBoot(activity: BaseActivity<*, *>) = activity.withExternalRW {
|
||||||
onSuccess {
|
onSuccess {
|
||||||
Utils.toast(R.string.patch_file_msg, Toast.LENGTH_LONG)
|
Utils.toast(R.string.patch_file_msg, Toast.LENGTH_LONG)
|
||||||
val intent = Intent(Intent.ACTION_GET_CONTENT)
|
val intent = Intent(Intent.ACTION_GET_CONTENT)
|
||||||
@ -49,7 +49,7 @@ internal class InstallMethodDialog(activity: MagiskActivity<*, *>, options: List
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun downloadOnly(activity: MagiskActivity<*, *>) = activity.withExternalRW {
|
private fun downloadOnly(activity: BaseActivity<*, *>) = activity.withExternalRW {
|
||||||
onSuccess {
|
onSuccess {
|
||||||
DownloadService(activity) {
|
DownloadService(activity) {
|
||||||
subject = DownloadSubject.Magisk(Configuration.Download)
|
subject = DownloadSubject.Magisk(Configuration.Download)
|
||||||
@ -57,7 +57,7 @@ internal class InstallMethodDialog(activity: MagiskActivity<*, *>, options: List
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun installInactiveSlot(activity: MagiskActivity<*, *>) {
|
private fun installInactiveSlot(activity: BaseActivity<*, *>) {
|
||||||
CustomAlertDialog(activity)
|
CustomAlertDialog(activity)
|
||||||
.setTitle(R.string.warning)
|
.setTitle(R.string.warning)
|
||||||
.setMessage(R.string.install_inactive_slot_msg)
|
.setMessage(R.string.install_inactive_slot_msg)
|
||||||
|
@ -3,14 +3,14 @@ package com.topjohnwu.magisk.view.dialogs
|
|||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import com.topjohnwu.magisk.Info
|
import com.topjohnwu.magisk.Info
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
import com.topjohnwu.magisk.ui.base.MagiskActivity
|
import com.topjohnwu.magisk.base.BaseActivity
|
||||||
import com.topjohnwu.magisk.utils.Utils
|
import com.topjohnwu.magisk.utils.Utils
|
||||||
import com.topjohnwu.magisk.view.MarkDownWindow
|
import com.topjohnwu.magisk.view.MarkDownWindow
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
import com.topjohnwu.superuser.ShellUtils
|
import com.topjohnwu.superuser.ShellUtils
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
class MagiskInstallDialog(a: MagiskActivity<*, *>) : CustomAlertDialog(a) {
|
class MagiskInstallDialog(a: BaseActivity<*, *>) : CustomAlertDialog(a) {
|
||||||
init {
|
init {
|
||||||
val filename = "Magisk v${Info.remote.magisk.version}" +
|
val filename = "Magisk v${Info.remote.magisk.version}" +
|
||||||
"(${Info.remote.magisk.versionCode})"
|
"(${Info.remote.magisk.versionCode})"
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
<data>
|
<data>
|
||||||
|
|
||||||
<import type="com.skoumal.teanity.viewmodel.LoadingViewModel.State" />
|
<import type="com.topjohnwu.magisk.base.viewmodel.LoadingViewModel.State" />
|
||||||
|
|
||||||
<import type="com.topjohnwu.magisk.ui.home.SafetyNetState" />
|
<import type="com.topjohnwu.magisk.ui.home.SafetyNetState" />
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# v7.3.4
|
# v7.3.5
|
||||||
- App is now fully written in Kotlin!
|
- Sort installed modules by name
|
||||||
- New downloading system
|
- Better pre-5.0 support
|
||||||
- Add new "Recovery Mode" to Advanced Settings
|
- Fix potential issues when patching tar files
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user