Merge branch 'md2'
This commit is contained in:
commit
c1dad11cb3
1
.gitattributes
vendored
1
.gitattributes
vendored
@ -17,3 +17,4 @@ tools/** binary
|
|||||||
*.apk binary
|
*.apk binary
|
||||||
*.png binary
|
*.png binary
|
||||||
*.jpg binary
|
*.jpg binary
|
||||||
|
*.ttf binary
|
||||||
|
@ -19,6 +19,12 @@ android {
|
|||||||
multiDexEnabled true
|
multiDexEnabled true
|
||||||
versionName props['appVersion']
|
versionName props['appVersion']
|
||||||
versionCode props['appVersionCode'] as Integer
|
versionCode props['appVersionCode'] as Integer
|
||||||
|
|
||||||
|
javaCompileOptions {
|
||||||
|
annotationProcessorOptions {
|
||||||
|
arguments = ["room.incremental":"true"]
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
@ -62,7 +68,7 @@ dependencies {
|
|||||||
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'
|
||||||
|
|
||||||
implementation 'io.reactivex.rxjava2:rxjava:2.2.13'
|
implementation 'io.reactivex.rxjava2:rxjava:2.2.16'
|
||||||
implementation 'io.reactivex.rxjava2:rxkotlin:2.4.0'
|
implementation 'io.reactivex.rxjava2:rxkotlin:2.4.0'
|
||||||
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
|
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
|
||||||
|
|
||||||
@ -89,14 +95,16 @@ 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.2'
|
def vRetrofit = '2.7.1'
|
||||||
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.6'
|
def vOkHttp = '3.12.7'
|
||||||
implementation "com.squareup.okhttp3:okhttp:${vOkHttp}"
|
implementation("com.squareup.okhttp3:okhttp:${vOkHttp}") {
|
||||||
|
force = true
|
||||||
|
}
|
||||||
implementation "com.squareup.okhttp3:logging-interceptor:${vOkHttp}"
|
implementation "com.squareup.okhttp3:logging-interceptor:${vOkHttp}"
|
||||||
|
|
||||||
def vMoshi = '1.9.2'
|
def vMoshi = '1.9.2'
|
||||||
@ -111,7 +119,7 @@ dependencies {
|
|||||||
replacedBy('com.github.topjohnwu:room-runtime')
|
replacedBy('com.github.topjohnwu:room-runtime')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
def vRoom = '2.2.2'
|
def vRoom = '2.2.3'
|
||||||
implementation "com.github.topjohnwu:room-runtime:${vRoom}"
|
implementation "com.github.topjohnwu:room-runtime:${vRoom}"
|
||||||
implementation "androidx.room:room-rxjava2:${vRoom}"
|
implementation "androidx.room:room-rxjava2:${vRoom}"
|
||||||
kapt "androidx.room:room-compiler:${vRoom}"
|
kapt "androidx.room:room-compiler:${vRoom}"
|
||||||
@ -120,16 +128,16 @@ dependencies {
|
|||||||
implementation "androidx.navigation:navigation-fragment-ktx:${vNav}"
|
implementation "androidx.navigation:navigation-fragment-ktx:${vNav}"
|
||||||
implementation "androidx.navigation:navigation-ui-ktx:${vNav}"
|
implementation "androidx.navigation:navigation-ui-ktx:${vNav}"
|
||||||
|
|
||||||
implementation 'androidx.biometric:biometric:1.0.0'
|
implementation 'androidx.biometric:biometric:1.0.1'
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||||
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0-alpha03'
|
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0-alpha03'
|
||||||
|
implementation 'androidx.browser:browser:1.2.0'
|
||||||
implementation 'androidx.preference:preference:1.1.0'
|
implementation 'androidx.preference:preference:1.1.0'
|
||||||
implementation 'androidx.recyclerview:recyclerview:1.1.0'
|
implementation 'androidx.recyclerview:recyclerview:1.1.0'
|
||||||
implementation 'androidx.fragment:fragment-ktx:1.2.0-rc03'
|
implementation 'androidx.fragment:fragment-ktx:1.2.0-rc05'
|
||||||
implementation 'androidx.cardview:cardview:1.0.0'
|
|
||||||
implementation 'androidx.work:work-runtime:2.2.0'
|
implementation 'androidx.work:work-runtime:2.2.0'
|
||||||
implementation 'androidx.transition:transition:1.3.0-rc02'
|
implementation 'androidx.transition:transition:1.3.0-rc02'
|
||||||
implementation 'androidx.multidex:multidex:2.0.1'
|
implementation 'androidx.multidex:multidex:2.0.1'
|
||||||
implementation 'androidx.core:core-ktx:1.1.0'
|
implementation 'androidx.core:core-ktx:1.2.0-rc01'
|
||||||
implementation 'com.google.android.material:material:1.2.0-alpha02'
|
implementation 'com.google.android.material:material:1.2.0-alpha03'
|
||||||
}
|
}
|
||||||
|
22
app/proguard-rules.pro
vendored
22
app/proguard-rules.pro
vendored
@ -17,32 +17,26 @@
|
|||||||
#}
|
#}
|
||||||
|
|
||||||
# Snet
|
# Snet
|
||||||
-keepclassmembers class com.topjohnwu.magisk.utils.SafetyNetHelper { *; }
|
-keepclassmembers class com.topjohnwu.magisk.core.utils.SafetyNetHelper { *; }
|
||||||
-keep,allowobfuscation interface com.topjohnwu.magisk.utils.SafetyNetHelper$Callback
|
-keep,allowobfuscation interface com.topjohnwu.magisk.core.utils.SafetyNetHelper$Callback
|
||||||
-keepclassmembers class * implements com.topjohnwu.magisk.utils.SafetyNetHelper$Callback {
|
-keepclassmembers class * implements com.topjohnwu.magisk.core.utils.SafetyNetHelper$Callback {
|
||||||
void onResponse(int);
|
void onResponse(int);
|
||||||
}
|
}
|
||||||
|
|
||||||
# Keep all fragment constructors
|
# Fragments
|
||||||
-keepclassmembers class * extends androidx.fragment.app.Fragment {
|
-keep,allowobfuscation class * extends androidx.fragment.app.Fragment
|
||||||
public <init>(...);
|
|
||||||
}
|
|
||||||
|
|
||||||
# DelegateWorker
|
# BaseWorkerWrapper
|
||||||
-keep,allowobfuscation class * extends com.topjohnwu.magisk.base.DelegateWorker
|
-keep,allowobfuscation class * extends com.topjohnwu.magisk.core.base.BaseWorkerWrapper
|
||||||
|
|
||||||
# BootSigner
|
# BootSigner
|
||||||
-keep class a.a { *; }
|
-keep class a.a { *; }
|
||||||
|
|
||||||
# Workaround R8 bug
|
|
||||||
-keep,allowobfuscation class com.topjohnwu.magisk.model.receiver.GeneralReceiver
|
|
||||||
-keepclassmembers class a.e { *; }
|
|
||||||
|
|
||||||
# Strip logging
|
# Strip logging
|
||||||
-assumenosideeffects class timber.log.Timber.Tree { *; }
|
-assumenosideeffects class timber.log.Timber.Tree { *; }
|
||||||
|
|
||||||
# Excessive obfuscation
|
# Excessive obfuscation
|
||||||
-repackageclasses 'a'
|
-repackageclasses a
|
||||||
-allowaccessmodification
|
-allowaccessmodification
|
||||||
|
|
||||||
# QOL
|
# QOL
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
<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">
|
||||||
@ -21,6 +20,10 @@
|
|||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.APPLICATION_PREFERENCES" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<!-- Main -->
|
<!-- Main -->
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package a;
|
package a;
|
||||||
|
|
||||||
import com.topjohnwu.magisk.utils.PatchAPK;
|
import com.topjohnwu.magisk.core.utils.PatchAPK;
|
||||||
import com.topjohnwu.signing.BootSigner;
|
import com.topjohnwu.signing.BootSigner;
|
||||||
|
|
||||||
public class a {
|
public class a {
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
package a;
|
|
||||||
|
|
||||||
import com.topjohnwu.magisk.ui.MainActivity;
|
|
||||||
|
|
||||||
public class b extends MainActivity {
|
|
||||||
/* stub */
|
|
||||||
}
|
|
@ -1,7 +0,0 @@
|
|||||||
package a;
|
|
||||||
|
|
||||||
import com.topjohnwu.magisk.ui.SplashActivity;
|
|
||||||
|
|
||||||
public class c extends SplashActivity {
|
|
||||||
/* stub */
|
|
||||||
}
|
|
@ -1,13 +0,0 @@
|
|||||||
package a;
|
|
||||||
|
|
||||||
import com.topjohnwu.magisk.App;
|
|
||||||
|
|
||||||
public class e extends App {
|
|
||||||
public e() {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
public e(Object o) {
|
|
||||||
super(o);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,7 +0,0 @@
|
|||||||
package a;
|
|
||||||
|
|
||||||
import com.topjohnwu.magisk.ui.flash.FlashActivity;
|
|
||||||
|
|
||||||
public class f extends FlashActivity {
|
|
||||||
/* stub */
|
|
||||||
}
|
|
@ -2,11 +2,11 @@ package a;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
||||||
import com.topjohnwu.magisk.model.update.UpdateCheckService;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.work.WorkerParameters;
|
import androidx.work.WorkerParameters;
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.core.UpdateCheckService;
|
||||||
|
|
||||||
public class g extends w<UpdateCheckService> {
|
public class g extends w<UpdateCheckService> {
|
||||||
/* Stub */
|
/* Stub */
|
||||||
public g(@NonNull Context context, @NonNull WorkerParameters workerParams) {
|
public g(@NonNull Context context, @NonNull WorkerParameters workerParams) {
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
package a;
|
|
||||||
|
|
||||||
import com.topjohnwu.magisk.model.receiver.GeneralReceiver;
|
|
||||||
|
|
||||||
public class h extends GeneralReceiver {
|
|
||||||
/* stub */
|
|
||||||
}
|
|
@ -1,7 +0,0 @@
|
|||||||
package a;
|
|
||||||
|
|
||||||
import com.topjohnwu.magisk.model.download.DownloadService;
|
|
||||||
|
|
||||||
public class j extends DownloadService {
|
|
||||||
/* stub */
|
|
||||||
}
|
|
@ -1,7 +0,0 @@
|
|||||||
package a;
|
|
||||||
|
|
||||||
import com.topjohnwu.magisk.ui.surequest.SuRequestActivity;
|
|
||||||
|
|
||||||
public class m extends SuRequestActivity {
|
|
||||||
/* stub */
|
|
||||||
}
|
|
55
app/src/main/java/a/stubs.kt
Normal file
55
app/src/main/java/a/stubs.kt
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
package a
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.work.Worker
|
||||||
|
import androidx.work.WorkerParameters
|
||||||
|
import com.topjohnwu.magisk.core.App
|
||||||
|
import com.topjohnwu.magisk.core.GeneralReceiver
|
||||||
|
import com.topjohnwu.magisk.core.SplashActivity
|
||||||
|
import com.topjohnwu.magisk.core.base.BaseWorkerWrapper
|
||||||
|
import com.topjohnwu.magisk.core.download.DownloadService
|
||||||
|
import com.topjohnwu.magisk.legacy.flash.FlashActivity
|
||||||
|
import com.topjohnwu.magisk.legacy.surequest.SuRequestActivity
|
||||||
|
import com.topjohnwu.magisk.ui.MainActivity
|
||||||
|
import java.lang.reflect.ParameterizedType
|
||||||
|
|
||||||
|
class b : MainActivity()
|
||||||
|
|
||||||
|
class c : SplashActivity()
|
||||||
|
|
||||||
|
class e : App {
|
||||||
|
constructor() : super()
|
||||||
|
constructor(o: Any) : super(o)
|
||||||
|
}
|
||||||
|
|
||||||
|
class f : FlashActivity()
|
||||||
|
|
||||||
|
class h : GeneralReceiver()
|
||||||
|
|
||||||
|
class j : DownloadService()
|
||||||
|
|
||||||
|
class m : SuRequestActivity()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper class to workaround Proguard rule :
|
||||||
|
* -keep class * extends Worker
|
||||||
|
* */
|
||||||
|
abstract class w<T : BaseWorkerWrapper>(
|
||||||
|
context: Context,
|
||||||
|
workerParams: WorkerParameters
|
||||||
|
) : Worker(context, workerParams) {
|
||||||
|
|
||||||
|
private var base: T? = null
|
||||||
|
|
||||||
|
override fun doWork() = base?.doWork() ?: Result.failure()
|
||||||
|
|
||||||
|
override fun onStopped() = base?.onStopped() ?: Unit
|
||||||
|
|
||||||
|
init {
|
||||||
|
try {
|
||||||
|
base = ((javaClass.genericSuperclass as ParameterizedType)
|
||||||
|
.actualTypeArguments[0] as Class<T>).newInstance()
|
||||||
|
base?.attachWorker(this)
|
||||||
|
} catch (e : java.lang.Exception) {}
|
||||||
|
}
|
||||||
|
}
|
@ -1,42 +0,0 @@
|
|||||||
package a;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.work.Worker;
|
|
||||||
import androidx.work.WorkerParameters;
|
|
||||||
|
|
||||||
import com.topjohnwu.magisk.base.DelegateWorker;
|
|
||||||
|
|
||||||
import java.lang.reflect.ParameterizedType;
|
|
||||||
|
|
||||||
public abstract class w<T extends DelegateWorker> extends Worker {
|
|
||||||
|
|
||||||
/* Wrapper class to workaround Proguard -keep class * extends Worker */
|
|
||||||
|
|
||||||
private T base;
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
w(@NonNull Context context, @NonNull WorkerParameters workerParams) {
|
|
||||||
super(context, workerParams);
|
|
||||||
try {
|
|
||||||
base = ((Class<T>) ((ParameterizedType) getClass().getGenericSuperclass())
|
|
||||||
.getActualTypeArguments()[0]).newInstance();
|
|
||||||
base.attachWorker(this);
|
|
||||||
} catch (Exception ignored) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public Result doWork() {
|
|
||||||
if (base == null)
|
|
||||||
return Result.failure();
|
|
||||||
return base.doWork();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onStopped() {
|
|
||||||
if (base != null)
|
|
||||||
base.onStopped();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,56 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,35 +0,0 @@
|
|||||||
package com.topjohnwu.magisk.base.viewmodel
|
|
||||||
|
|
||||||
import com.topjohnwu.magisk.base.BaseActivity
|
|
||||||
import com.topjohnwu.magisk.extensions.doOnSubscribeUi
|
|
||||||
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.utils.KObservableField
|
|
||||||
import io.reactivex.Observable
|
|
||||||
import io.reactivex.subjects.PublishSubject
|
|
||||||
import com.topjohnwu.magisk.Info.isConnected as gIsConnected
|
|
||||||
|
|
||||||
|
|
||||||
abstract class BaseViewModel(
|
|
||||||
initialState: State = State.LOADING
|
|
||||||
) : LoadingViewModel(initialState) {
|
|
||||||
|
|
||||||
val isConnected = object : KObservableField<Boolean>(gIsConnected.value, gIsConnected) {
|
|
||||||
override fun get(): Boolean {
|
|
||||||
return gIsConnected.value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun withView(action: BaseActivity<*, *>.() -> Unit) {
|
|
||||||
ViewActionEvent(action).publish()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun withPermissions(vararg permissions: String): Observable<Boolean> {
|
|
||||||
val subject = PublishSubject.create<Boolean>()
|
|
||||||
return subject.doOnSubscribeUi { PermissionEvent(permissions.toList(), subject).publish() }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun back() = BackPressEvent().publish()
|
|
||||||
|
|
||||||
}
|
|
@ -1,78 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
@ -1,46 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,15 +0,0 @@
|
|||||||
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
|
|
||||||
|
|
||||||
}
|
|
@ -1,33 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,4 +1,4 @@
|
|||||||
package com.topjohnwu.magisk
|
package com.topjohnwu.magisk.core
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
@ -9,6 +9,12 @@ import androidx.room.Room
|
|||||||
import androidx.work.WorkManager
|
import androidx.work.WorkManager
|
||||||
import androidx.work.impl.WorkDatabase
|
import androidx.work.impl.WorkDatabase
|
||||||
import androidx.work.impl.WorkDatabase_Impl
|
import androidx.work.impl.WorkDatabase_Impl
|
||||||
|
import com.topjohnwu.magisk.BuildConfig
|
||||||
|
import com.topjohnwu.magisk.DynAPK
|
||||||
|
import com.topjohnwu.magisk.FileProvider
|
||||||
|
import com.topjohnwu.magisk.core.su.SuCallbackHandler
|
||||||
|
import com.topjohnwu.magisk.core.utils.RootInit
|
||||||
|
import com.topjohnwu.magisk.core.utils.updateConfig
|
||||||
import com.topjohnwu.magisk.data.database.RepoDatabase
|
import com.topjohnwu.magisk.data.database.RepoDatabase
|
||||||
import com.topjohnwu.magisk.data.database.RepoDatabase_Impl
|
import com.topjohnwu.magisk.data.database.RepoDatabase_Impl
|
||||||
import com.topjohnwu.magisk.data.database.SuLogDatabase
|
import com.topjohnwu.magisk.data.database.SuLogDatabase
|
||||||
@ -17,13 +23,11 @@ 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.extensions.unwrap
|
import com.topjohnwu.magisk.extensions.unwrap
|
||||||
import com.topjohnwu.magisk.utils.RootInit
|
|
||||||
import com.topjohnwu.magisk.utils.SuHandler
|
|
||||||
import com.topjohnwu.magisk.utils.updateConfig
|
|
||||||
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
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
import kotlin.system.exitProcess
|
||||||
|
|
||||||
open class App() : Application() {
|
open class App() : Application() {
|
||||||
|
|
||||||
@ -37,7 +41,7 @@ open class App() : Application() {
|
|||||||
Shell.Config.verboseLogging(BuildConfig.DEBUG)
|
Shell.Config.verboseLogging(BuildConfig.DEBUG)
|
||||||
Shell.Config.addInitializers(RootInit::class.java)
|
Shell.Config.addInitializers(RootInit::class.java)
|
||||||
Shell.Config.setTimeout(2)
|
Shell.Config.setTimeout(2)
|
||||||
FileProvider.callHandler = SuHandler
|
FileProvider.callHandler = SuCallbackHandler
|
||||||
Room.setFactory {
|
Room.setFactory {
|
||||||
when (it) {
|
when (it) {
|
||||||
WorkDatabase::class.java -> WorkDatabase_Impl()
|
WorkDatabase::class.java -> WorkDatabase_Impl()
|
||||||
@ -46,13 +50,19 @@ open class App() : Application() {
|
|||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Always log full stack trace with Timber
|
||||||
|
Timber.plant(Timber.DebugTree())
|
||||||
|
Thread.setDefaultUncaughtExceptionHandler { _, e ->
|
||||||
|
Timber.e(e)
|
||||||
|
exitProcess(1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun attachBaseContext(base: Context) {
|
override fun attachBaseContext(base: Context) {
|
||||||
// Basic setup
|
// Basic setup
|
||||||
if (BuildConfig.DEBUG)
|
if (BuildConfig.DEBUG)
|
||||||
MultiDex.install(base)
|
MultiDex.install(base)
|
||||||
Timber.plant(Timber.DebugTree())
|
|
||||||
|
|
||||||
// Some context magic
|
// Some context magic
|
||||||
val app: Application
|
val app: Application
|
@ -1,19 +1,22 @@
|
|||||||
package com.topjohnwu.magisk
|
package com.topjohnwu.magisk.core
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
import android.util.Xml
|
import android.util.Xml
|
||||||
|
import androidx.appcompat.app.AppCompatDelegate
|
||||||
import androidx.core.content.edit
|
import androidx.core.content.edit
|
||||||
import com.topjohnwu.magisk.data.database.SettingsDao
|
import com.topjohnwu.magisk.BuildConfig
|
||||||
import com.topjohnwu.magisk.data.database.StringDao
|
import com.topjohnwu.magisk.core.magiskdb.SettingsDao
|
||||||
|
import com.topjohnwu.magisk.core.magiskdb.StringDao
|
||||||
import com.topjohnwu.magisk.data.repository.DBConfig
|
import com.topjohnwu.magisk.data.repository.DBConfig
|
||||||
import com.topjohnwu.magisk.di.Protected
|
import com.topjohnwu.magisk.di.Protected
|
||||||
import com.topjohnwu.magisk.extensions.get
|
import com.topjohnwu.magisk.extensions.get
|
||||||
import com.topjohnwu.magisk.extensions.inject
|
import com.topjohnwu.magisk.extensions.inject
|
||||||
import com.topjohnwu.magisk.model.preference.PreferenceModel
|
import com.topjohnwu.magisk.model.preference.PreferenceModel
|
||||||
import com.topjohnwu.magisk.utils.BiometricHelper
|
import com.topjohnwu.magisk.ui.theme.Theme
|
||||||
import com.topjohnwu.magisk.utils.Utils
|
import com.topjohnwu.magisk.core.utils.BiometricHelper
|
||||||
|
import com.topjohnwu.magisk.core.utils.Utils
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
import com.topjohnwu.superuser.io.SuFile
|
import com.topjohnwu.superuser.io.SuFile
|
||||||
import com.topjohnwu.superuser.io.SuFileInputStream
|
import com.topjohnwu.superuser.io.SuFileInputStream
|
||||||
@ -45,10 +48,15 @@ object Config : PreferenceModel, DBConfig {
|
|||||||
const val CUSTOM_CHANNEL = "custom_channel"
|
const val CUSTOM_CHANNEL = "custom_channel"
|
||||||
const val LOCALE = "locale"
|
const val LOCALE = "locale"
|
||||||
const val DARK_THEME = "dark_theme"
|
const val DARK_THEME = "dark_theme"
|
||||||
|
const val DARK_THEME_EXTENDED = "dark_theme_extended"
|
||||||
const val REPO_ORDER = "repo_order"
|
const val REPO_ORDER = "repo_order"
|
||||||
const val SHOW_SYSTEM_APP = "show_system"
|
const val SHOW_SYSTEM_APP = "show_system"
|
||||||
const val DOWNLOAD_PATH = "download_path"
|
const val DOWNLOAD_PATH = "download_path"
|
||||||
|
const val REDESIGN = "redesign"
|
||||||
|
const val SAFETY = "safety_notice"
|
||||||
|
const val THEME_ORDINAL = "theme_ordinal"
|
||||||
const val BOOT_ID = "boot_id"
|
const val BOOT_ID = "boot_id"
|
||||||
|
const val LIST_SPAN_COUNT = "list_span_count"
|
||||||
|
|
||||||
// system state
|
// system state
|
||||||
const val MAGISKHIDE = "magiskhide"
|
const val MAGISKHIDE = "magiskhide"
|
||||||
@ -103,38 +111,66 @@ object Config : PreferenceModel, DBConfig {
|
|||||||
Value.CANARY_DEBUG_CHANNEL
|
Value.CANARY_DEBUG_CHANNEL
|
||||||
else
|
else
|
||||||
Value.CANARY_CHANNEL
|
Value.CANARY_CHANNEL
|
||||||
}
|
} else Value.DEFAULT_CHANNEL
|
||||||
else Value.DEFAULT_CHANNEL
|
|
||||||
|
|
||||||
var bootId by preference(Key.BOOT_ID, "")
|
var bootId by preference(Key.BOOT_ID, "")
|
||||||
|
|
||||||
var downloadPath by preference(Key.DOWNLOAD_PATH, Environment.DIRECTORY_DOWNLOADS)
|
var downloadPath by preference(Key.DOWNLOAD_PATH, Environment.DIRECTORY_DOWNLOADS)
|
||||||
var repoOrder by preference(Key.REPO_ORDER, Value.ORDER_DATE)
|
var repoOrder by preference(
|
||||||
|
Key.REPO_ORDER,
|
||||||
|
Value.ORDER_DATE
|
||||||
|
)
|
||||||
|
|
||||||
var suDefaultTimeout by preferenceStrInt(Key.SU_REQUEST_TIMEOUT, 10)
|
var suDefaultTimeout by preferenceStrInt(Key.SU_REQUEST_TIMEOUT, 10)
|
||||||
var suAutoReponse by preferenceStrInt(Key.SU_AUTO_RESPONSE, Value.SU_PROMPT)
|
var suAutoReponse by preferenceStrInt(
|
||||||
var suNotification by preferenceStrInt(Key.SU_NOTIFICATION, Value.NOTIFICATION_TOAST)
|
Key.SU_AUTO_RESPONSE,
|
||||||
var updateChannel by preferenceStrInt(Key.UPDATE_CHANNEL, defaultChannel)
|
Value.SU_PROMPT
|
||||||
|
)
|
||||||
|
var suNotification by preferenceStrInt(
|
||||||
|
Key.SU_NOTIFICATION,
|
||||||
|
Value.NOTIFICATION_TOAST
|
||||||
|
)
|
||||||
|
var updateChannel by preferenceStrInt(
|
||||||
|
Key.UPDATE_CHANNEL,
|
||||||
|
defaultChannel
|
||||||
|
)
|
||||||
|
|
||||||
var darkTheme by preference(Key.DARK_THEME, true)
|
var safetyNotice by preference(Key.SAFETY, true)
|
||||||
|
var darkThemeExtended by preference(
|
||||||
|
Key.DARK_THEME_EXTENDED,
|
||||||
|
AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
|
||||||
|
)
|
||||||
|
var themeOrdinal by preference(Key.THEME_ORDINAL, Theme.Piplup.ordinal)
|
||||||
var suReAuth by preference(Key.SU_REAUTH, false)
|
var suReAuth by preference(Key.SU_REAUTH, false)
|
||||||
var checkUpdate by preference(Key.CHECK_UPDATES, true)
|
var checkUpdate by preference(Key.CHECK_UPDATES, true)
|
||||||
var magiskHide by preference(Key.MAGISKHIDE, true)
|
var magiskHide by preference(Key.MAGISKHIDE, true)
|
||||||
|
@JvmStatic
|
||||||
var coreOnly by preference(Key.COREONLY, false)
|
var coreOnly by preference(Key.COREONLY, false)
|
||||||
var showSystemApp by preference(Key.SHOW_SYSTEM_APP, false)
|
var showSystemApp by preference(Key.SHOW_SYSTEM_APP, false)
|
||||||
|
var listSpanCount by preference(Key.LIST_SPAN_COUNT, 2)
|
||||||
|
|
||||||
var customChannelUrl by preference(Key.CUSTOM_CHANNEL, "")
|
var customChannelUrl by preference(Key.CUSTOM_CHANNEL, "")
|
||||||
var locale by preference(Key.LOCALE, "")
|
var locale by preference(Key.LOCALE, "")
|
||||||
|
|
||||||
var rootMode by dbSettings(Key.ROOT_ACCESS, Value.ROOT_ACCESS_APPS_AND_ADB)
|
var rootMode by dbSettings(
|
||||||
var suMntNamespaceMode by dbSettings(Key.SU_MNT_NS, Value.NAMESPACE_MODE_REQUESTER)
|
Key.ROOT_ACCESS,
|
||||||
var suMultiuserMode by dbSettings(Key.SU_MULTIUSER_MODE, Value.MULTIUSER_MODE_OWNER_ONLY)
|
Value.ROOT_ACCESS_APPS_AND_ADB
|
||||||
|
)
|
||||||
|
var suMntNamespaceMode by dbSettings(
|
||||||
|
Key.SU_MNT_NS,
|
||||||
|
Value.NAMESPACE_MODE_REQUESTER
|
||||||
|
)
|
||||||
|
var suMultiuserMode by dbSettings(
|
||||||
|
Key.SU_MULTIUSER_MODE,
|
||||||
|
Value.MULTIUSER_MODE_OWNER_ONLY
|
||||||
|
)
|
||||||
var suBiometric by dbSettings(Key.SU_BIOMETRIC, false)
|
var suBiometric by dbSettings(Key.SU_BIOMETRIC, false)
|
||||||
var suManager by dbStrings(Key.SU_MANAGER, "", true)
|
var suManager by dbStrings(Key.SU_MANAGER, "", true)
|
||||||
var keyStoreRaw by dbStrings(Key.KEYSTORE, "", true)
|
var keyStoreRaw by dbStrings(Key.KEYSTORE, "", true)
|
||||||
|
|
||||||
// Always return a path in external storage where we can write
|
// Always return a path in external storage where we can write
|
||||||
val downloadDirectory get() =
|
val downloadDirectory
|
||||||
|
get() =
|
||||||
Utils.ensureDownloadPath(downloadPath) ?: get<Context>().getExternalFilesDir(null)!!
|
Utils.ensureDownloadPath(downloadPath) ?: get<Context>().getExternalFilesDir(null)!!
|
||||||
|
|
||||||
private const val SU_FINGERPRINT = "su_fingerprint"
|
private const val SU_FINGERPRINT = "su_fingerprint"
|
||||||
@ -163,7 +199,9 @@ object Config : PreferenceModel, DBConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun parsePrefs(editor: SharedPreferences.Editor) = editor.apply {
|
private fun parsePrefs(editor: SharedPreferences.Editor) = editor.apply {
|
||||||
val config = SuFile.open("/data/adb", Const.MANAGER_CONFIGS)
|
val config = SuFile.open("/data/adb",
|
||||||
|
Const.MANAGER_CONFIGS
|
||||||
|
)
|
||||||
if (config.exists()) runCatching {
|
if (config.exists()) runCatching {
|
||||||
val input = SuFileInputStream(config)
|
val input = SuFileInputStream(config)
|
||||||
val parser = Xml.newPullParser()
|
val parser = Xml.newPullParser()
|
@ -1,4 +1,4 @@
|
|||||||
package com.topjohnwu.magisk
|
package com.topjohnwu.magisk.core
|
||||||
|
|
||||||
import android.os.Process
|
import android.os.Process
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@ -62,6 +62,7 @@ object Const {
|
|||||||
const val ETAG_KEY = "ETag"
|
const val ETAG_KEY = "ETag"
|
||||||
// intents
|
// intents
|
||||||
const val OPEN_SECTION = "section"
|
const val OPEN_SECTION = "section"
|
||||||
|
const val OPEN_SETTINGS = "settings"
|
||||||
const val INTENT_SET_APP = "app_json"
|
const val INTENT_SET_APP = "app_json"
|
||||||
const val FLASH_ACTION = "action"
|
const val FLASH_ACTION = "action"
|
||||||
const val FLASH_DATA = "additional_data"
|
const val FLASH_DATA = "additional_data"
|
@ -1,19 +1,16 @@
|
|||||||
package com.topjohnwu.magisk.model.receiver
|
package com.topjohnwu.magisk.core
|
||||||
|
|
||||||
import android.content.ContextWrapper
|
import android.content.ContextWrapper
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import com.topjohnwu.magisk.Config
|
import com.topjohnwu.magisk.core.base.BaseReceiver
|
||||||
import com.topjohnwu.magisk.Const
|
import com.topjohnwu.magisk.core.download.DownloadService
|
||||||
import com.topjohnwu.magisk.Info
|
import com.topjohnwu.magisk.core.magiskdb.PolicyDao
|
||||||
import com.topjohnwu.magisk.base.BaseReceiver
|
import com.topjohnwu.magisk.core.model.ManagerJson
|
||||||
import com.topjohnwu.magisk.data.database.PolicyDao
|
import com.topjohnwu.magisk.core.su.SuCallbackHandler
|
||||||
|
import com.topjohnwu.magisk.core.view.Shortcuts
|
||||||
import com.topjohnwu.magisk.extensions.reboot
|
import com.topjohnwu.magisk.extensions.reboot
|
||||||
import com.topjohnwu.magisk.model.download.DownloadService
|
|
||||||
import com.topjohnwu.magisk.model.entity.ManagerJson
|
|
||||||
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.utils.SuHandler
|
|
||||||
import com.topjohnwu.magisk.view.Shortcuts
|
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
import org.koin.core.inject
|
import org.koin.core.inject
|
||||||
|
|
||||||
@ -30,7 +27,7 @@ open class GeneralReceiver : BaseReceiver() {
|
|||||||
|
|
||||||
when (intent.action ?: return) {
|
when (intent.action ?: return) {
|
||||||
Intent.ACTION_REBOOT -> {
|
Intent.ACTION_REBOOT -> {
|
||||||
SuHandler(context, intent.getStringExtra("action"), intent.extras)
|
SuCallbackHandler(context, intent.getStringExtra("action"), intent.extras)
|
||||||
}
|
}
|
||||||
Intent.ACTION_PACKAGE_REPLACED -> {
|
Intent.ACTION_PACKAGE_REPLACED -> {
|
||||||
// This will only work pre-O
|
// This will only work pre-O
|
@ -1,6 +1,6 @@
|
|||||||
@file:Suppress("DEPRECATION")
|
@file:Suppress("DEPRECATION")
|
||||||
|
|
||||||
package com.topjohnwu.magisk
|
package com.topjohnwu.magisk.core
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.job.JobInfo
|
import android.app.job.JobInfo
|
||||||
@ -14,23 +14,22 @@ import android.content.res.AssetManager
|
|||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
|
import com.topjohnwu.magisk.DynAPK
|
||||||
|
import com.topjohnwu.magisk.ProcessPhoenix
|
||||||
|
import com.topjohnwu.magisk.core.download.DownloadService
|
||||||
|
import com.topjohnwu.magisk.core.utils.refreshLocale
|
||||||
|
import com.topjohnwu.magisk.core.utils.updateConfig
|
||||||
import com.topjohnwu.magisk.extensions.forceGetDeclaredField
|
import com.topjohnwu.magisk.extensions.forceGetDeclaredField
|
||||||
import com.topjohnwu.magisk.model.download.DownloadService
|
import com.topjohnwu.magisk.legacy.flash.FlashActivity
|
||||||
import com.topjohnwu.magisk.model.receiver.GeneralReceiver
|
import com.topjohnwu.magisk.legacy.surequest.SuRequestActivity
|
||||||
import com.topjohnwu.magisk.model.update.UpdateCheckService
|
|
||||||
import com.topjohnwu.magisk.ui.MainActivity
|
import com.topjohnwu.magisk.ui.MainActivity
|
||||||
import com.topjohnwu.magisk.ui.SplashActivity
|
|
||||||
import com.topjohnwu.magisk.ui.flash.FlashActivity
|
|
||||||
import com.topjohnwu.magisk.ui.surequest.SuRequestActivity
|
|
||||||
import com.topjohnwu.magisk.utils.refreshLocale
|
|
||||||
import com.topjohnwu.magisk.utils.updateConfig
|
|
||||||
|
|
||||||
fun AssetManager.addAssetPath(path: String) {
|
fun AssetManager.addAssetPath(path: String) {
|
||||||
DynAPK.addAssetPath(this, path)
|
DynAPK.addAssetPath(this, path)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Context.wrap(global: Boolean = true): Context
|
fun Context.wrap(global: Boolean = true): Context =
|
||||||
= if (global) GlobalResContext(this) else ResContext(this)
|
if (global) GlobalResContext(this) else ResContext(this)
|
||||||
|
|
||||||
fun Context.wrapJob(): Context = object : GlobalResContext(this) {
|
fun Context.wrapJob(): Context = object : GlobalResContext(this) {
|
||||||
|
|
||||||
@ -130,7 +129,8 @@ private class JobSchedulerWrapper(private val base: JobScheduler) : JobScheduler
|
|||||||
val name = service.className
|
val name = service.className
|
||||||
val component = ComponentName(
|
val component = ComponentName(
|
||||||
service.packageName,
|
service.packageName,
|
||||||
Info.stub!!.classToComponent[name] ?: name)
|
Info.stub!!.classToComponent[name] ?: name
|
||||||
|
)
|
||||||
|
|
||||||
javaClass.forceGetDeclaredField("service")?.set(this, component)
|
javaClass.forceGetDeclaredField("service")?.set(this, component)
|
||||||
return this
|
return this
|
@ -1,9 +1,10 @@
|
|||||||
package com.topjohnwu.magisk
|
package com.topjohnwu.magisk.core
|
||||||
|
|
||||||
import com.github.pwittchen.reactivenetwork.library.rx2.ReactiveNetwork
|
import com.github.pwittchen.reactivenetwork.library.rx2.ReactiveNetwork
|
||||||
|
import com.topjohnwu.magisk.DynAPK
|
||||||
|
import com.topjohnwu.magisk.core.model.UpdateInfo
|
||||||
import com.topjohnwu.magisk.extensions.get
|
import com.topjohnwu.magisk.extensions.get
|
||||||
import com.topjohnwu.magisk.extensions.subscribeK
|
import com.topjohnwu.magisk.extensions.subscribeK
|
||||||
import com.topjohnwu.magisk.model.entity.UpdateInfo
|
|
||||||
import com.topjohnwu.magisk.utils.CachedValue
|
import com.topjohnwu.magisk.utils.CachedValue
|
||||||
import com.topjohnwu.magisk.utils.KObservableField
|
import com.topjohnwu.magisk.utils.KObservableField
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
@ -17,12 +18,16 @@ object Info {
|
|||||||
|
|
||||||
val envRef = CachedValue { loadState() }
|
val envRef = CachedValue { loadState() }
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
val env by envRef // Local
|
val env by envRef // Local
|
||||||
var remote = UpdateInfo() // Remote
|
var remote = UpdateInfo() // Remote
|
||||||
var stub: DynAPK.Data? = null // Stub
|
var stub: DynAPK.Data? = null // Stub
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
var keepVerity = false
|
var keepVerity = false
|
||||||
|
@JvmStatic
|
||||||
var keepEnc = false
|
var keepEnc = false
|
||||||
|
@JvmStatic
|
||||||
var recovery = false
|
var recovery = false
|
||||||
|
|
||||||
val isConnected by lazy {
|
val isConnected by lazy {
|
@ -1,12 +1,13 @@
|
|||||||
package com.topjohnwu.magisk.ui
|
package com.topjohnwu.magisk.core
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import com.topjohnwu.magisk.*
|
import com.topjohnwu.magisk.BuildConfig
|
||||||
import com.topjohnwu.magisk.utils.Utils
|
import com.topjohnwu.magisk.model.navigation.Navigation
|
||||||
import com.topjohnwu.magisk.view.Notifications
|
import com.topjohnwu.magisk.core.utils.Utils
|
||||||
import com.topjohnwu.magisk.view.Shortcuts
|
import com.topjohnwu.magisk.core.view.Notifications
|
||||||
|
import com.topjohnwu.magisk.core.view.Shortcuts
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
import com.topjohnwu.superuser.ShellUtils
|
import com.topjohnwu.superuser.ShellUtils
|
||||||
|
|
||||||
@ -61,8 +62,7 @@ open class SplashActivity : Activity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
DONE = true
|
DONE = true
|
||||||
|
Navigation.start(intent, this)
|
||||||
startActivity(intent<MainActivity>().apply { intent?.also { putExtras(it) } })
|
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
|
|
@ -1,15 +1,14 @@
|
|||||||
package com.topjohnwu.magisk.model.update
|
package com.topjohnwu.magisk.core
|
||||||
|
|
||||||
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.core.base.BaseWorkerWrapper
|
||||||
import com.topjohnwu.magisk.base.DelegateWorker
|
import com.topjohnwu.magisk.core.view.Notifications
|
||||||
import com.topjohnwu.magisk.data.repository.MagiskRepository
|
import com.topjohnwu.magisk.data.repository.MagiskRepository
|
||||||
import com.topjohnwu.magisk.extensions.inject
|
import com.topjohnwu.magisk.extensions.inject
|
||||||
import com.topjohnwu.magisk.view.Notifications
|
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
|
|
||||||
class UpdateCheckService : DelegateWorker() {
|
class UpdateCheckService : BaseWorkerWrapper() {
|
||||||
|
|
||||||
private val magiskRepo: MagiskRepository by inject()
|
private val magiskRepo: MagiskRepository by inject()
|
||||||
|
|
@ -1,51 +1,26 @@
|
|||||||
package com.topjohnwu.magisk.base
|
package com.topjohnwu.magisk.core.base
|
||||||
|
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import android.os.Bundle
|
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.appcompat.app.AppCompatDelegate
|
|
||||||
import androidx.collection.SparseArrayCompat
|
import androidx.collection.SparseArrayCompat
|
||||||
import androidx.core.app.ActivityCompat
|
import androidx.core.app.ActivityCompat
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.databinding.DataBindingUtil
|
import com.topjohnwu.magisk.core.utils.currentLocale
|
||||||
import androidx.databinding.ViewDataBinding
|
import com.topjohnwu.magisk.core.wrap
|
||||||
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.extensions.set
|
||||||
import com.topjohnwu.magisk.model.events.EventHandler
|
|
||||||
import com.topjohnwu.magisk.model.permissions.PermissionRequestBuilder
|
import com.topjohnwu.magisk.model.permissions.PermissionRequestBuilder
|
||||||
import com.topjohnwu.magisk.utils.currentLocale
|
|
||||||
import com.topjohnwu.magisk.wrap
|
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
|
||||||
typealias RequestCallback = BaseActivity<*, *>.(Int, Intent?) -> Unit
|
typealias RequestCallback = BaseActivity.(Int, Intent?) -> Unit
|
||||||
|
|
||||||
abstract class BaseActivity<ViewModel : BaseViewModel, Binding : ViewDataBinding> :
|
abstract class BaseActivity : AppCompatActivity() {
|
||||||
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>() }
|
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?) {
|
override fun applyOverrideConfiguration(config: Configuration?) {
|
||||||
// Force applying our preferred local
|
// Force applying our preferred local
|
||||||
config?.setLocale(currentLocale)
|
config?.setLocale(currentLocale)
|
||||||
@ -56,18 +31,6 @@ abstract class BaseActivity<ViewModel : BaseViewModel, Binding : ViewDataBinding
|
|||||||
super.attachBaseContext(base.wrap(false))
|
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) {
|
fun withPermissions(vararg permissions: String, builder: PermissionRequestBuilder.() -> Unit) {
|
||||||
val request = PermissionRequestBuilder().apply(builder).build()
|
val request = PermissionRequestBuilder().apply(builder).build()
|
||||||
val ungranted = permissions.filter {
|
val ungranted = permissions.filter {
|
||||||
@ -101,18 +64,18 @@ abstract class BaseActivity<ViewModel : BaseViewModel, Binding : ViewDataBinding
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
resultCallbacks[requestCode]?.apply {
|
resultCallbacks[requestCode]?.also {
|
||||||
resultCallbacks.remove(requestCode)
|
resultCallbacks.remove(requestCode)
|
||||||
invoke(this@BaseActivity, if (success) 1 else -1, null)
|
it(this@BaseActivity, if (success) 1 else -1, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
super.onActivityResult(requestCode, resultCode, data)
|
super.onActivityResult(requestCode, resultCode, data)
|
||||||
resultCallbacks[requestCode]?.apply {
|
resultCallbacks[requestCode]?.also {
|
||||||
resultCallbacks.remove(requestCode)
|
resultCallbacks.remove(requestCode)
|
||||||
invoke(this@BaseActivity, resultCode, data)
|
it(this@BaseActivity, resultCode, data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,10 +1,10 @@
|
|||||||
package com.topjohnwu.magisk.base
|
package com.topjohnwu.magisk.core.base
|
||||||
|
|
||||||
import android.content.BroadcastReceiver
|
import android.content.BroadcastReceiver
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.ContextWrapper
|
import android.content.ContextWrapper
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import com.topjohnwu.magisk.wrap
|
import com.topjohnwu.magisk.core.wrap
|
||||||
import org.koin.core.KoinComponent
|
import org.koin.core.KoinComponent
|
||||||
|
|
||||||
abstract class BaseReceiver : BroadcastReceiver(), KoinComponent {
|
abstract class BaseReceiver : BroadcastReceiver(), KoinComponent {
|
@ -1,8 +1,8 @@
|
|||||||
package com.topjohnwu.magisk.base
|
package com.topjohnwu.magisk.core.base
|
||||||
|
|
||||||
import android.app.Service
|
import android.app.Service
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import com.topjohnwu.magisk.wrap
|
import com.topjohnwu.magisk.core.wrap
|
||||||
import org.koin.core.KoinComponent
|
import org.koin.core.KoinComponent
|
||||||
|
|
||||||
abstract class BaseService : Service(), KoinComponent {
|
abstract class BaseService : Service(), KoinComponent {
|
@ -1,4 +1,4 @@
|
|||||||
package com.topjohnwu.magisk.base
|
package com.topjohnwu.magisk.core.base
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.Network
|
import android.net.Network
|
||||||
@ -10,7 +10,7 @@ import androidx.work.ListenableWorker
|
|||||||
import com.google.common.util.concurrent.ListenableFuture
|
import com.google.common.util.concurrent.ListenableFuture
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
abstract class DelegateWorker {
|
abstract class BaseWorkerWrapper {
|
||||||
|
|
||||||
private lateinit var worker: ListenableWorker
|
private lateinit var worker: ListenableWorker
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
package com.topjohnwu.magisk.model.download
|
package com.topjohnwu.magisk.core.download
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.Notification
|
import android.app.Notification
|
||||||
@ -8,15 +8,15 @@ import android.content.Intent
|
|||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.webkit.MimeTypeMap
|
import android.webkit.MimeTypeMap
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
|
import com.topjohnwu.magisk.core.intent
|
||||||
import com.topjohnwu.magisk.extensions.chooser
|
import com.topjohnwu.magisk.extensions.chooser
|
||||||
import com.topjohnwu.magisk.extensions.exists
|
import com.topjohnwu.magisk.extensions.exists
|
||||||
import com.topjohnwu.magisk.extensions.provide
|
import com.topjohnwu.magisk.extensions.provide
|
||||||
import com.topjohnwu.magisk.intent
|
import com.topjohnwu.magisk.legacy.flash.FlashActivity
|
||||||
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
|
||||||
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.*
|
||||||
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 org.koin.core.get
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@ -114,13 +114,15 @@ open class DownloadService : RemoteFileService() {
|
|||||||
|
|
||||||
@Suppress("ReplaceSingleLineLet")
|
@Suppress("ReplaceSingleLineLet")
|
||||||
private fun Notification.Builder.setContentIntent(intent: Intent) =
|
private fun Notification.Builder.setContentIntent(intent: Intent) =
|
||||||
|
setContentIntent(
|
||||||
PendingIntent.getActivity(context, nextInt(), intent, PendingIntent.FLAG_ONE_SHOT)
|
PendingIntent.getActivity(context, nextInt(), intent, PendingIntent.FLAG_ONE_SHOT)
|
||||||
.let { setContentIntent(it) }
|
)
|
||||||
|
|
||||||
@Suppress("ReplaceSingleLineLet")
|
@Suppress("ReplaceSingleLineLet")
|
||||||
private fun Notification.Builder.addAction(icon: Int, title: Int, intent: Intent) =
|
private fun Notification.Builder.addAction(icon: Int, title: Int, intent: Intent) =
|
||||||
|
addAction(icon, getString(title),
|
||||||
PendingIntent.getActivity(context, nextInt(), intent, PendingIntent.FLAG_ONE_SHOT)
|
PendingIntent.getActivity(context, nextInt(), intent, PendingIntent.FLAG_ONE_SHOT)
|
||||||
.let { addAction(icon, getString(title), it) }
|
)
|
||||||
|
|
||||||
// ---
|
// ---
|
||||||
|
|
@ -1,11 +1,18 @@
|
|||||||
package com.topjohnwu.magisk.model.download
|
package com.topjohnwu.magisk.core.download
|
||||||
|
|
||||||
import com.topjohnwu.magisk.*
|
import com.topjohnwu.magisk.BuildConfig
|
||||||
|
import com.topjohnwu.magisk.DynAPK
|
||||||
|
import com.topjohnwu.magisk.ProcessPhoenix
|
||||||
|
import com.topjohnwu.magisk.R
|
||||||
|
import com.topjohnwu.magisk.core.Config
|
||||||
|
import com.topjohnwu.magisk.core.Info
|
||||||
|
import com.topjohnwu.magisk.core.intent
|
||||||
|
import com.topjohnwu.magisk.core.isRunningAsStub
|
||||||
|
import com.topjohnwu.magisk.core.utils.PatchAPK
|
||||||
import com.topjohnwu.magisk.extensions.writeTo
|
import com.topjohnwu.magisk.extensions.writeTo
|
||||||
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.utils.PatchAPK
|
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
package com.topjohnwu.magisk.model.download
|
package com.topjohnwu.magisk.core.download
|
||||||
|
|
||||||
import com.topjohnwu.magisk.extensions.withStreams
|
import com.topjohnwu.magisk.extensions.withStreams
|
||||||
import java.io.File
|
import java.io.File
|
@ -1,10 +1,10 @@
|
|||||||
package com.topjohnwu.magisk.model.download
|
package com.topjohnwu.magisk.core.download
|
||||||
|
|
||||||
import android.app.Notification
|
import android.app.Notification
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import com.topjohnwu.magisk.base.BaseService
|
import com.topjohnwu.magisk.core.base.BaseService
|
||||||
import com.topjohnwu.magisk.view.Notifications
|
import com.topjohnwu.magisk.core.view.Notifications
|
||||||
import org.koin.core.KoinComponent
|
import org.koin.core.KoinComponent
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.random.Random.Default.nextInt
|
import kotlin.random.Random.Default.nextInt
|
@ -1,8 +1,10 @@
|
|||||||
package com.topjohnwu.magisk.model.download
|
package com.topjohnwu.magisk.core.download
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.app.Notification
|
import android.app.Notification
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
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
|
||||||
@ -11,12 +13,13 @@ 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.*
|
||||||
import com.topjohnwu.magisk.utils.ProgressInputStream
|
import com.topjohnwu.magisk.core.utils.ProgressInputStream
|
||||||
import com.topjohnwu.magisk.view.Notifications
|
import com.topjohnwu.magisk.core.view.Notifications
|
||||||
import com.topjohnwu.superuser.ShellUtils
|
import com.topjohnwu.superuser.ShellUtils
|
||||||
import io.reactivex.Completable
|
import io.reactivex.Completable
|
||||||
import okhttp3.ResponseBody
|
import okhttp3.ResponseBody
|
||||||
import org.koin.android.ext.android.inject
|
import org.koin.android.ext.android.inject
|
||||||
|
import org.koin.core.KoinComponent
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
|
|
||||||
@ -39,11 +42,7 @@ abstract class RemoteFileService : NotificationService() {
|
|||||||
.doOnSubscribe { update(subject.hashCode()) { it.setContentTitle(subject.title) } }
|
.doOnSubscribe { update(subject.hashCode()) { it.setContentTitle(subject.title) } }
|
||||||
.subscribeK(onError = {
|
.subscribeK(onError = {
|
||||||
Timber.e(it)
|
Timber.e(it)
|
||||||
finishNotify(subject.hashCode()) { notification ->
|
failNotify(subject)
|
||||||
notification.setContentText(getString(R.string.download_file_error))
|
|
||||||
.setSmallIcon(android.R.drawable.stat_notify_error)
|
|
||||||
.setOngoing(false)
|
|
||||||
}
|
|
||||||
}) {
|
}) {
|
||||||
val newId = finishNotify(subject)
|
val newId = finishNotify(subject)
|
||||||
if (get<Activity>() !is NullActivity) {
|
if (get<Activity>() !is NullActivity) {
|
||||||
@ -62,7 +61,7 @@ abstract class RemoteFileService : NotificationService() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun download(subject: DownloadSubject) = service.fetchFile(subject.url)
|
private fun download(subject: DownloadSubject) = service.fetchFile(subject.url)
|
||||||
.map { it.toStream(subject.hashCode()) }
|
.map { it.toStream(subject.hashCode(), subject) }
|
||||||
.flatMapCompletable { stream ->
|
.flatMapCompletable { stream ->
|
||||||
when (subject) {
|
when (subject) {
|
||||||
is Module -> service.fetchInstaller()
|
is Module -> service.fetchInstaller()
|
||||||
@ -75,7 +74,7 @@ abstract class RemoteFileService : NotificationService() {
|
|||||||
handleAPK(subject)
|
handleAPK(subject)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun ResponseBody.toStream(id: Int): InputStream {
|
private fun ResponseBody.toStream(id: Int, subject: DownloadSubject): InputStream {
|
||||||
val maxRaw = contentLength()
|
val maxRaw = contentLength()
|
||||||
val max = maxRaw / 1_000_000f
|
val max = maxRaw / 1_000_000f
|
||||||
|
|
||||||
@ -83,17 +82,27 @@ abstract class RemoteFileService : NotificationService() {
|
|||||||
val progress = it / 1_000_000f
|
val progress = it / 1_000_000f
|
||||||
update(id) { notification ->
|
update(id) { notification ->
|
||||||
if (maxRaw > 0) {
|
if (maxRaw > 0) {
|
||||||
|
send(progress / max, subject)
|
||||||
notification
|
notification
|
||||||
.setProgress(maxRaw.toInt(), it.toInt(), false)
|
.setProgress(maxRaw.toInt(), it.toInt(), false)
|
||||||
.setContentText("%.2f / %.2f MB".format(progress, max))
|
.setContentText("%.2f / %.2f MB".format(progress, max))
|
||||||
} else {
|
} else {
|
||||||
|
send(-1f, subject)
|
||||||
notification.setContentText("%.2f MB / ??".format(progress))
|
notification.setContentText("%.2f MB / ??".format(progress))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun failNotify(subject: DownloadSubject) = finishNotify(subject.hashCode()) {
|
||||||
|
send(0f, subject)
|
||||||
|
it.setContentText(getString(R.string.download_file_error))
|
||||||
|
.setSmallIcon(android.R.drawable.stat_notify_error)
|
||||||
|
.setOngoing(false)
|
||||||
|
}
|
||||||
|
|
||||||
private fun finishNotify(subject: DownloadSubject) = finishNotify(subject.hashCode()) {
|
private fun finishNotify(subject: DownloadSubject) = finishNotify(subject.hashCode()) {
|
||||||
|
send(1f, subject)
|
||||||
it.addActions(subject)
|
it.addActions(subject)
|
||||||
.setContentText(getString(R.string.download_complete))
|
.setContentText(getString(R.string.download_complete))
|
||||||
.setSmallIcon(android.R.drawable.stat_sys_download_done)
|
.setSmallIcon(android.R.drawable.stat_sys_download_done)
|
||||||
@ -111,8 +120,19 @@ abstract class RemoteFileService : NotificationService() {
|
|||||||
protected abstract fun Notification.Builder.addActions(subject: DownloadSubject)
|
protected abstract fun Notification.Builder.addActions(subject: DownloadSubject)
|
||||||
: Notification.Builder
|
: Notification.Builder
|
||||||
|
|
||||||
companion object {
|
companion object : KoinComponent {
|
||||||
const val ARG_URL = "arg_url"
|
const val ARG_URL = "arg_url"
|
||||||
|
|
||||||
|
private val internalProgressBroadcast = MutableLiveData<Pair<Float, DownloadSubject>>()
|
||||||
|
val progressBroadcast: LiveData<Pair<Float, DownloadSubject>> get() = internalProgressBroadcast
|
||||||
|
|
||||||
|
fun send(progress: Float, subject: DownloadSubject) {
|
||||||
|
internalProgressBroadcast.postValue(progress to subject)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun reset() {
|
||||||
|
internalProgressBroadcast.value = null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package com.topjohnwu.magisk.data.database.magiskdb
|
package com.topjohnwu.magisk.core.magiskdb
|
||||||
|
|
||||||
import androidx.annotation.StringDef
|
import androidx.annotation.StringDef
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
@ -1,16 +1,12 @@
|
|||||||
package com.topjohnwu.magisk.data.database
|
package com.topjohnwu.magisk.core.magiskdb
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import com.topjohnwu.magisk.Const
|
import com.topjohnwu.magisk.core.Const
|
||||||
import com.topjohnwu.magisk.data.database.magiskdb.BaseDao
|
import com.topjohnwu.magisk.core.model.MagiskPolicy
|
||||||
import com.topjohnwu.magisk.data.database.magiskdb.Delete
|
import com.topjohnwu.magisk.core.model.toMap
|
||||||
import com.topjohnwu.magisk.data.database.magiskdb.Replace
|
import com.topjohnwu.magisk.core.model.toPolicy
|
||||||
import com.topjohnwu.magisk.data.database.magiskdb.Select
|
|
||||||
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.toMap
|
|
||||||
import com.topjohnwu.magisk.model.entity.toPolicy
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
package com.topjohnwu.magisk.data.database.magiskdb
|
package com.topjohnwu.magisk.core.magiskdb
|
||||||
|
|
||||||
import androidx.annotation.StringDef
|
import androidx.annotation.StringDef
|
||||||
|
|
@ -1,9 +1,4 @@
|
|||||||
package com.topjohnwu.magisk.data.database
|
package com.topjohnwu.magisk.core.magiskdb
|
||||||
|
|
||||||
import com.topjohnwu.magisk.data.database.magiskdb.BaseDao
|
|
||||||
import com.topjohnwu.magisk.data.database.magiskdb.Delete
|
|
||||||
import com.topjohnwu.magisk.data.database.magiskdb.Replace
|
|
||||||
import com.topjohnwu.magisk.data.database.magiskdb.Select
|
|
||||||
|
|
||||||
class SettingsDao : BaseDao() {
|
class SettingsDao : BaseDao() {
|
||||||
|
|
@ -1,9 +1,4 @@
|
|||||||
package com.topjohnwu.magisk.data.database
|
package com.topjohnwu.magisk.core.magiskdb
|
||||||
|
|
||||||
import com.topjohnwu.magisk.data.database.magiskdb.BaseDao
|
|
||||||
import com.topjohnwu.magisk.data.database.magiskdb.Delete
|
|
||||||
import com.topjohnwu.magisk.data.database.magiskdb.Replace
|
|
||||||
import com.topjohnwu.magisk.data.database.magiskdb.Select
|
|
||||||
|
|
||||||
class StringDao : BaseDao() {
|
class StringDao : BaseDao() {
|
||||||
|
|
@ -1,9 +1,9 @@
|
|||||||
package com.topjohnwu.magisk.model.entity
|
package com.topjohnwu.magisk.core.model
|
||||||
|
|
||||||
import android.content.pm.ApplicationInfo
|
import android.content.pm.ApplicationInfo
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
|
import com.topjohnwu.magisk.core.model.MagiskPolicy.Companion.INTERACTIVE
|
||||||
import com.topjohnwu.magisk.extensions.getLabel
|
import com.topjohnwu.magisk.extensions.getLabel
|
||||||
import com.topjohnwu.magisk.model.entity.MagiskPolicy.Companion.INTERACTIVE
|
|
||||||
|
|
||||||
|
|
||||||
data class MagiskPolicy(
|
data class MagiskPolicy(
|
@ -1,4 +1,4 @@
|
|||||||
package com.topjohnwu.magisk.model.entity
|
package com.topjohnwu.magisk.core.model
|
||||||
|
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import kotlinx.android.parcel.Parcelize
|
import kotlinx.android.parcel.Parcelize
|
@ -1,4 +1,4 @@
|
|||||||
package com.topjohnwu.magisk.model.entity.module
|
package com.topjohnwu.magisk.core.model.module
|
||||||
|
|
||||||
abstract class BaseModule : Comparable<BaseModule> {
|
abstract class BaseModule : Comparable<BaseModule> {
|
||||||
abstract var id: String
|
abstract var id: String
|
@ -1,7 +1,7 @@
|
|||||||
package com.topjohnwu.magisk.model.entity.module
|
package com.topjohnwu.magisk.core.model.module
|
||||||
|
|
||||||
import androidx.annotation.WorkerThread
|
import androidx.annotation.WorkerThread
|
||||||
import com.topjohnwu.magisk.Const
|
import com.topjohnwu.magisk.core.Const
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
import com.topjohnwu.superuser.io.SuFile
|
import com.topjohnwu.superuser.io.SuFile
|
||||||
|
|
@ -1,9 +1,9 @@
|
|||||||
package com.topjohnwu.magisk.model.entity.module
|
package com.topjohnwu.magisk.core.model.module
|
||||||
|
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import androidx.room.Entity
|
import androidx.room.Entity
|
||||||
import androidx.room.PrimaryKey
|
import androidx.room.PrimaryKey
|
||||||
import com.topjohnwu.magisk.Const
|
import com.topjohnwu.magisk.core.Const
|
||||||
import com.topjohnwu.magisk.data.repository.StringRepository
|
import com.topjohnwu.magisk.data.repository.StringRepository
|
||||||
import com.topjohnwu.magisk.extensions.get
|
import com.topjohnwu.magisk.extensions.get
|
||||||
import com.topjohnwu.magisk.extensions.legalFilename
|
import com.topjohnwu.magisk.extensions.legalFilename
|
@ -1,4 +1,4 @@
|
|||||||
package com.topjohnwu.magisk.utils
|
package com.topjohnwu.magisk.core.su
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
@ -6,20 +6,26 @@ import android.os.Build
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Process
|
import android.os.Process
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import com.topjohnwu.magisk.*
|
import com.topjohnwu.magisk.BuildConfig
|
||||||
|
import com.topjohnwu.magisk.ProviderCallHandler
|
||||||
|
import com.topjohnwu.magisk.R
|
||||||
|
import com.topjohnwu.magisk.core.Config
|
||||||
|
import com.topjohnwu.magisk.core.intent
|
||||||
|
import com.topjohnwu.magisk.core.model.MagiskPolicy
|
||||||
|
import com.topjohnwu.magisk.core.model.toPolicy
|
||||||
|
import com.topjohnwu.magisk.core.wrap
|
||||||
import com.topjohnwu.magisk.data.repository.LogRepository
|
import com.topjohnwu.magisk.data.repository.LogRepository
|
||||||
import com.topjohnwu.magisk.extensions.get
|
import com.topjohnwu.magisk.extensions.get
|
||||||
import com.topjohnwu.magisk.extensions.startActivity
|
import com.topjohnwu.magisk.extensions.startActivity
|
||||||
import com.topjohnwu.magisk.extensions.startActivityWithRoot
|
import com.topjohnwu.magisk.extensions.startActivityWithRoot
|
||||||
import com.topjohnwu.magisk.extensions.subscribeK
|
import com.topjohnwu.magisk.extensions.subscribeK
|
||||||
import com.topjohnwu.magisk.model.entity.MagiskPolicy
|
import com.topjohnwu.magisk.legacy.surequest.SuRequestActivity
|
||||||
import com.topjohnwu.magisk.model.entity.toLog
|
import com.topjohnwu.magisk.model.entity.toLog
|
||||||
import com.topjohnwu.magisk.model.entity.toPolicy
|
import com.topjohnwu.magisk.core.utils.Utils
|
||||||
import com.topjohnwu.magisk.ui.surequest.SuRequestActivity
|
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
object SuHandler : ProviderCallHandler {
|
object SuCallbackHandler : ProviderCallHandler {
|
||||||
|
|
||||||
const val REQUEST = "request"
|
const val REQUEST = "request"
|
||||||
const val LOG = "log"
|
const val LOG = "log"
|
@ -1,4 +1,4 @@
|
|||||||
package com.topjohnwu.magisk.utils
|
package com.topjohnwu.magisk.core.su
|
||||||
|
|
||||||
import android.net.LocalSocket
|
import android.net.LocalSocket
|
||||||
import android.net.LocalSocketAddress
|
import android.net.LocalSocketAddress
|
@ -0,0 +1,103 @@
|
|||||||
|
package com.topjohnwu.magisk.core.su
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.os.CountDownTimer
|
||||||
|
import com.topjohnwu.magisk.BuildConfig
|
||||||
|
import com.topjohnwu.magisk.core.Config
|
||||||
|
import com.topjohnwu.magisk.core.Const
|
||||||
|
import com.topjohnwu.magisk.core.magiskdb.PolicyDao
|
||||||
|
import com.topjohnwu.magisk.core.model.MagiskPolicy
|
||||||
|
import com.topjohnwu.magisk.core.model.toPolicy
|
||||||
|
import com.topjohnwu.magisk.extensions.now
|
||||||
|
import timber.log.Timber
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
abstract class SuRequestHandler(
|
||||||
|
private val packageManager: PackageManager,
|
||||||
|
private val policyDB: PolicyDao
|
||||||
|
) {
|
||||||
|
protected var timer: CountDownTimer = object : CountDownTimer(
|
||||||
|
TimeUnit.MINUTES.toMillis(1), TimeUnit.MINUTES.toMillis(1)) {
|
||||||
|
override fun onFinish() {
|
||||||
|
respond(MagiskPolicy.DENY, 0)
|
||||||
|
}
|
||||||
|
override fun onTick(remains: Long) {}
|
||||||
|
}
|
||||||
|
set(value) {
|
||||||
|
field.cancel()
|
||||||
|
field = value
|
||||||
|
field.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
protected lateinit var policy: MagiskPolicy
|
||||||
|
|
||||||
|
private val cleanupTasks = mutableListOf<() -> Unit>()
|
||||||
|
private lateinit var connector: SuConnector
|
||||||
|
|
||||||
|
abstract fun onStart()
|
||||||
|
abstract fun onRespond()
|
||||||
|
|
||||||
|
fun start(intent: Intent): Boolean {
|
||||||
|
val socketName = intent.getStringExtra("socket") ?: return false
|
||||||
|
|
||||||
|
try {
|
||||||
|
connector = object : SuConnector(socketName) {
|
||||||
|
override fun onResponse() {
|
||||||
|
out.writeInt(policy.policy)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val map = connector.readRequest()
|
||||||
|
val uid = map["uid"]?.toIntOrNull() ?: return false
|
||||||
|
policy = uid.toPolicy(packageManager)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Timber.e(e)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Never allow com.topjohnwu.magisk (could be malware)
|
||||||
|
if (policy.packageName == BuildConfig.APPLICATION_ID)
|
||||||
|
return false
|
||||||
|
|
||||||
|
when (Config.suAutoReponse) {
|
||||||
|
Config.Value.SU_AUTO_DENY -> {
|
||||||
|
respond(MagiskPolicy.DENY, 0)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
Config.Value.SU_AUTO_ALLOW -> {
|
||||||
|
respond(MagiskPolicy.ALLOW, 0)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
timer.start()
|
||||||
|
cleanupTasks.add {
|
||||||
|
timer.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
onStart()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun respond() {
|
||||||
|
connector.response()
|
||||||
|
cleanupTasks.forEach { it() }
|
||||||
|
onRespond()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun respond(action: Int, time: Int) {
|
||||||
|
val until = if (time > 0)
|
||||||
|
TimeUnit.MILLISECONDS.toSeconds(now) + TimeUnit.MINUTES.toSeconds(time.toLong())
|
||||||
|
else
|
||||||
|
time.toLong()
|
||||||
|
|
||||||
|
policy.policy = action
|
||||||
|
policy.until = until
|
||||||
|
policy.uid = policy.uid % 100000 + Const.USER_ID * 100000
|
||||||
|
|
||||||
|
if (until >= 0)
|
||||||
|
policyDB.update(policy).blockingAwait()
|
||||||
|
|
||||||
|
respond()
|
||||||
|
}
|
||||||
|
}
|
@ -1,13 +1,13 @@
|
|||||||
package com.topjohnwu.magisk.tasks
|
package com.topjohnwu.magisk.core.tasks
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import com.topjohnwu.magisk.Const
|
import com.topjohnwu.magisk.core.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.extensions.subscribeK
|
||||||
import com.topjohnwu.magisk.utils.unzip
|
import com.topjohnwu.magisk.core.utils.unzip
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
import io.reactivex.Single
|
import io.reactivex.Single
|
||||||
import java.io.File
|
import java.io.File
|
@ -1,4 +1,4 @@
|
|||||||
package com.topjohnwu.magisk.tasks
|
package com.topjohnwu.magisk.core.tasks
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
@ -6,11 +6,13 @@ 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.topjohnwu.magisk.Config
|
import androidx.core.net.toUri
|
||||||
import com.topjohnwu.magisk.Info
|
import com.topjohnwu.magisk.core.Info
|
||||||
|
import com.topjohnwu.magisk.core.Config
|
||||||
import com.topjohnwu.magisk.data.network.GithubRawServices
|
import com.topjohnwu.magisk.data.network.GithubRawServices
|
||||||
import com.topjohnwu.magisk.di.Protected
|
import com.topjohnwu.magisk.di.Protected
|
||||||
import com.topjohnwu.magisk.extensions.*
|
import com.topjohnwu.magisk.extensions.*
|
||||||
|
import com.topjohnwu.magisk.net.Networking
|
||||||
import com.topjohnwu.signing.SignBoot
|
import com.topjohnwu.signing.SignBoot
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
import com.topjohnwu.superuser.ShellUtils
|
import com.topjohnwu.superuser.ShellUtils
|
||||||
@ -34,10 +36,10 @@ import java.util.zip.ZipInputStream
|
|||||||
|
|
||||||
abstract class MagiskInstaller {
|
abstract class MagiskInstaller {
|
||||||
|
|
||||||
protected lateinit var srcBoot: String
|
|
||||||
protected lateinit var destFile: File
|
|
||||||
protected lateinit var installDir: File
|
protected lateinit var installDir: File
|
||||||
protected lateinit var zipUri: Uri
|
private lateinit var srcBoot: String
|
||||||
|
private lateinit var destFile: File
|
||||||
|
private lateinit var zipUri: Uri
|
||||||
|
|
||||||
private val console: MutableList<String>
|
private val console: MutableList<String>
|
||||||
private val logs: MutableList<String>
|
private val logs: MutableList<String>
|
||||||
@ -60,7 +62,7 @@ abstract class MagiskInstaller {
|
|||||||
installDir.mkdirs()
|
installDir.mkdirs()
|
||||||
}
|
}
|
||||||
|
|
||||||
protected fun findImage(): Boolean {
|
private fun findImage(): Boolean {
|
||||||
srcBoot = "find_boot_image; echo \"\$BOOTIMAGE\"".fsh()
|
srcBoot = "find_boot_image; echo \"\$BOOTIMAGE\"".fsh()
|
||||||
if (srcBoot.isEmpty()) {
|
if (srcBoot.isEmpty()) {
|
||||||
console.add("! Unable to detect target image")
|
console.add("! Unable to detect target image")
|
||||||
@ -70,7 +72,7 @@ abstract class MagiskInstaller {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
protected fun findSecondaryImage(): Boolean {
|
private fun findSecondaryImage(): Boolean {
|
||||||
val slot = "echo \$SLOT".fsh()
|
val slot = "echo \$SLOT".fsh()
|
||||||
val target = if (slot == "_a") "_b" else "_a"
|
val target = if (slot == "_a") "_b" else "_a"
|
||||||
console.add("- Target slot: $target")
|
console.add("- Target slot: $target")
|
||||||
@ -87,7 +89,7 @@ abstract class MagiskInstaller {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
protected fun extractZip(): Boolean {
|
private fun extractZip(): Boolean {
|
||||||
val arch: String
|
val arch: String
|
||||||
arch = if (Build.VERSION.SDK_INT >= 21) {
|
arch = if (Build.VERSION.SDK_INT >= 21) {
|
||||||
val abis = listOf(*Build.SUPPORTED_ABIS)
|
val abis = listOf(*Build.SUPPORTED_ABIS)
|
||||||
@ -208,7 +210,7 @@ abstract class MagiskInstaller {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected fun handleFile(uri: Uri): Boolean {
|
private fun handleFile(uri: Uri): Boolean {
|
||||||
try {
|
try {
|
||||||
context.readUri(uri).buffered().use {
|
context.readUri(uri).buffered().use {
|
||||||
it.mark(500)
|
it.mark(500)
|
||||||
@ -238,7 +240,7 @@ abstract class MagiskInstaller {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
protected fun patchBoot(): Boolean {
|
private fun patchBoot(): Boolean {
|
||||||
var isSigned = false
|
var isSigned = false
|
||||||
try {
|
try {
|
||||||
SuFileInputStream(srcBoot).use {
|
SuFileInputStream(srcBoot).use {
|
||||||
@ -284,7 +286,7 @@ abstract class MagiskInstaller {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
protected fun flashBoot(): Boolean {
|
private fun flashBoot(): Boolean {
|
||||||
if (!"direct_install $installDir $srcBoot".sh().isSuccess)
|
if (!"direct_install $installDir $srcBoot".sh().isSuccess)
|
||||||
return false
|
return false
|
||||||
arrayOf(
|
arrayOf(
|
||||||
@ -294,7 +296,7 @@ abstract class MagiskInstaller {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
protected fun storeBoot(): Boolean {
|
private fun storeBoot(): Boolean {
|
||||||
val patched = SuFile.open(installDir, "new-boot.img")
|
val patched = SuFile.open(installDir, "new-boot.img")
|
||||||
try {
|
try {
|
||||||
val os = tarOut?.let {
|
val os = tarOut?.let {
|
||||||
@ -320,7 +322,7 @@ abstract class MagiskInstaller {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
protected fun postOTA(): Boolean {
|
private fun postOTA(): Boolean {
|
||||||
val bootctl = SuFile("/data/adb/bootctl")
|
val bootctl = SuFile("/data/adb/bootctl")
|
||||||
try {
|
try {
|
||||||
withStreams(service.fetchBootctl().blockingGet().byteStream(), bootctl.suOutputStream()) {
|
withStreams(service.fetchBootctl().blockingGet().byteStream(), bootctl.suOutputStream()) {
|
||||||
@ -345,6 +347,28 @@ abstract class MagiskInstaller {
|
|||||||
private fun String.fsh() = ShellUtils.fastCmd(this)
|
private fun String.fsh() = ShellUtils.fastCmd(this)
|
||||||
private fun Array<String>.fsh() = ShellUtils.fastCmd(*this)
|
private fun Array<String>.fsh() = ShellUtils.fastCmd(*this)
|
||||||
|
|
||||||
|
protected fun doPatchFile(patchFile: Uri) =
|
||||||
|
extractZip() && handleFile(patchFile) && patchBoot() && storeBoot()
|
||||||
|
|
||||||
|
protected fun direct() = findImage() && extractZip() && patchBoot() && flashBoot()
|
||||||
|
|
||||||
|
protected fun secondSlot() =
|
||||||
|
findSecondaryImage() && extractZip() && patchBoot() && flashBoot() && postOTA()
|
||||||
|
|
||||||
|
protected fun fixEnv(): Boolean {
|
||||||
|
val context = get<Context>()
|
||||||
|
val zip: File = context.cachedFile("magisk.zip")
|
||||||
|
|
||||||
|
installDir = SuFile("/data/adb/magisk")
|
||||||
|
Shell.su("rm -rf /data/adb/magisk/*").exec()
|
||||||
|
|
||||||
|
if (!ShellUtils.checkSum("MD5", zip, Info.remote.magisk.md5))
|
||||||
|
Networking.get(Info.remote.magisk.link).execForFile(zip)
|
||||||
|
|
||||||
|
zipUri = zip.toUri()
|
||||||
|
return extractZip() && Shell.su("fix_env").exec().isSuccess
|
||||||
|
}
|
||||||
|
|
||||||
@WorkerThread
|
@WorkerThread
|
||||||
protected abstract fun operations(): Boolean
|
protected abstract fun operations(): Boolean
|
||||||
|
|
101
app/src/main/java/com/topjohnwu/magisk/core/tasks/RepoUpdater.kt
Normal file
101
app/src/main/java/com/topjohnwu/magisk/core/tasks/RepoUpdater.kt
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
package com.topjohnwu.magisk.core.tasks
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.core.Const
|
||||||
|
import com.topjohnwu.magisk.core.model.module.Repo
|
||||||
|
import com.topjohnwu.magisk.data.database.RepoDao
|
||||||
|
import com.topjohnwu.magisk.data.network.GithubApiServices
|
||||||
|
import io.reactivex.Completable
|
||||||
|
import io.reactivex.Flowable
|
||||||
|
import io.reactivex.rxkotlin.toFlowable
|
||||||
|
import io.reactivex.schedulers.Schedulers
|
||||||
|
import se.ansman.kotshi.JsonSerializable
|
||||||
|
import timber.log.Timber
|
||||||
|
import java.net.HttpURLConnection
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.*
|
||||||
|
import kotlin.collections.HashSet
|
||||||
|
|
||||||
|
class RepoUpdater(
|
||||||
|
private val api: GithubApiServices,
|
||||||
|
private val repoDB: RepoDao
|
||||||
|
) {
|
||||||
|
private fun loadRepos(repos: List<GithubRepoInfo>, cached: MutableSet<String>) =
|
||||||
|
repos.toFlowable().parallel().runOn(Schedulers.io()).map {
|
||||||
|
// Skip submission
|
||||||
|
if (it.id == "submission")
|
||||||
|
return@map
|
||||||
|
val repo = repoDB.getRepo(it.id)?.apply { cached.remove(it.id) } ?: Repo(it.id)
|
||||||
|
repo.runCatching {
|
||||||
|
update(it.pushDate)
|
||||||
|
repoDB.addRepo(this)
|
||||||
|
}.getOrElse(Timber::e)
|
||||||
|
}.sequential()
|
||||||
|
|
||||||
|
private fun loadPage(
|
||||||
|
cached: MutableSet<String>,
|
||||||
|
page: Int = 1,
|
||||||
|
etag: String = ""
|
||||||
|
): Flowable<Unit> = api.fetchRepos(page, etag).flatMap {
|
||||||
|
it.error()?.also { throw it }
|
||||||
|
it.response()?.run {
|
||||||
|
if (code() == HttpURLConnection.HTTP_NOT_MODIFIED)
|
||||||
|
return@run Flowable.error<Unit>(CachedException())
|
||||||
|
|
||||||
|
if (page == 1)
|
||||||
|
repoDB.etagKey = headers()[Const.Key.ETAG_KEY].orEmpty().trimEtag()
|
||||||
|
|
||||||
|
val flow = loadRepos(body()!!, cached)
|
||||||
|
if (headers()[Const.Key.LINK_KEY].orEmpty().contains("next")) {
|
||||||
|
flow.mergeWith(loadPage(cached, page + 1))
|
||||||
|
} else {
|
||||||
|
flow
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun forcedReload(cached: MutableSet<String>) =
|
||||||
|
cached.toFlowable().parallel().runOn(Schedulers.io()).map {
|
||||||
|
runCatching {
|
||||||
|
Repo(it).update()
|
||||||
|
}.getOrElse(Timber::e)
|
||||||
|
}.sequential()
|
||||||
|
|
||||||
|
private fun String.trimEtag() = substring(indexOf('\"'), lastIndexOf('\"') + 1)
|
||||||
|
|
||||||
|
@Suppress("RedundantLambdaArrow")
|
||||||
|
operator fun invoke(forced: Boolean) : Completable {
|
||||||
|
return Flowable
|
||||||
|
.fromCallable { Collections.synchronizedSet(HashSet(repoDB.repoIDList)) }
|
||||||
|
.flatMap { cached ->
|
||||||
|
loadPage(cached, etag = repoDB.etagKey).doOnComplete {
|
||||||
|
repoDB.removeRepos(cached)
|
||||||
|
}.onErrorResumeNext { it: Throwable ->
|
||||||
|
if (it is CachedException) {
|
||||||
|
if (forced)
|
||||||
|
return@onErrorResumeNext forcedReload(cached)
|
||||||
|
} else {
|
||||||
|
Timber.e(it)
|
||||||
|
}
|
||||||
|
Flowable.empty()
|
||||||
|
}
|
||||||
|
}.ignoreElements()
|
||||||
|
}
|
||||||
|
|
||||||
|
class CachedException : Exception()
|
||||||
|
}
|
||||||
|
|
||||||
|
private val dateFormat: SimpleDateFormat =
|
||||||
|
SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US).apply {
|
||||||
|
timeZone = TimeZone.getTimeZone("UTC")
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonSerializable
|
||||||
|
data class GithubRepoInfo(
|
||||||
|
val name: String,
|
||||||
|
val pushed_at: String
|
||||||
|
) {
|
||||||
|
val id get() = name
|
||||||
|
|
||||||
|
@Transient
|
||||||
|
val pushDate = dateFormat.parse(pushed_at)!!
|
||||||
|
}
|
@ -1,11 +1,11 @@
|
|||||||
package com.topjohnwu.magisk.utils
|
package com.topjohnwu.magisk.core.utils
|
||||||
|
|
||||||
import androidx.biometric.BiometricManager
|
import androidx.biometric.BiometricManager
|
||||||
import androidx.biometric.BiometricPrompt
|
import androidx.biometric.BiometricPrompt
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.fragment.app.FragmentActivity
|
import androidx.fragment.app.FragmentActivity
|
||||||
import com.topjohnwu.magisk.Config
|
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
|
import com.topjohnwu.magisk.core.Config
|
||||||
import org.koin.core.KoinComponent
|
import org.koin.core.KoinComponent
|
||||||
import org.koin.core.get
|
import org.koin.core.get
|
||||||
|
|
@ -1,11 +1,11 @@
|
|||||||
package com.topjohnwu.magisk.utils
|
package com.topjohnwu.magisk.core.utils
|
||||||
|
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.util.Base64
|
import android.util.Base64
|
||||||
import android.util.Base64OutputStream
|
import android.util.Base64OutputStream
|
||||||
import com.topjohnwu.magisk.Config
|
import com.topjohnwu.magisk.core.Config
|
||||||
|
import com.topjohnwu.magisk.core.utils.PatchAPK.ALPHANUM
|
||||||
import com.topjohnwu.magisk.di.koinModules
|
import com.topjohnwu.magisk.di.koinModules
|
||||||
import com.topjohnwu.magisk.utils.PatchAPK.ALPHANUM
|
|
||||||
import com.topjohnwu.signing.CryptoUtils.readCertificate
|
import com.topjohnwu.signing.CryptoUtils.readCertificate
|
||||||
import com.topjohnwu.signing.CryptoUtils.readPrivateKey
|
import com.topjohnwu.signing.CryptoUtils.readPrivateKey
|
||||||
import com.topjohnwu.superuser.internal.InternalUtils
|
import com.topjohnwu.superuser.internal.InternalUtils
|
||||||
@ -50,10 +50,14 @@ class Keygen: CertKeyProvider {
|
|||||||
|
|
||||||
private val provider: CertKeyProvider
|
private val provider: CertKeyProvider
|
||||||
|
|
||||||
inner class KeyStoreProvider : CertKeyProvider {
|
inner class KeyStoreProvider :
|
||||||
|
CertKeyProvider {
|
||||||
private val ks by lazy { init() }
|
private val ks by lazy { init() }
|
||||||
override val cert by lazy { ks.getCertificate(ALIAS) as X509Certificate }
|
override val cert by lazy { ks.getCertificate(ALIAS) as X509Certificate }
|
||||||
override val key by lazy { ks.getKey(ALIAS, PASSWORD) as PrivateKey }
|
override val key by lazy { ks.getKey(
|
||||||
|
ALIAS,
|
||||||
|
PASSWORD
|
||||||
|
) as PrivateKey }
|
||||||
}
|
}
|
||||||
|
|
||||||
class TestProvider : CertKeyProvider {
|
class TestProvider : CertKeyProvider {
|
||||||
@ -113,8 +117,12 @@ class Keygen: CertKeyProvider {
|
|||||||
if (raw.isEmpty()) {
|
if (raw.isEmpty()) {
|
||||||
ks.load(null)
|
ks.load(null)
|
||||||
} else {
|
} else {
|
||||||
GZIPInputStream(Base64.decode(raw, BASE64_FLAG).inputStream()).use {
|
GZIPInputStream(Base64.decode(raw,
|
||||||
ks.load(it, PASSWORD)
|
BASE64_FLAG
|
||||||
|
).inputStream()).use {
|
||||||
|
ks.load(it,
|
||||||
|
PASSWORD
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -131,10 +139,16 @@ class Keygen: CertKeyProvider {
|
|||||||
val cert = JcaX509CertificateConverter().getCertificate(builder.build(signer))
|
val cert = JcaX509CertificateConverter().getCertificate(builder.build(signer))
|
||||||
|
|
||||||
// Store them into keystore
|
// Store them into keystore
|
||||||
ks.setKeyEntry(ALIAS, kp.private, PASSWORD, arrayOf(cert))
|
ks.setKeyEntry(
|
||||||
|
ALIAS, kp.private,
|
||||||
|
PASSWORD, arrayOf(cert))
|
||||||
val bytes = ByteArrayOutputStream()
|
val bytes = ByteArrayOutputStream()
|
||||||
GZIPOutputStream(Base64OutputStream(bytes, BASE64_FLAG)).use {
|
GZIPOutputStream(Base64OutputStream(bytes,
|
||||||
ks.store(it, PASSWORD)
|
BASE64_FLAG
|
||||||
|
)).use {
|
||||||
|
ks.store(it,
|
||||||
|
PASSWORD
|
||||||
|
)
|
||||||
}
|
}
|
||||||
Config.keyStoreRaw = bytes.toString("UTF-8")
|
Config.keyStoreRaw = bytes.toString("UTF-8")
|
||||||
|
|
@ -1,13 +1,13 @@
|
|||||||
@file:Suppress("DEPRECATION")
|
@file:Suppress("DEPRECATION")
|
||||||
|
|
||||||
package com.topjohnwu.magisk.utils
|
package com.topjohnwu.magisk.core.utils
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
import com.topjohnwu.magisk.Config
|
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
import com.topjohnwu.magisk.ResourceMgr
|
import com.topjohnwu.magisk.core.Config
|
||||||
|
import com.topjohnwu.magisk.core.ResourceMgr
|
||||||
import com.topjohnwu.magisk.extensions.langTagToLocale
|
import com.topjohnwu.magisk.extensions.langTagToLocale
|
||||||
import com.topjohnwu.magisk.extensions.toLangTag
|
import com.topjohnwu.magisk.extensions.toLangTag
|
||||||
import io.reactivex.Single
|
import io.reactivex.Single
|
@ -1,15 +1,20 @@
|
|||||||
package com.topjohnwu.magisk.utils
|
package com.topjohnwu.magisk.core.utils
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.Build.VERSION.SDK_INT
|
import android.os.Build.VERSION.SDK_INT
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import com.topjohnwu.magisk.*
|
import com.topjohnwu.magisk.BuildConfig
|
||||||
|
import com.topjohnwu.magisk.R
|
||||||
|
import com.topjohnwu.magisk.core.Config
|
||||||
|
import com.topjohnwu.magisk.core.Const
|
||||||
|
import com.topjohnwu.magisk.core.Info
|
||||||
|
import com.topjohnwu.magisk.core.isRunningAsStub
|
||||||
import com.topjohnwu.magisk.data.network.GithubRawServices
|
import com.topjohnwu.magisk.data.network.GithubRawServices
|
||||||
import com.topjohnwu.magisk.extensions.DynamicClassLoader
|
import com.topjohnwu.magisk.extensions.DynamicClassLoader
|
||||||
import com.topjohnwu.magisk.extensions.get
|
import com.topjohnwu.magisk.extensions.get
|
||||||
import com.topjohnwu.magisk.extensions.subscribeK
|
import com.topjohnwu.magisk.extensions.subscribeK
|
||||||
import com.topjohnwu.magisk.extensions.writeTo
|
import com.topjohnwu.magisk.extensions.writeTo
|
||||||
import com.topjohnwu.magisk.view.Notifications
|
import com.topjohnwu.magisk.core.view.Notifications
|
||||||
import com.topjohnwu.signing.JarMap
|
import com.topjohnwu.signing.JarMap
|
||||||
import com.topjohnwu.signing.SignAPK
|
import com.topjohnwu.signing.SignAPK
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
@ -1,4 +1,4 @@
|
|||||||
package com.topjohnwu.magisk.utils
|
package com.topjohnwu.magisk.core.utils
|
||||||
|
|
||||||
import com.topjohnwu.superuser.internal.UiThreadHandler
|
import com.topjohnwu.superuser.internal.UiThreadHandler
|
||||||
import java.io.FilterInputStream
|
import java.io.FilterInputStream
|
@ -1,10 +1,10 @@
|
|||||||
package com.topjohnwu.magisk.utils
|
package com.topjohnwu.magisk.core.utils
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import com.topjohnwu.magisk.Const
|
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
|
import com.topjohnwu.magisk.core.Const
|
||||||
|
import com.topjohnwu.magisk.core.wrap
|
||||||
import com.topjohnwu.magisk.extensions.rawResource
|
import com.topjohnwu.magisk.extensions.rawResource
|
||||||
import com.topjohnwu.magisk.wrap
|
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
import com.topjohnwu.superuser.io.SuFile
|
import com.topjohnwu.superuser.io.SuFile
|
||||||
|
|
@ -0,0 +1,21 @@
|
|||||||
|
package com.topjohnwu.magisk.core.utils
|
||||||
|
|
||||||
|
interface SafetyNetHelper {
|
||||||
|
|
||||||
|
val version: Int
|
||||||
|
|
||||||
|
fun attest()
|
||||||
|
|
||||||
|
interface Callback {
|
||||||
|
fun onResponse(responseCode: Int)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
const val RESPONSE_ERR = 0x01
|
||||||
|
const val CONNECTION_FAIL = 0x02
|
||||||
|
|
||||||
|
const val BASIC_PASS = 0x10
|
||||||
|
const val CTS_PASS = 0x20
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package com.topjohnwu.magisk.utils
|
package com.topjohnwu.magisk.core.utils
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
@ -7,10 +7,10 @@ import android.net.Uri
|
|||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.work.*
|
import androidx.work.*
|
||||||
import com.topjohnwu.magisk.*
|
import com.topjohnwu.magisk.BuildConfig
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
|
import com.topjohnwu.magisk.core.*
|
||||||
import com.topjohnwu.magisk.extensions.get
|
import com.topjohnwu.magisk.extensions.get
|
||||||
import com.topjohnwu.magisk.model.update.UpdateCheckService
|
|
||||||
import com.topjohnwu.superuser.internal.UiThreadHandler
|
import com.topjohnwu.superuser.internal.UiThreadHandler
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
@ -62,7 +62,10 @@ object Utils {
|
|||||||
if (intent.resolveActivity(context.packageManager) != null) {
|
if (intent.resolveActivity(context.packageManager) != null) {
|
||||||
context.startActivity(intent)
|
context.startActivity(intent)
|
||||||
} else {
|
} else {
|
||||||
toast(R.string.open_link_failed_toast, Toast.LENGTH_SHORT)
|
toast(
|
||||||
|
R.string.open_link_failed_toast,
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
package com.topjohnwu.magisk.utils
|
package com.topjohnwu.magisk.core.utils
|
||||||
|
|
||||||
import com.topjohnwu.superuser.io.SuFile
|
import com.topjohnwu.superuser.io.SuFile
|
||||||
import com.topjohnwu.superuser.io.SuFileOutputStream
|
import com.topjohnwu.superuser.io.SuFileOutputStream
|
@ -1,4 +1,4 @@
|
|||||||
package com.topjohnwu.magisk.view
|
package com.topjohnwu.magisk.core.view
|
||||||
|
|
||||||
import android.app.Notification
|
import android.app.Notification
|
||||||
import android.app.NotificationChannel
|
import android.app.NotificationChannel
|
||||||
@ -9,13 +9,12 @@ import android.os.Build.VERSION.SDK_INT
|
|||||||
import androidx.core.app.TaskStackBuilder
|
import androidx.core.app.TaskStackBuilder
|
||||||
import androidx.core.content.getSystemService
|
import androidx.core.content.getSystemService
|
||||||
import androidx.core.graphics.drawable.toIcon
|
import androidx.core.graphics.drawable.toIcon
|
||||||
import com.topjohnwu.magisk.*
|
import com.topjohnwu.magisk.R
|
||||||
import com.topjohnwu.magisk.Const.ID.PROGRESS_NOTIFICATION_CHANNEL
|
import com.topjohnwu.magisk.core.*
|
||||||
import com.topjohnwu.magisk.Const.ID.UPDATE_NOTIFICATION_CHANNEL
|
import com.topjohnwu.magisk.core.Const.ID.PROGRESS_NOTIFICATION_CHANNEL
|
||||||
|
import com.topjohnwu.magisk.core.Const.ID.UPDATE_NOTIFICATION_CHANNEL
|
||||||
import com.topjohnwu.magisk.extensions.get
|
import com.topjohnwu.magisk.extensions.get
|
||||||
import com.topjohnwu.magisk.extensions.getBitmap
|
import com.topjohnwu.magisk.extensions.getBitmap
|
||||||
import com.topjohnwu.magisk.model.receiver.GeneralReceiver
|
|
||||||
import com.topjohnwu.magisk.ui.SplashActivity
|
|
||||||
|
|
||||||
object Notifications {
|
object Notifications {
|
||||||
|
|
||||||
@ -52,10 +51,13 @@ object Notifications {
|
|||||||
val stackBuilder = TaskStackBuilder.create(context)
|
val stackBuilder = TaskStackBuilder.create(context)
|
||||||
stackBuilder.addParentStack(SplashActivity::class.java.cmp(context.packageName))
|
stackBuilder.addParentStack(SplashActivity::class.java.cmp(context.packageName))
|
||||||
stackBuilder.addNextIntent(intent)
|
stackBuilder.addNextIntent(intent)
|
||||||
val pendingIntent = stackBuilder.getPendingIntent(Const.ID.MAGISK_UPDATE_NOTIFICATION_ID,
|
val pendingIntent = stackBuilder.getPendingIntent(
|
||||||
|
Const.ID.MAGISK_UPDATE_NOTIFICATION_ID,
|
||||||
PendingIntent.FLAG_UPDATE_CURRENT)
|
PendingIntent.FLAG_UPDATE_CURRENT)
|
||||||
|
|
||||||
val builder = updateBuilder(context)
|
val builder = updateBuilder(
|
||||||
|
context
|
||||||
|
)
|
||||||
.setContentTitle(context.getString(R.string.magisk_update_title))
|
.setContentTitle(context.getString(R.string.magisk_update_title))
|
||||||
.setContentText(context.getString(R.string.manager_download_install))
|
.setContentText(context.getString(R.string.manager_download_install))
|
||||||
.setAutoCancel(true)
|
.setAutoCancel(true)
|
||||||
@ -72,7 +74,9 @@ object Notifications {
|
|||||||
val pendingIntent = PendingIntent.getBroadcast(context,
|
val pendingIntent = PendingIntent.getBroadcast(context,
|
||||||
Const.ID.APK_UPDATE_NOTIFICATION_ID, intent, PendingIntent.FLAG_UPDATE_CURRENT)
|
Const.ID.APK_UPDATE_NOTIFICATION_ID, intent, PendingIntent.FLAG_UPDATE_CURRENT)
|
||||||
|
|
||||||
val builder = updateBuilder(context)
|
val builder = updateBuilder(
|
||||||
|
context
|
||||||
|
)
|
||||||
.setContentTitle(context.getString(R.string.manager_update_title))
|
.setContentTitle(context.getString(R.string.manager_update_title))
|
||||||
.setContentText(context.getString(R.string.manager_download_install))
|
.setContentText(context.getString(R.string.manager_download_install))
|
||||||
.setAutoCancel(true)
|
.setAutoCancel(true)
|
||||||
@ -87,7 +91,9 @@ object Notifications {
|
|||||||
val pendingIntent = PendingIntent.getBroadcast(context,
|
val pendingIntent = PendingIntent.getBroadcast(context,
|
||||||
Const.ID.DTBO_NOTIFICATION_ID, intent, PendingIntent.FLAG_UPDATE_CURRENT)
|
Const.ID.DTBO_NOTIFICATION_ID, intent, PendingIntent.FLAG_UPDATE_CURRENT)
|
||||||
|
|
||||||
val builder = updateBuilder(context)
|
val builder = updateBuilder(
|
||||||
|
context
|
||||||
|
)
|
||||||
.setContentTitle(context.getString(R.string.dtbo_patched_title))
|
.setContentTitle(context.getString(R.string.dtbo_patched_title))
|
||||||
.setContentText(context.getString(R.string.dtbo_patched_reboot))
|
.setContentText(context.getString(R.string.dtbo_patched_reboot))
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
package com.topjohnwu.magisk.view
|
package com.topjohnwu.magisk.core.view
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
@ -10,17 +10,18 @@ import androidx.annotation.RequiresApi
|
|||||||
import androidx.core.content.getSystemService
|
import androidx.core.content.getSystemService
|
||||||
import androidx.core.graphics.drawable.toAdaptiveIcon
|
import androidx.core.graphics.drawable.toAdaptiveIcon
|
||||||
import androidx.core.graphics.drawable.toIcon
|
import androidx.core.graphics.drawable.toIcon
|
||||||
import com.topjohnwu.magisk.*
|
import com.topjohnwu.magisk.R
|
||||||
|
import com.topjohnwu.magisk.core.*
|
||||||
|
import com.topjohnwu.magisk.core.utils.Utils
|
||||||
import com.topjohnwu.magisk.extensions.getBitmap
|
import com.topjohnwu.magisk.extensions.getBitmap
|
||||||
import com.topjohnwu.magisk.ui.SplashActivity
|
|
||||||
import com.topjohnwu.magisk.utils.Utils
|
|
||||||
|
|
||||||
object Shortcuts {
|
object Shortcuts {
|
||||||
|
|
||||||
fun setup(context: Context) {
|
fun setup(context: Context) {
|
||||||
if (Build.VERSION.SDK_INT >= 25) {
|
if (Build.VERSION.SDK_INT >= 25) {
|
||||||
val manager = context.getSystemService<ShortcutManager>()
|
val manager = context.getSystemService<ShortcutManager>()
|
||||||
manager?.dynamicShortcuts = getShortCuts(context)
|
manager?.dynamicShortcuts =
|
||||||
|
getShortCuts(context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,19 +78,6 @@ object Shortcuts {
|
|||||||
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
|
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
|
||||||
)
|
)
|
||||||
.setIcon(getIcon(R.drawable.sc_extension))
|
.setIcon(getIcon(R.drawable.sc_extension))
|
||||||
.setRank(3)
|
|
||||||
.build()
|
|
||||||
)
|
|
||||||
shortCuts.add(
|
|
||||||
ShortcutInfo.Builder(context, "downloads")
|
|
||||||
.setShortLabel(context.getString(R.string.downloads))
|
|
||||||
.setIntent(
|
|
||||||
Intent(intent)
|
|
||||||
.putExtra(Const.Key.OPEN_SECTION, "downloads")
|
|
||||||
.setAction(Intent.ACTION_VIEW)
|
|
||||||
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
|
|
||||||
)
|
|
||||||
.setIcon(getIcon(R.drawable.sc_cloud_download))
|
|
||||||
.setRank(2)
|
.setRank(2)
|
||||||
.build()
|
.build()
|
||||||
)
|
)
|
67
app/src/main/java/com/topjohnwu/magisk/data/database/Repo.kt
Normal file
67
app/src/main/java/com/topjohnwu/magisk/data/database/Repo.kt
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
@file:JvmMultifileClass
|
||||||
|
|
||||||
|
package com.topjohnwu.magisk.data.database
|
||||||
|
|
||||||
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Query
|
||||||
|
import com.topjohnwu.magisk.core.model.module.Repo
|
||||||
|
|
||||||
|
interface RepoBase {
|
||||||
|
|
||||||
|
fun getRepos(offset: Int, limit: Int = LIMIT): List<Repo>
|
||||||
|
fun searchRepos(query: String, offset: Int, limit: Int = LIMIT): List<Repo>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM repos WHERE id = :id AND versionCode > :versionCode LIMIT 1")
|
||||||
|
fun getUpdatableRepoById(id: String, versionCode: Int): Repo?
|
||||||
|
|
||||||
|
@Query("SELECT * FROM repos WHERE id = :id LIMIT 1")
|
||||||
|
fun getRepoById(id: String): Repo?
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val LIMIT = 10
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface RepoByUpdatedDao : RepoBase {
|
||||||
|
|
||||||
|
@Query("SELECT * FROM repos ORDER BY last_update DESC LIMIT :limit OFFSET :offset")
|
||||||
|
override fun getRepos(offset: Int, limit: Int): List<Repo>
|
||||||
|
|
||||||
|
@Query(
|
||||||
|
"""SELECT *
|
||||||
|
FROM repos
|
||||||
|
WHERE
|
||||||
|
(author LIKE '%' || :query || '%') ||
|
||||||
|
(name LIKE '%' || :query || '%') ||
|
||||||
|
(description LIKE '%' || :query || '%')
|
||||||
|
ORDER BY last_update DESC
|
||||||
|
LIMIT :limit
|
||||||
|
OFFSET :offset"""
|
||||||
|
)
|
||||||
|
override fun searchRepos(query: String, offset: Int, limit: Int): List<Repo>
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface RepoByNameDao : RepoBase {
|
||||||
|
|
||||||
|
@Query("SELECT * FROM repos ORDER BY name COLLATE NOCASE LIMIT :limit OFFSET :offset")
|
||||||
|
override fun getRepos(offset: Int, limit: Int): List<Repo>
|
||||||
|
|
||||||
|
@Query(
|
||||||
|
"""SELECT *
|
||||||
|
FROM repos
|
||||||
|
WHERE
|
||||||
|
(author LIKE '%' || :query || '%') ||
|
||||||
|
(name LIKE '%' || :query || '%') ||
|
||||||
|
(description LIKE '%' || :query || '%')
|
||||||
|
ORDER BY name COLLATE NOCASE
|
||||||
|
LIMIT :limit
|
||||||
|
OFFSET :offset"""
|
||||||
|
)
|
||||||
|
override fun searchRepos(query: String, offset: Int, limit: Int): List<Repo>
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -1,13 +1,15 @@
|
|||||||
package com.topjohnwu.magisk.data.database
|
package com.topjohnwu.magisk.data.database
|
||||||
|
|
||||||
import androidx.room.*
|
import androidx.room.*
|
||||||
import com.topjohnwu.magisk.Config
|
import com.topjohnwu.magisk.core.Config
|
||||||
import com.topjohnwu.magisk.model.entity.module.Repo
|
import com.topjohnwu.magisk.core.model.module.Repo
|
||||||
|
|
||||||
@Database(version = 6, entities = [Repo::class, RepoEtag::class])
|
@Database(version = 6, entities = [Repo::class, RepoEtag::class])
|
||||||
abstract class RepoDatabase : RoomDatabase() {
|
abstract class RepoDatabase : RoomDatabase() {
|
||||||
|
|
||||||
abstract fun repoDao() : RepoDao
|
abstract fun repoDao() : RepoDao
|
||||||
|
abstract fun repoByUpdatedDao(): RepoByUpdatedDao
|
||||||
|
abstract fun repoByNameDao(): RepoByNameDao
|
||||||
}
|
}
|
||||||
|
|
||||||
@Dao
|
@Dao
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
package com.topjohnwu.magisk.data.network
|
package com.topjohnwu.magisk.data.network
|
||||||
|
|
||||||
import com.topjohnwu.magisk.Const
|
import com.topjohnwu.magisk.core.Const
|
||||||
import com.topjohnwu.magisk.model.entity.UpdateInfo
|
import com.topjohnwu.magisk.core.tasks.GithubRepoInfo
|
||||||
import com.topjohnwu.magisk.tasks.GithubRepoInfo
|
import com.topjohnwu.magisk.core.model.UpdateInfo
|
||||||
import io.reactivex.Flowable
|
import io.reactivex.Flowable
|
||||||
import io.reactivex.Single
|
import io.reactivex.Single
|
||||||
import okhttp3.ResponseBody
|
import okhttp3.ResponseBody
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package com.topjohnwu.magisk.data.repository
|
package com.topjohnwu.magisk.data.repository
|
||||||
|
|
||||||
import com.topjohnwu.magisk.data.database.SettingsDao
|
import com.topjohnwu.magisk.core.magiskdb.SettingsDao
|
||||||
import com.topjohnwu.magisk.data.database.StringDao
|
import com.topjohnwu.magisk.core.magiskdb.StringDao
|
||||||
import io.reactivex.schedulers.Schedulers
|
import io.reactivex.schedulers.Schedulers
|
||||||
import kotlin.properties.ReadWriteProperty
|
import kotlin.properties.ReadWriteProperty
|
||||||
import kotlin.reflect.KProperty
|
import kotlin.reflect.KProperty
|
||||||
|
@ -1,20 +1,18 @@
|
|||||||
package com.topjohnwu.magisk.data.repository
|
package com.topjohnwu.magisk.data.repository
|
||||||
|
|
||||||
import com.topjohnwu.magisk.Const
|
import com.topjohnwu.magisk.core.Const
|
||||||
import com.topjohnwu.magisk.data.database.SuLogDao
|
import com.topjohnwu.magisk.data.database.SuLogDao
|
||||||
import com.topjohnwu.magisk.model.entity.MagiskLog
|
import com.topjohnwu.magisk.model.entity.MagiskLog
|
||||||
import com.topjohnwu.magisk.model.entity.WrappedMagiskLog
|
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
import io.reactivex.Completable
|
import io.reactivex.Completable
|
||||||
import io.reactivex.Single
|
import io.reactivex.Single
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
|
|
||||||
|
|
||||||
class LogRepository(
|
class LogRepository(
|
||||||
private val logDao: SuLogDao
|
private val logDao: SuLogDao
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun fetchLogs() = logDao.fetchAll().map { it.wrap() }
|
fun fetchLogs() = logDao.fetchAll()
|
||||||
|
|
||||||
fun fetchMagiskLogs() = Single.fromCallable {
|
fun fetchMagiskLogs() = Single.fromCallable {
|
||||||
Shell.su("tail -n 5000 ${Const.MAGISK_LOG}").exec().out
|
Shell.su("tail -n 5000 ${Const.MAGISK_LOG}").exec().out
|
||||||
@ -28,11 +26,4 @@ class LogRepository(
|
|||||||
|
|
||||||
fun insert(log: MagiskLog) = logDao.insert(log)
|
fun insert(log: MagiskLog) = logDao.insert(log)
|
||||||
|
|
||||||
private fun List<MagiskLog>.wrap(): List<WrappedMagiskLog> {
|
|
||||||
val day = TimeUnit.DAYS.toMillis(1)
|
|
||||||
return groupBy { it.time / day }
|
|
||||||
.map { WrappedMagiskLog(it.key * day, it.value) }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
package com.topjohnwu.magisk.data.repository
|
package com.topjohnwu.magisk.data.repository
|
||||||
|
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import com.topjohnwu.magisk.Config
|
import com.topjohnwu.magisk.core.Info
|
||||||
import com.topjohnwu.magisk.Info
|
import com.topjohnwu.magisk.core.Config
|
||||||
import com.topjohnwu.magisk.data.network.GithubRawServices
|
import com.topjohnwu.magisk.data.network.GithubRawServices
|
||||||
import com.topjohnwu.magisk.extensions.getLabel
|
import com.topjohnwu.magisk.extensions.getLabel
|
||||||
import com.topjohnwu.magisk.extensions.packageName
|
import com.topjohnwu.magisk.extensions.packageName
|
||||||
@ -24,7 +24,8 @@ class MagiskRepository(
|
|||||||
Config.Value.BETA_CHANNEL -> apiRaw.fetchBetaUpdate()
|
Config.Value.BETA_CHANNEL -> apiRaw.fetchBetaUpdate()
|
||||||
Config.Value.CANARY_CHANNEL -> apiRaw.fetchCanaryUpdate()
|
Config.Value.CANARY_CHANNEL -> apiRaw.fetchCanaryUpdate()
|
||||||
Config.Value.CANARY_DEBUG_CHANNEL -> apiRaw.fetchCanaryDebugUpdate()
|
Config.Value.CANARY_DEBUG_CHANNEL -> apiRaw.fetchCanaryDebugUpdate()
|
||||||
Config.Value.CUSTOM_CHANNEL -> apiRaw.fetchCustomUpdate(Config.customChannelUrl)
|
Config.Value.CUSTOM_CHANNEL -> apiRaw.fetchCustomUpdate(
|
||||||
|
Config.customChannelUrl)
|
||||||
else -> throw IllegalArgumentException()
|
else -> throw IllegalArgumentException()
|
||||||
}.flatMap {
|
}.flatMap {
|
||||||
// If remote version is lower than current installed, try switching to beta
|
// If remote version is lower than current installed, try switching to beta
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package com.topjohnwu.magisk.data.repository
|
package com.topjohnwu.magisk.data.repository
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.core.model.module.Repo
|
||||||
import com.topjohnwu.magisk.data.network.GithubRawServices
|
import com.topjohnwu.magisk.data.network.GithubRawServices
|
||||||
import com.topjohnwu.magisk.model.entity.module.Repo
|
|
||||||
|
|
||||||
class StringRepository(
|
class StringRepository(
|
||||||
private val api: GithubRawServices
|
private val api: GithubRawServices
|
||||||
|
@ -6,6 +6,7 @@ import android.app.Application
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import androidx.localbroadcastmanager.content.LocalBroadcastManager
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import com.topjohnwu.magisk.utils.RxBus
|
import com.topjohnwu.magisk.utils.RxBus
|
||||||
import org.koin.core.qualifier.named
|
import org.koin.core.qualifier.named
|
||||||
@ -23,6 +24,7 @@ val applicationModule = module {
|
|||||||
single { PreferenceManager.getDefaultSharedPreferences(get<Context>(Protected)) }
|
single { PreferenceManager.getDefaultSharedPreferences(get<Context>(Protected)) }
|
||||||
single { ActivityTracker() }
|
single { ActivityTracker() }
|
||||||
factory { get<ActivityTracker>().foreground ?: NullActivity }
|
factory { get<ActivityTracker>().foreground ?: NullActivity }
|
||||||
|
single { LocalBroadcastManager.getInstance(get()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createDEContext(context: Context): Context {
|
private fun createDEContext(context: Context): Context {
|
||||||
|
@ -2,8 +2,12 @@ package com.topjohnwu.magisk.di
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.room.Room
|
import androidx.room.Room
|
||||||
import com.topjohnwu.magisk.data.database.*
|
import com.topjohnwu.magisk.core.magiskdb.PolicyDao
|
||||||
import com.topjohnwu.magisk.tasks.RepoUpdater
|
import com.topjohnwu.magisk.core.magiskdb.SettingsDao
|
||||||
|
import com.topjohnwu.magisk.core.magiskdb.StringDao
|
||||||
|
import com.topjohnwu.magisk.core.tasks.RepoUpdater
|
||||||
|
import com.topjohnwu.magisk.data.database.RepoDatabase
|
||||||
|
import com.topjohnwu.magisk.data.database.SuLogDatabase
|
||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
|
|
||||||
|
|
||||||
@ -11,7 +15,10 @@ val databaseModule = module {
|
|||||||
single { PolicyDao(get()) }
|
single { PolicyDao(get()) }
|
||||||
single { SettingsDao() }
|
single { SettingsDao() }
|
||||||
single { StringDao() }
|
single { StringDao() }
|
||||||
single { createRepoDatabase(get()).repoDao() }
|
single { createRepoDatabase(get()) }
|
||||||
|
single { get<RepoDatabase>().repoDao() }
|
||||||
|
single { get<RepoDatabase>().repoByNameDao() }
|
||||||
|
single { get<RepoDatabase>().repoByUpdatedDao() }
|
||||||
single { createSuLogDatabase(get(Protected)).suLogDao() }
|
single { createSuLogDatabase(get(Protected)).suLogDao() }
|
||||||
single { RepoUpdater(get(), get()) }
|
single { RepoUpdater(get(), get()) }
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ 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.core.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.Networking
|
||||||
|
@ -1,25 +1,36 @@
|
|||||||
package com.topjohnwu.magisk.di
|
package com.topjohnwu.magisk.di
|
||||||
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import com.topjohnwu.magisk.legacy.flash.FlashViewModel
|
||||||
|
import com.topjohnwu.magisk.legacy.surequest.SuRequestViewModel
|
||||||
import com.topjohnwu.magisk.ui.MainViewModel
|
import com.topjohnwu.magisk.ui.MainViewModel
|
||||||
import com.topjohnwu.magisk.ui.flash.FlashViewModel
|
|
||||||
import com.topjohnwu.magisk.ui.hide.HideViewModel
|
import com.topjohnwu.magisk.ui.hide.HideViewModel
|
||||||
import com.topjohnwu.magisk.ui.home.HomeViewModel
|
import com.topjohnwu.magisk.ui.home.HomeViewModel
|
||||||
|
import com.topjohnwu.magisk.ui.install.InstallViewModel
|
||||||
import com.topjohnwu.magisk.ui.log.LogViewModel
|
import com.topjohnwu.magisk.ui.log.LogViewModel
|
||||||
import com.topjohnwu.magisk.ui.module.ModuleViewModel
|
import com.topjohnwu.magisk.ui.module.ModuleViewModel
|
||||||
|
import com.topjohnwu.magisk.ui.request.RequestViewModel
|
||||||
|
import com.topjohnwu.magisk.ui.safetynet.SafetynetViewModel
|
||||||
|
import com.topjohnwu.magisk.ui.settings.SettingsViewModel
|
||||||
import com.topjohnwu.magisk.ui.superuser.SuperuserViewModel
|
import com.topjohnwu.magisk.ui.superuser.SuperuserViewModel
|
||||||
import com.topjohnwu.magisk.ui.surequest.SuRequestViewModel
|
import com.topjohnwu.magisk.ui.theme.ThemeViewModel
|
||||||
import org.koin.androidx.viewmodel.dsl.viewModel
|
import org.koin.androidx.viewmodel.dsl.viewModel
|
||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
|
|
||||||
|
|
||||||
val viewModelModules = module {
|
val viewModelModules = module {
|
||||||
viewModel { MainViewModel() }
|
viewModel { HideViewModel(get()) }
|
||||||
viewModel { HomeViewModel(get()) }
|
viewModel { HomeViewModel(get()) }
|
||||||
viewModel { SuperuserViewModel(get(), get(), get(), get()) }
|
viewModel { LogViewModel(get()) }
|
||||||
viewModel { HideViewModel(get(), get()) }
|
|
||||||
viewModel { ModuleViewModel(get(), get(), get()) }
|
viewModel { ModuleViewModel(get(), get(), get()) }
|
||||||
viewModel { LogViewModel(get(), get()) }
|
viewModel { RequestViewModel() }
|
||||||
|
viewModel { SafetynetViewModel(get()) }
|
||||||
|
viewModel { SettingsViewModel(get()) }
|
||||||
|
viewModel { SuperuserViewModel(get(), get(), get()) }
|
||||||
|
viewModel { ThemeViewModel() }
|
||||||
|
viewModel { InstallViewModel() }
|
||||||
|
viewModel { MainViewModel() }
|
||||||
|
|
||||||
|
// Legacy
|
||||||
viewModel { (action: String, file: Uri, additional: Uri) ->
|
viewModel { (action: String, file: Uri, additional: Uri) ->
|
||||||
FlashViewModel(action, file, additional, get())
|
FlashViewModel(action, file, additional, get())
|
||||||
}
|
}
|
||||||
|
@ -52,9 +52,9 @@ fun <T> Observable<T>.subscribeK(
|
|||||||
|
|
||||||
fun <T> Single<T>.subscribeK(
|
fun <T> Single<T>.subscribeK(
|
||||||
onError: OnErrorListener = { it.printStackTrace() },
|
onError: OnErrorListener = { it.printStackTrace() },
|
||||||
onSuccess: OnSuccessListener<T> = {}
|
onNext: OnSuccessListener<T> = {}
|
||||||
) = applySchedulers()
|
) = applySchedulers()
|
||||||
.subscribe(onSuccess, onError)
|
.subscribe(onNext, onError)
|
||||||
|
|
||||||
fun <T> Maybe<T>.subscribeK(
|
fun <T> Maybe<T>.subscribeK(
|
||||||
onError: OnErrorListener = { it.printStackTrace() },
|
onError: OnErrorListener = { it.printStackTrace() },
|
||||||
@ -198,5 +198,8 @@ fun <T> ObservableField<T>.toObservable(): Observable<T> {
|
|||||||
|
|
||||||
fun <T : Any> T.toSingle() = Single.just(this)
|
fun <T : Any> T.toSingle() = Single.just(this)
|
||||||
|
|
||||||
fun <T1, T2, R> zip(t1: Single<T1>, t2: Single<T2>, zipper: (T1, T2) -> R) =
|
inline fun <T1, T2, R> zip(
|
||||||
Single.zip(t1, t2, BiFunction<T1, T2, R> { rt1, rt2 -> zipper(rt1, rt2) })
|
t1: Single<T1>,
|
||||||
|
t2: Single<T2>,
|
||||||
|
crossinline zipper: (T1, T2) -> R
|
||||||
|
) = Single.zip(t1, t2, BiFunction<T1, T2, R> { rt1, rt2 -> zipper(rt1, rt2) })
|
@ -28,14 +28,17 @@ import androidx.appcompat.content.res.AppCompatResources
|
|||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.net.toFile
|
import androidx.core.net.toFile
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import com.topjohnwu.magisk.Const
|
|
||||||
import com.topjohnwu.magisk.FileProvider
|
import com.topjohnwu.magisk.FileProvider
|
||||||
|
import com.topjohnwu.magisk.core.Const
|
||||||
|
import com.topjohnwu.magisk.core.utils.currentLocale
|
||||||
import com.topjohnwu.magisk.utils.DynamicClassLoader
|
import com.topjohnwu.magisk.utils.DynamicClassLoader
|
||||||
import com.topjohnwu.magisk.utils.Utils
|
import com.topjohnwu.magisk.core.utils.Utils
|
||||||
import com.topjohnwu.magisk.utils.currentLocale
|
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
|
import com.topjohnwu.superuser.ShellUtils
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileNotFoundException
|
import java.io.FileNotFoundException
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.*
|
||||||
import java.lang.reflect.Array as JArray
|
import java.lang.reflect.Array as JArray
|
||||||
|
|
||||||
val packageName: String get() = get<Context>().packageName
|
val packageName: String get() = get<Context>().packageName
|
||||||
@ -283,7 +286,7 @@ fun Context.drawableCompat(@DrawableRes id: Int) = ContextCompat.getDrawable(thi
|
|||||||
* with respect to RTL layout direction
|
* with respect to RTL layout direction
|
||||||
*/
|
*/
|
||||||
fun Context.startEndToLeftRight(start: Int, end: Int): Pair<Int, Int> {
|
fun Context.startEndToLeftRight(start: Int, end: Int): Pair<Int, Int> {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 &&
|
if (SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 &&
|
||||||
resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_RTL
|
resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_RTL
|
||||||
) {
|
) {
|
||||||
return end to start
|
return end to start
|
||||||
@ -294,10 +297,10 @@ fun Context.startEndToLeftRight(start: Int, end: Int): Pair<Int, Int> {
|
|||||||
fun Context.openUrl(url: String) = Utils.openLink(this, url.toUri())
|
fun Context.openUrl(url: String) = Utils.openLink(this, url.toUri())
|
||||||
|
|
||||||
@Suppress("FunctionName")
|
@Suppress("FunctionName")
|
||||||
inline fun <reified T> T.DynamicClassLoader(apk: File)
|
inline fun <reified T> T.DynamicClassLoader(apk: File) =
|
||||||
= DynamicClassLoader(apk, T::class.java.classLoader)
|
DynamicClassLoader(apk, T::class.java.classLoader)
|
||||||
|
|
||||||
fun Context.unwrap() : Context {
|
fun Context.unwrap(): Context {
|
||||||
var context = this
|
var context = this
|
||||||
while (true) {
|
while (true) {
|
||||||
if (context is ContextWrapper)
|
if (context is ContextWrapper)
|
||||||
@ -309,3 +312,38 @@ fun Context.unwrap() : Context {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun Uri.writeTo(file: File) = toFile().copyTo(file)
|
fun Uri.writeTo(file: File) = toFile().copyTo(file)
|
||||||
|
|
||||||
|
fun Context.hasPermissions(vararg permissions: String) = permissions.all {
|
||||||
|
ContextCompat.checkSelfPermission(this, it) == PERMISSION_GRANTED
|
||||||
|
}
|
||||||
|
|
||||||
|
private val securityLevelFormatter get() = SimpleDateFormat("yyyy-MM-dd",
|
||||||
|
currentLocale
|
||||||
|
)
|
||||||
|
|
||||||
|
/** Friendly reminder to seek newer roms or install oem updates. */
|
||||||
|
val isDeviceSecure: Boolean
|
||||||
|
get() {
|
||||||
|
val latestPermittedTime = Calendar.getInstance().apply {
|
||||||
|
time = securityLevelDate
|
||||||
|
add(Calendar.MONTH, 2)
|
||||||
|
}.time.time
|
||||||
|
return now in 0..latestPermittedTime
|
||||||
|
}
|
||||||
|
val securityLevelDate get() = securityLevelFormatter.parseOrNull(securityLevel) ?: Date(0)
|
||||||
|
val securityLevel
|
||||||
|
get() = if (SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
|
Build.VERSION.SECURITY_PATCH
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
} ?: "1970-01-01" //never
|
||||||
|
|
||||||
|
val isSAR
|
||||||
|
get() = ShellUtils
|
||||||
|
.fastCmd("grep_prop ro.build.system_root_image")
|
||||||
|
.let { it.isNotEmpty() && it.toBoolean() }
|
||||||
|
|
||||||
|
val isAB
|
||||||
|
get() = ShellUtils
|
||||||
|
.fastCmd("grep_prop ro.build.ab_update")
|
||||||
|
.let { it.isNotEmpty() && it.toBoolean() }
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
package com.topjohnwu.magisk.extensions
|
package com.topjohnwu.magisk.extensions
|
||||||
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
import timber.log.Timber
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
import java.lang.reflect.Field
|
import java.lang.reflect.Field
|
||||||
import java.lang.reflect.Method
|
import java.lang.reflect.Method
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.zip.ZipEntry
|
import java.util.zip.ZipEntry
|
||||||
import java.util.zip.ZipInputStream
|
import java.util.zip.ZipInputStream
|
||||||
@ -100,6 +102,9 @@ fun Locale.toLangTag(): String {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun SimpleDateFormat.parseOrNull(date: String) =
|
||||||
|
runCatching { parse(date) }.onFailure { Timber.e(it) }.getOrNull()
|
||||||
|
|
||||||
// Reflection hacks
|
// Reflection hacks
|
||||||
|
|
||||||
private val loadClass = ClassLoader::class.java.getMethod("loadClass", String::class.java)
|
private val loadClass = ClassLoader::class.java.getMethod("loadClass", String::class.java)
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package com.topjohnwu.magisk.extensions
|
package com.topjohnwu.magisk.extensions
|
||||||
|
|
||||||
import com.topjohnwu.magisk.Info
|
import com.topjohnwu.magisk.core.Info
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
import com.topjohnwu.superuser.io.SuFileInputStream
|
import com.topjohnwu.superuser.io.SuFileInputStream
|
||||||
import com.topjohnwu.superuser.io.SuFileOutputStream
|
import com.topjohnwu.superuser.io.SuFileOutputStream
|
||||||
@ -12,3 +12,5 @@ fun reboot(reason: String = if (Info.recovery) "recovery" else "") {
|
|||||||
|
|
||||||
fun File.suOutputStream() = SuFileOutputStream(this)
|
fun File.suOutputStream() = SuFileOutputStream(this)
|
||||||
fun File.suInputStream() = SuFileInputStream(this)
|
fun File.suInputStream() = SuFileInputStream(this)
|
||||||
|
|
||||||
|
val hasRoot get() = Shell.rootAccess()
|
||||||
|
@ -2,7 +2,15 @@ package com.topjohnwu.magisk.extensions
|
|||||||
|
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
|
|
||||||
val specialChars = arrayOf('!', '@', '#', '$', '%', '&', '?')
|
val specialChars = arrayOf('!', '@', '#', '$', '%', '&', '?')
|
||||||
|
|
||||||
|
fun String.replaceRandomWithSpecial(passes: Int): String {
|
||||||
|
var string = this
|
||||||
|
repeat(passes) {
|
||||||
|
string = string.replaceRandomWithSpecial()
|
||||||
|
}
|
||||||
|
return string
|
||||||
|
}
|
||||||
|
|
||||||
fun String.replaceRandomWithSpecial(): String {
|
fun String.replaceRandomWithSpecial(): String {
|
||||||
var random: Char
|
var random: Char
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package com.topjohnwu.magisk.extensions
|
package com.topjohnwu.magisk.extensions
|
||||||
|
|
||||||
import com.topjohnwu.magisk.utils.currentLocale
|
import com.topjohnwu.magisk.core.utils.currentLocale
|
||||||
import java.text.DateFormat
|
import java.text.DateFormat
|
||||||
import java.text.ParseException
|
import java.text.ParseException
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
@ -14,7 +14,22 @@ fun String.toTime(format: DateFormat) = try {
|
|||||||
-1L
|
-1L
|
||||||
}
|
}
|
||||||
|
|
||||||
val timeFormatFull by lazy { SimpleDateFormat("yyyy/MM/dd_HH:mm:ss", currentLocale) }
|
val timeFormatFull by lazy { SimpleDateFormat("yyyy/MM/dd_HH:mm:ss",
|
||||||
val timeFormatStandard by lazy { SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", currentLocale) }
|
currentLocale
|
||||||
val timeFormatMedium by lazy { DateFormat.getDateInstance(DateFormat.MEDIUM, currentLocale) }
|
) }
|
||||||
val timeFormatTime by lazy { SimpleDateFormat("h:mm a", currentLocale) }
|
val timeFormatStandard by lazy { SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'",
|
||||||
|
currentLocale
|
||||||
|
) }
|
||||||
|
val timeFormatMedium by lazy { DateFormat.getDateInstance(DateFormat.MEDIUM,
|
||||||
|
currentLocale
|
||||||
|
) }
|
||||||
|
val timeFormatTime by lazy { SimpleDateFormat("h:mm a",
|
||||||
|
currentLocale
|
||||||
|
) }
|
||||||
|
val timeDateFormat by lazy {
|
||||||
|
DateFormat.getDateTimeInstance(
|
||||||
|
DateFormat.DEFAULT,
|
||||||
|
DateFormat.DEFAULT,
|
||||||
|
currentLocale
|
||||||
|
)
|
||||||
|
}
|
||||||
|
@ -1,7 +1,11 @@
|
|||||||
package com.topjohnwu.magisk.extensions
|
package com.topjohnwu.magisk.extensions
|
||||||
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
import android.view.ViewTreeObserver
|
import android.view.ViewTreeObserver
|
||||||
|
import androidx.interpolator.view.animation.FastOutSlowInInterpolator
|
||||||
|
import androidx.transition.AutoTransition
|
||||||
|
import androidx.transition.TransitionManager
|
||||||
|
|
||||||
fun View.setOnViewReadyListener(callback: () -> Unit) = addOnGlobalLayoutListener(true, callback)
|
fun View.setOnViewReadyListener(callback: () -> Unit) = addOnGlobalLayoutListener(true, callback)
|
||||||
|
|
||||||
@ -12,3 +16,8 @@ fun View.addOnGlobalLayoutListener(oneShot: Boolean = false, callback: () -> Uni
|
|||||||
callback()
|
callback()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
fun ViewGroup.startAnimations() {
|
||||||
|
val transition = AutoTransition().setInterpolator(FastOutSlowInInterpolator()).setDuration(400)
|
||||||
|
TransitionManager.beginDelayedTransition(this, transition)
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package com.topjohnwu.magisk.ui.flash
|
package com.topjohnwu.magisk.legacy.flash
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
@ -6,22 +6,22 @@ import android.content.pm.ActivityInfo
|
|||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
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.core.Const
|
||||||
|
import com.topjohnwu.magisk.core.intent
|
||||||
import com.topjohnwu.magisk.databinding.ActivityFlashBinding
|
import com.topjohnwu.magisk.databinding.ActivityFlashBinding
|
||||||
import com.topjohnwu.magisk.extensions.snackbar
|
import com.topjohnwu.magisk.extensions.snackbar
|
||||||
import com.topjohnwu.magisk.intent
|
|
||||||
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.SnackbarEvent
|
import com.topjohnwu.magisk.model.events.SnackbarEvent
|
||||||
import com.topjohnwu.magisk.model.events.ViewEvent
|
import com.topjohnwu.magisk.model.events.ViewEvent
|
||||||
import com.topjohnwu.magisk.view.Notifications
|
import com.topjohnwu.magisk.ui.base.BaseUIActivity
|
||||||
|
import com.topjohnwu.magisk.core.view.Notifications
|
||||||
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 : BaseActivity<FlashViewModel, ActivityFlashBinding>() {
|
open class FlashActivity : BaseUIActivity<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 themeRes: Int = R.style.MagiskTheme_Flashing
|
@ -0,0 +1,110 @@
|
|||||||
|
package com.topjohnwu.magisk.legacy.flash
|
||||||
|
|
||||||
|
import android.Manifest.permission.READ_EXTERNAL_STORAGE
|
||||||
|
import android.Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||||
|
import android.content.res.Resources
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Handler
|
||||||
|
import androidx.core.os.postDelayed
|
||||||
|
import androidx.databinding.ObservableArrayList
|
||||||
|
import com.topjohnwu.magisk.BR
|
||||||
|
import com.topjohnwu.magisk.R
|
||||||
|
import com.topjohnwu.magisk.core.Config
|
||||||
|
import com.topjohnwu.magisk.core.Const
|
||||||
|
import com.topjohnwu.magisk.databinding.ComparableRvItem
|
||||||
|
import com.topjohnwu.magisk.extensions.*
|
||||||
|
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.Flashing
|
||||||
|
import com.topjohnwu.magisk.model.flash.Patching
|
||||||
|
import com.topjohnwu.magisk.ui.base.BaseViewModel
|
||||||
|
import com.topjohnwu.magisk.utils.DiffObservableList
|
||||||
|
import com.topjohnwu.magisk.utils.KObservableField
|
||||||
|
import com.topjohnwu.superuser.Shell
|
||||||
|
import me.tatarka.bindingcollectionadapter2.ItemBinding
|
||||||
|
import java.io.File
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
class FlashViewModel(
|
||||||
|
action: String,
|
||||||
|
installer: Uri,
|
||||||
|
uri: Uri,
|
||||||
|
private val resources: Resources
|
||||||
|
) : BaseViewModel(), FlashResultListener {
|
||||||
|
|
||||||
|
val canShowReboot = Shell.rootAccess()
|
||||||
|
val showRestartTitle = KObservableField(false)
|
||||||
|
|
||||||
|
val behaviorText = KObservableField(resources.getString(R.string.flashing))
|
||||||
|
|
||||||
|
val items = DiffObservableList(ComparableRvItem.callback)
|
||||||
|
val itemBinding = ItemBinding.of<ComparableRvItem<*>> { itemBinding, _, item ->
|
||||||
|
item.bind(itemBinding)
|
||||||
|
itemBinding.bindExtra(BR.viewModel, this@FlashViewModel)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val outItems = ObservableArrayList<String>()
|
||||||
|
private val logItems = Collections.synchronizedList(mutableListOf<String>())
|
||||||
|
|
||||||
|
init {
|
||||||
|
outItems.sendUpdatesTo(items) { it.map { ConsoleRvItem(it) } }
|
||||||
|
outItems.copyNewInputInto(logItems)
|
||||||
|
|
||||||
|
state = State.LOADING
|
||||||
|
|
||||||
|
when (action) {
|
||||||
|
Const.Value.FLASH_ZIP -> Flashing
|
||||||
|
.Install(installer, outItems, logItems, this)
|
||||||
|
.exec()
|
||||||
|
Const.Value.UNINSTALL -> Flashing
|
||||||
|
.Uninstall(installer, outItems, logItems, this)
|
||||||
|
.exec()
|
||||||
|
Const.Value.FLASH_MAGISK -> Patching
|
||||||
|
.Direct(installer, outItems, logItems, this)
|
||||||
|
.exec()
|
||||||
|
Const.Value.FLASH_INACTIVE_SLOT -> Patching
|
||||||
|
.SecondSlot(installer, outItems, logItems, this)
|
||||||
|
.exec()
|
||||||
|
Const.Value.PATCH_FILE -> Patching
|
||||||
|
.File(installer, uri, outItems, logItems, this)
|
||||||
|
.exec()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResult(isSuccess: Boolean) {
|
||||||
|
state = if (isSuccess) State.LOADED else State.LOADING_FAILED
|
||||||
|
behaviorText.value = when {
|
||||||
|
isSuccess -> resources.getString(R.string.done)
|
||||||
|
else -> resources.getString(R.string.failure)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isSuccess) {
|
||||||
|
Handler().postDelayed(500) {
|
||||||
|
showRestartTitle.value = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun savePressed() = withPermissions(READ_EXTERNAL_STORAGE, WRITE_EXTERNAL_STORAGE)
|
||||||
|
.map { now }
|
||||||
|
.map { it.toTime(timeFormatStandard) }
|
||||||
|
.map { Const.MAGISK_INSTALL_LOG_FILENAME.format(it) }
|
||||||
|
.map { File(Config.downloadDirectory, it) }
|
||||||
|
.map { file ->
|
||||||
|
file.bufferedWriter().use { writer ->
|
||||||
|
logItems.forEach {
|
||||||
|
writer.write(it)
|
||||||
|
writer.newLine()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file.path
|
||||||
|
}
|
||||||
|
.subscribeK { SnackbarEvent(it).publish() }
|
||||||
|
.add()
|
||||||
|
|
||||||
|
fun restartPressed() = reboot()
|
||||||
|
|
||||||
|
fun backPressed() = back()
|
||||||
|
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package com.topjohnwu.magisk.ui.surequest
|
package com.topjohnwu.magisk.legacy.surequest
|
||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.ActivityInfo
|
import android.content.pm.ActivityInfo
|
||||||
@ -6,16 +6,16 @@ import android.os.Build
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.Window
|
import android.view.Window
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
import com.topjohnwu.magisk.base.BaseActivity
|
import com.topjohnwu.magisk.core.su.SuCallbackHandler
|
||||||
|
import com.topjohnwu.magisk.core.su.SuCallbackHandler.REQUEST
|
||||||
import com.topjohnwu.magisk.databinding.ActivityRequestBinding
|
import com.topjohnwu.magisk.databinding.ActivityRequestBinding
|
||||||
import com.topjohnwu.magisk.model.events.DieEvent
|
import com.topjohnwu.magisk.model.events.DieEvent
|
||||||
import com.topjohnwu.magisk.model.events.ViewActionEvent
|
import com.topjohnwu.magisk.model.events.ViewActionEvent
|
||||||
import com.topjohnwu.magisk.model.events.ViewEvent
|
import com.topjohnwu.magisk.model.events.ViewEvent
|
||||||
import com.topjohnwu.magisk.utils.SuHandler
|
import com.topjohnwu.magisk.ui.base.BaseUIActivity
|
||||||
import com.topjohnwu.magisk.utils.SuHandler.REQUEST
|
|
||||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||||
|
|
||||||
open class SuRequestActivity : BaseActivity<SuRequestViewModel, ActivityRequestBinding>() {
|
open class SuRequestActivity : BaseUIActivity<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 themeRes: Int = R.style.MagiskTheme_SU
|
||||||
@ -36,7 +36,11 @@ open class SuRequestActivity : BaseActivity<SuRequestViewModel, ActivityRequestB
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun runHandler(action: String?) {
|
fun runHandler(action: String?) {
|
||||||
SuHandler(this, action, intent.extras)
|
SuCallbackHandler(
|
||||||
|
this,
|
||||||
|
action,
|
||||||
|
intent.extras
|
||||||
|
)
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,123 @@
|
|||||||
|
package com.topjohnwu.magisk.legacy.surequest
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.content.res.Resources
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
|
import android.os.CountDownTimer
|
||||||
|
import com.topjohnwu.magisk.R
|
||||||
|
import com.topjohnwu.magisk.core.Config
|
||||||
|
import com.topjohnwu.magisk.core.magiskdb.PolicyDao
|
||||||
|
import com.topjohnwu.magisk.core.model.MagiskPolicy.Companion.ALLOW
|
||||||
|
import com.topjohnwu.magisk.core.model.MagiskPolicy.Companion.DENY
|
||||||
|
import com.topjohnwu.magisk.core.su.SuRequestHandler
|
||||||
|
import com.topjohnwu.magisk.databinding.ComparableRvItem
|
||||||
|
import com.topjohnwu.magisk.model.entity.recycler.SpinnerRvItem
|
||||||
|
import com.topjohnwu.magisk.model.events.DieEvent
|
||||||
|
import com.topjohnwu.magisk.ui.base.BaseViewModel
|
||||||
|
import com.topjohnwu.magisk.core.utils.BiometricHelper
|
||||||
|
import com.topjohnwu.magisk.utils.DiffObservableList
|
||||||
|
import com.topjohnwu.magisk.utils.KObservableField
|
||||||
|
import me.tatarka.bindingcollectionadapter2.BindingListViewAdapter
|
||||||
|
import me.tatarka.bindingcollectionadapter2.ItemBinding
|
||||||
|
import java.util.concurrent.TimeUnit.SECONDS
|
||||||
|
|
||||||
|
class SuRequestViewModel(
|
||||||
|
private val pm: PackageManager,
|
||||||
|
private val policyDB: PolicyDao,
|
||||||
|
private val timeoutPrefs: SharedPreferences,
|
||||||
|
private val res: Resources
|
||||||
|
) : BaseViewModel() {
|
||||||
|
|
||||||
|
val icon = KObservableField<Drawable?>(null)
|
||||||
|
val title = KObservableField("")
|
||||||
|
val packageName = KObservableField("")
|
||||||
|
|
||||||
|
val denyText = KObservableField(res.getString(R.string.deny))
|
||||||
|
val warningText = KObservableField<CharSequence>(res.getString(R.string.su_warning))
|
||||||
|
|
||||||
|
val selectedItemPosition = KObservableField(0)
|
||||||
|
|
||||||
|
private val items = DiffObservableList(ComparableRvItem.callback)
|
||||||
|
private val itemBinding = ItemBinding.of<ComparableRvItem<*>> { binding, _, item ->
|
||||||
|
item.bind(binding)
|
||||||
|
}
|
||||||
|
|
||||||
|
val adapter = BindingListViewAdapter<ComparableRvItem<*>>(1).apply {
|
||||||
|
itemBinding = this@SuRequestViewModel.itemBinding
|
||||||
|
setItems(items)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val handler = Handler()
|
||||||
|
|
||||||
|
fun grantPressed() {
|
||||||
|
handler.cancelTimer()
|
||||||
|
if (BiometricHelper.isEnabled) {
|
||||||
|
withView {
|
||||||
|
BiometricHelper.authenticate(this) {
|
||||||
|
handler.respond(ALLOW)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
handler.respond(ALLOW)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun denyPressed() {
|
||||||
|
handler.respond(DENY)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun spinnerTouched(): Boolean {
|
||||||
|
handler.cancelTimer()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
fun handleRequest(intent: Intent): Boolean {
|
||||||
|
return handler.start(intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
private inner class Handler : SuRequestHandler(pm, policyDB) {
|
||||||
|
|
||||||
|
fun respond(action: Int) {
|
||||||
|
val pos = selectedItemPosition.value
|
||||||
|
timeoutPrefs.edit().putInt(policy.packageName, pos).apply()
|
||||||
|
respond(action, Config.Value.TIMEOUT_LIST[pos])
|
||||||
|
}
|
||||||
|
|
||||||
|
fun cancelTimer() {
|
||||||
|
timer.cancel()
|
||||||
|
denyText.value = res.getString(R.string.deny)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStart() {
|
||||||
|
res.getStringArray(R.array.allow_timeout)
|
||||||
|
.map { SpinnerRvItem(it) }
|
||||||
|
.let { items.update(it) }
|
||||||
|
|
||||||
|
icon.value = policy.applicationInfo.loadIcon(pm)
|
||||||
|
title.value = policy.appName
|
||||||
|
packageName.value = policy.packageName
|
||||||
|
selectedItemPosition.value = timeoutPrefs.getInt(policy.packageName, 0)
|
||||||
|
|
||||||
|
// Override timer
|
||||||
|
val millis = SECONDS.toMillis(Config.suDefaultTimeout.toLong())
|
||||||
|
timer = object : CountDownTimer(millis, 1000) {
|
||||||
|
override fun onTick(remains: Long) {
|
||||||
|
denyText.value = "${res.getString(R.string.deny)} (${remains / 1000})"
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFinish() {
|
||||||
|
denyText.value = res.getString(R.string.deny)
|
||||||
|
respond(DENY)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRespond() {
|
||||||
|
// Kill activity after response
|
||||||
|
DieEvent().publish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -14,3 +14,14 @@ class HideAppInfo(
|
|||||||
val processes = info.packageInfo?.processes?.distinct() ?: listOf(info.packageName)
|
val processes = info.packageInfo?.processes?.distinct() ?: listOf(info.packageName)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data class StatefulProcess(
|
||||||
|
val name: String,
|
||||||
|
val packageName: String,
|
||||||
|
val isHidden: Boolean
|
||||||
|
)
|
||||||
|
|
||||||
|
class ProcessHideApp(
|
||||||
|
val info: HideAppInfo,
|
||||||
|
val processes: List<StatefulProcess>
|
||||||
|
)
|
@ -3,10 +3,11 @@ package com.topjohnwu.magisk.model.entity
|
|||||||
import androidx.room.Entity
|
import androidx.room.Entity
|
||||||
import androidx.room.Ignore
|
import androidx.room.Ignore
|
||||||
import androidx.room.PrimaryKey
|
import androidx.room.PrimaryKey
|
||||||
|
import com.topjohnwu.magisk.core.model.MagiskPolicy
|
||||||
|
import com.topjohnwu.magisk.core.model.MagiskPolicy.Companion.ALLOW
|
||||||
import com.topjohnwu.magisk.extensions.now
|
import com.topjohnwu.magisk.extensions.now
|
||||||
import com.topjohnwu.magisk.extensions.timeFormatTime
|
import com.topjohnwu.magisk.extensions.timeFormatTime
|
||||||
import com.topjohnwu.magisk.extensions.toTime
|
import com.topjohnwu.magisk.extensions.toTime
|
||||||
import com.topjohnwu.magisk.model.entity.MagiskPolicy.Companion.ALLOW
|
|
||||||
|
|
||||||
@Entity(tableName = "logs")
|
@Entity(tableName = "logs")
|
||||||
data class MagiskLog(
|
data class MagiskLog(
|
||||||
@ -23,11 +24,6 @@ data class MagiskLog(
|
|||||||
@Ignore val timeString = time.toTime(timeFormatTime)
|
@Ignore val timeString = time.toTime(timeFormatTime)
|
||||||
}
|
}
|
||||||
|
|
||||||
data class WrappedMagiskLog(
|
|
||||||
val time: Long,
|
|
||||||
val items: List<MagiskLog>
|
|
||||||
)
|
|
||||||
|
|
||||||
fun MagiskPolicy.toLog(
|
fun MagiskPolicy.toLog(
|
||||||
toUid: Int,
|
toUid: Int,
|
||||||
fromPid: Int,
|
fromPid: Int,
|
||||||
|
@ -2,13 +2,13 @@ package com.topjohnwu.magisk.model.entity.internal
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import com.topjohnwu.magisk.Config
|
import com.topjohnwu.magisk.core.Info
|
||||||
import com.topjohnwu.magisk.Info
|
import com.topjohnwu.magisk.core.Config
|
||||||
|
import com.topjohnwu.magisk.core.model.module.Repo
|
||||||
import com.topjohnwu.magisk.extensions.cachedFile
|
import com.topjohnwu.magisk.extensions.cachedFile
|
||||||
import com.topjohnwu.magisk.extensions.get
|
import com.topjohnwu.magisk.extensions.get
|
||||||
import com.topjohnwu.magisk.model.entity.MagiskJson
|
import com.topjohnwu.magisk.core.model.MagiskJson
|
||||||
import com.topjohnwu.magisk.model.entity.ManagerJson
|
import com.topjohnwu.magisk.core.model.ManagerJson
|
||||||
import com.topjohnwu.magisk.model.entity.module.Repo
|
|
||||||
import kotlinx.android.parcel.IgnoredOnParcel
|
import kotlinx.android.parcel.IgnoredOnParcel
|
||||||
import kotlinx.android.parcel.Parcelize
|
import kotlinx.android.parcel.Parcelize
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@ -59,7 +59,7 @@ sealed class DownloadSubject : Parcelable {
|
|||||||
val magisk: MagiskJson = Info.remote.magisk
|
val magisk: MagiskJson = Info.remote.magisk
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
protected data class Flash(
|
data class Flash(
|
||||||
override val configuration: Configuration
|
override val configuration: Configuration
|
||||||
) : Magisk() {
|
) : Magisk() {
|
||||||
override val url: String get() = magisk.link
|
override val url: String get() = magisk.link
|
||||||
@ -72,7 +72,7 @@ sealed class DownloadSubject : Parcelable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
protected class Uninstall : Magisk() {
|
class Uninstall : Magisk() {
|
||||||
override val configuration: Configuration get() = Configuration.Uninstall
|
override val configuration: Configuration get() = Configuration.Uninstall
|
||||||
override val url: String get() = Info.remote.uninstaller.link
|
override val url: String get() = Info.remote.uninstaller.link
|
||||||
|
|
||||||
@ -83,7 +83,7 @@ sealed class DownloadSubject : Parcelable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
protected class Download : Magisk() {
|
class Download : Magisk() {
|
||||||
override val configuration: Configuration get() = Configuration.Download
|
override val configuration: Configuration get() = Configuration.Download
|
||||||
override val url: String get() = magisk.link
|
override val url: String get() = magisk.link
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@ import androidx.databinding.ViewDataBinding
|
|||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
|
|
||||||
class ConsoleRvItem(val item: String) : LenientRvItem<ConsoleRvItem>() {
|
open class ConsoleRvItem(val item: String) : LenientRvItem<ConsoleRvItem>() {
|
||||||
override val layoutRes: Int = R.layout.item_console
|
override val layoutRes: Int = R.layout.item_console
|
||||||
|
|
||||||
override fun onBindingBound(binding: ViewDataBinding, recyclerView: RecyclerView) {
|
override fun onBindingBound(binding: ViewDataBinding, recyclerView: RecyclerView) {
|
||||||
@ -24,3 +24,7 @@ class ConsoleRvItem(val item: String) : LenientRvItem<ConsoleRvItem>() {
|
|||||||
override fun contentSameAs(other: ConsoleRvItem) = itemSameAs(other)
|
override fun contentSameAs(other: ConsoleRvItem) = itemSameAs(other)
|
||||||
override fun itemSameAs(other: ConsoleRvItem) = item == other.item
|
override fun itemSameAs(other: ConsoleRvItem) = item == other.item
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ConsoleItem(item: String) : ConsoleRvItem(item) {
|
||||||
|
override val layoutRes = R.layout.item_console_md2
|
||||||
|
}
|
@ -1,92 +1,81 @@
|
|||||||
package com.topjohnwu.magisk.model.entity.recycler
|
package com.topjohnwu.magisk.model.entity.recycler
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
import com.topjohnwu.magisk.databinding.ComparableRvItem
|
import com.topjohnwu.magisk.databinding.ComparableRvItem
|
||||||
import com.topjohnwu.magisk.extensions.addOnPropertyChangedCallback
|
import com.topjohnwu.magisk.extensions.addOnPropertyChangedCallback
|
||||||
import com.topjohnwu.magisk.extensions.inject
|
import com.topjohnwu.magisk.extensions.startAnimations
|
||||||
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.ProcessHideApp
|
||||||
import com.topjohnwu.magisk.model.entity.HideTarget
|
import com.topjohnwu.magisk.model.entity.StatefulProcess
|
||||||
import com.topjohnwu.magisk.model.entity.state.IndeterminateState
|
import com.topjohnwu.magisk.model.observer.Observer
|
||||||
import com.topjohnwu.magisk.model.events.HideProcessEvent
|
import com.topjohnwu.magisk.ui.hide.HideViewModel
|
||||||
import com.topjohnwu.magisk.utils.DiffObservableList
|
|
||||||
import com.topjohnwu.magisk.utils.KObservableField
|
import com.topjohnwu.magisk.utils.KObservableField
|
||||||
import com.topjohnwu.magisk.utils.RxBus
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
class HideRvItem(val item: HideAppInfo, targets: List<HideTarget>) :
|
class HideItem(val item: ProcessHideApp) : ComparableRvItem<HideItem>() {
|
||||||
ComparableRvItem<HideRvItem>() {
|
|
||||||
|
|
||||||
override val layoutRes: Int = R.layout.item_hide_app
|
override val layoutRes = R.layout.item_hide_md2
|
||||||
|
|
||||||
|
val packageName = item.info.info.packageName.orEmpty()
|
||||||
|
val items = item.processes.map { HideProcessItem(it) }
|
||||||
|
|
||||||
val packageName = item.info.packageName.orEmpty()
|
|
||||||
val items = DiffObservableList(callback).also {
|
|
||||||
val items = item.processes.map {
|
|
||||||
val isHidden = targets.any { target ->
|
|
||||||
packageName == target.packageName && it == target.process
|
|
||||||
}
|
|
||||||
HideProcessRvItem(packageName, it, isHidden)
|
|
||||||
}
|
|
||||||
it.update(items)
|
|
||||||
}
|
|
||||||
val isHiddenState = KObservableField(currentState)
|
|
||||||
val isExpanded = KObservableField(false)
|
val isExpanded = KObservableField(false)
|
||||||
|
val itemsChecked = KObservableField(0)
|
||||||
private val itemsProcess get() = items.filterIsInstance<HideProcessRvItem>()
|
val itemsCheckedPercent = Observer(itemsChecked) {
|
||||||
|
(itemsChecked.value.toFloat() / items.size * 100).roundToInt()
|
||||||
private val currentState
|
|
||||||
get() = when (itemsProcess.count { it.isHidden.value }) {
|
|
||||||
items.size -> IndeterminateState.CHECKED
|
|
||||||
in 1 until items.size -> IndeterminateState.INDETERMINATE
|
|
||||||
else -> IndeterminateState.UNCHECKED
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** [toggle] depends on this functionality */
|
||||||
|
private val isHidden get() = itemsChecked.value == items.size
|
||||||
|
|
||||||
init {
|
init {
|
||||||
itemsProcess.forEach {
|
items.forEach { it.isHidden.addOnPropertyChangedCallback { recalculateChecked() } }
|
||||||
it.isHidden.addOnPropertyChangedCallback { isHiddenState.value = currentState }
|
recalculateChecked()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun toggle() {
|
fun collapse(v: View) {
|
||||||
val desiredState = when (isHiddenState.value) {
|
(v.parent.parent as? ViewGroup)?.startAnimations()
|
||||||
IndeterminateState.INDETERMINATE,
|
isExpanded.value = false
|
||||||
IndeterminateState.UNCHECKED -> true
|
|
||||||
IndeterminateState.CHECKED -> false
|
|
||||||
}
|
|
||||||
itemsProcess.forEach { it.isHidden.value = desiredState }
|
|
||||||
isHiddenState.value = currentState
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun toggleExpansion() {
|
fun toggle(v: View) {
|
||||||
if (items.size <= 1) return
|
(v.parent as? ViewGroup)?.startAnimations()
|
||||||
isExpanded.toggle()
|
isExpanded.toggle()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun contentSameAs(other: HideRvItem): Boolean = items.all { other.items.contains(it) }
|
fun toggle(viewModel: HideViewModel): Boolean {
|
||||||
override fun itemSameAs(other: HideRvItem): Boolean = item.info == other.item.info
|
// contract implies that isHidden == all checked
|
||||||
|
if (!isHidden) {
|
||||||
|
items.filterNot { it.isHidden.value }
|
||||||
|
} else {
|
||||||
|
items
|
||||||
|
}.forEach { it.toggle(viewModel) }
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun recalculateChecked() {
|
||||||
|
itemsChecked.value = items.count { it.isHidden.value }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun contentSameAs(other: HideItem): Boolean = item == other.item
|
||||||
|
override fun itemSameAs(other: HideItem): Boolean = item.info == other.item.info
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class HideProcessRvItem(
|
class HideProcessItem(val item: StatefulProcess) : ComparableRvItem<HideProcessItem>() {
|
||||||
val packageName: String,
|
|
||||||
val process: String,
|
|
||||||
isHidden: Boolean
|
|
||||||
) : ComparableRvItem<HideProcessRvItem>() {
|
|
||||||
|
|
||||||
override val layoutRes: Int = R.layout.item_hide_process
|
override val layoutRes = R.layout.item_hide_process_md2
|
||||||
|
|
||||||
val isHidden = KObservableField(isHidden)
|
val isHidden = KObservableField(item.isHidden)
|
||||||
|
|
||||||
private val rxBus: RxBus by inject()
|
fun toggle(viewModel: HideViewModel) {
|
||||||
|
isHidden.toggle()
|
||||||
init {
|
viewModel.toggleItem(this)
|
||||||
this.isHidden.addOnPropertyChangedCallback {
|
|
||||||
rxBus.post(HideProcessEvent(this@HideProcessRvItem))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun toggle() = isHidden.toggle()
|
override fun contentSameAs(other: HideProcessItem) = item == other.item
|
||||||
|
override fun itemSameAs(other: HideProcessItem) = item.name == other.item.name
|
||||||
|
|
||||||
override fun contentSameAs(other: HideProcessRvItem): Boolean = itemSameAs(other)
|
|
||||||
override fun itemSameAs(other: HideProcessRvItem): Boolean =
|
|
||||||
packageName == other.packageName && process == other.process
|
|
||||||
}
|
}
|
@ -0,0 +1,116 @@
|
|||||||
|
package com.topjohnwu.magisk.model.entity.recycler
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.R
|
||||||
|
import com.topjohnwu.magisk.core.Const
|
||||||
|
import com.topjohnwu.magisk.databinding.ComparableRvItem
|
||||||
|
|
||||||
|
sealed class HomeItem : ComparableRvItem<HomeItem>() {
|
||||||
|
|
||||||
|
abstract val icon: Int
|
||||||
|
abstract val title: Int
|
||||||
|
abstract val link: String
|
||||||
|
|
||||||
|
override val layoutRes = R.layout.item_developer_link
|
||||||
|
|
||||||
|
override fun contentSameAs(other: HomeItem) = itemSameAs(other)
|
||||||
|
override fun itemSameAs(other: HomeItem) = this == other
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (other !is HomeItem) return false
|
||||||
|
return icon == other.icon && title == other.title && link == other.link
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode() =
|
||||||
|
icon.hashCode() + title.hashCode() + link.hashCode() + layoutRes.hashCode()
|
||||||
|
|
||||||
|
// region Children
|
||||||
|
sealed class PayPal : HomeItem() {
|
||||||
|
override val icon = R.drawable.ic_paypal
|
||||||
|
override val title = R.string.home_item_paypal
|
||||||
|
override val link = "https://paypal.me/%s"
|
||||||
|
|
||||||
|
// region Children
|
||||||
|
object App : PayPal() {
|
||||||
|
override val link = super.link.format("diareuse")
|
||||||
|
}
|
||||||
|
|
||||||
|
object Mainline : PayPal() {
|
||||||
|
override val link = super.link.format("topjohnwu")
|
||||||
|
}
|
||||||
|
// endregion
|
||||||
|
}
|
||||||
|
|
||||||
|
object Patreon : HomeItem() {
|
||||||
|
override val icon = R.drawable.ic_patreon
|
||||||
|
override val title = R.string.home_item_patreon
|
||||||
|
override val link = Const.Url.PATREON_URL
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class Twitter : HomeItem() {
|
||||||
|
override val icon = R.drawable.ic_twitter
|
||||||
|
override val title = R.string.home_item_twitter
|
||||||
|
override val link = "https://twitter.com/%s"
|
||||||
|
|
||||||
|
// region Children
|
||||||
|
object App : Twitter() {
|
||||||
|
override val link = super.link.format("diareuse")
|
||||||
|
}
|
||||||
|
|
||||||
|
object Mainline : Twitter() {
|
||||||
|
override val link = super.link.format("topjohnwu")
|
||||||
|
}
|
||||||
|
// endregion
|
||||||
|
}
|
||||||
|
|
||||||
|
object Github : HomeItem() {
|
||||||
|
override val icon = R.drawable.ic_github
|
||||||
|
override val title = R.string.home_item_source
|
||||||
|
override val link = Const.Url.SOURCE_CODE_URL
|
||||||
|
}
|
||||||
|
|
||||||
|
object Xda : HomeItem() {
|
||||||
|
override val icon = R.drawable.ic_xda
|
||||||
|
override val title = R.string.home_item_xda
|
||||||
|
override val link = Const.Url.XDA_THREAD
|
||||||
|
}
|
||||||
|
// endregion
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class DeveloperItem : ComparableRvItem<DeveloperItem>() {
|
||||||
|
|
||||||
|
abstract val items: List<HomeItem>
|
||||||
|
abstract val name: Int
|
||||||
|
|
||||||
|
override val layoutRes = R.layout.item_developer
|
||||||
|
|
||||||
|
override fun contentSameAs(other: DeveloperItem) = itemSameAs(other)
|
||||||
|
override fun itemSameAs(other: DeveloperItem) = this == other
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (other !is DeveloperItem) return false
|
||||||
|
return name == other.name && items == other.items
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode() = name.hashCode() + items.hashCode() + layoutRes.hashCode()
|
||||||
|
|
||||||
|
//region Children
|
||||||
|
object Mainline : DeveloperItem() {
|
||||||
|
override val items =
|
||||||
|
listOf(HomeItem.PayPal.Mainline, HomeItem.Patreon, HomeItem.Twitter.Mainline)
|
||||||
|
override val name = R.string.home_links_mainline
|
||||||
|
}
|
||||||
|
|
||||||
|
object App : DeveloperItem() {
|
||||||
|
override val items =
|
||||||
|
listOf(HomeItem.PayPal.App, HomeItem.Twitter.App)
|
||||||
|
override val name = R.string.home_links_app
|
||||||
|
}
|
||||||
|
|
||||||
|
object Project : DeveloperItem() {
|
||||||
|
override val items =
|
||||||
|
listOf(HomeItem.Github, HomeItem.Xda)
|
||||||
|
override val name = R.string.home_links_project
|
||||||
|
}
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
}
|
@ -1,73 +1,39 @@
|
|||||||
package com.topjohnwu.magisk.model.entity.recycler
|
package com.topjohnwu.magisk.model.entity.recycler
|
||||||
|
|
||||||
|
import androidx.databinding.Bindable
|
||||||
|
import com.topjohnwu.magisk.BR
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
import com.topjohnwu.magisk.databinding.ComparableRvItem
|
import com.topjohnwu.magisk.extensions.timeDateFormat
|
||||||
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.model.entity.MagiskLog
|
import com.topjohnwu.magisk.model.entity.MagiskLog
|
||||||
import com.topjohnwu.magisk.model.entity.WrappedMagiskLog
|
|
||||||
import com.topjohnwu.magisk.utils.DiffObservableList
|
|
||||||
import com.topjohnwu.magisk.utils.KObservableField
|
|
||||||
|
|
||||||
class LogRvItem : ComparableRvItem<LogRvItem>() {
|
class LogItem(val item: MagiskLog) : ObservableItem<LogItem>() {
|
||||||
override val layoutRes: Int = R.layout.item_page_log
|
|
||||||
|
|
||||||
val items = DiffObservableList(callback)
|
override val layoutRes = R.layout.item_log_access_md2
|
||||||
|
|
||||||
fun update(list: List<LogItemRvItem>) {
|
val date = item.time.toTime(timeDateFormat)
|
||||||
list.firstOrNull()?.isExpanded?.value = true
|
var isTop = false
|
||||||
items.update(list)
|
@Bindable get
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
notifyChange(BR.top)
|
||||||
|
}
|
||||||
|
var isBottom = false
|
||||||
|
@Bindable get
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
notifyChange(BR.bottom)
|
||||||
}
|
}
|
||||||
|
|
||||||
//two of these will never be present, safe to assume it's unique
|
override fun itemSameAs(other: LogItem) = item.appName == other.item.appName
|
||||||
override fun contentSameAs(other: LogRvItem): Boolean = false
|
|
||||||
|
|
||||||
override fun itemSameAs(other: LogRvItem): Boolean = false
|
override fun contentSameAs(other: LogItem) = item.fromUid == other.item.fromUid &&
|
||||||
}
|
item.toUid == other.item.toUid &&
|
||||||
|
item.fromPid == other.item.fromPid &&
|
||||||
class LogItemRvItem(
|
item.packageName == other.item.packageName &&
|
||||||
item: WrappedMagiskLog
|
item.command == other.item.command &&
|
||||||
) : ComparableRvItem<LogItemRvItem>() {
|
item.action == other.item.action &&
|
||||||
override val layoutRes: Int = R.layout.item_superuser_log
|
item.time == other.item.time &&
|
||||||
|
isTop == other.isTop &&
|
||||||
val date = item.time.toTime(timeFormatMedium)
|
isBottom == other.isBottom
|
||||||
val items: List<ComparableRvItem<*>> = item.items.map { LogItemEntryRvItem(it) }
|
|
||||||
val isExpanded = KObservableField(false)
|
|
||||||
|
|
||||||
fun toggle() = isExpanded.toggle()
|
|
||||||
|
|
||||||
override fun contentSameAs(other: LogItemRvItem): Boolean {
|
|
||||||
if (items.size != other.items.size) return false
|
|
||||||
return items.all { it in other.items }
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun itemSameAs(other: LogItemRvItem): Boolean = date == other.date
|
|
||||||
}
|
|
||||||
|
|
||||||
class LogItemEntryRvItem(val item: MagiskLog) : ComparableRvItem<LogItemEntryRvItem>() {
|
|
||||||
override val layoutRes: Int = R.layout.item_superuser_log_entry
|
|
||||||
|
|
||||||
val isExpanded = KObservableField(false)
|
|
||||||
|
|
||||||
fun toggle() = isExpanded.toggle()
|
|
||||||
|
|
||||||
override fun contentSameAs(other: LogItemEntryRvItem) = item == other.item
|
|
||||||
|
|
||||||
override fun itemSameAs(other: LogItemEntryRvItem) = item.appName == other.item.appName
|
|
||||||
}
|
|
||||||
|
|
||||||
class MagiskLogRvItem : ComparableRvItem<MagiskLogRvItem>() {
|
|
||||||
override val layoutRes: Int = R.layout.item_page_magisk_log
|
|
||||||
|
|
||||||
val items = DiffObservableList(callback)
|
|
||||||
|
|
||||||
fun update(list: List<ConsoleRvItem>) {
|
|
||||||
items.update(list)
|
|
||||||
}
|
|
||||||
|
|
||||||
//two of these will never be present, safe to assume it's unique
|
|
||||||
override fun contentSameAs(other: MagiskLogRvItem): Boolean = false
|
|
||||||
|
|
||||||
override fun itemSameAs(other: MagiskLogRvItem): Boolean = false
|
|
||||||
}
|
}
|
||||||
|
@ -1,75 +1,162 @@
|
|||||||
package com.topjohnwu.magisk.model.entity.recycler
|
package com.topjohnwu.magisk.model.entity.recycler
|
||||||
|
|
||||||
import android.content.res.Resources
|
import androidx.databinding.Bindable
|
||||||
import androidx.annotation.StringRes
|
import androidx.databinding.Observable
|
||||||
|
import androidx.databinding.PropertyChangeRegistry
|
||||||
|
import androidx.databinding.ViewDataBinding
|
||||||
|
import androidx.recyclerview.widget.StaggeredGridLayoutManager
|
||||||
|
import com.topjohnwu.magisk.BR
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
|
import com.topjohnwu.magisk.core.model.module.Module
|
||||||
|
import com.topjohnwu.magisk.core.model.module.Repo
|
||||||
import com.topjohnwu.magisk.databinding.ComparableRvItem
|
import com.topjohnwu.magisk.databinding.ComparableRvItem
|
||||||
import com.topjohnwu.magisk.extensions.addOnPropertyChangedCallback
|
import com.topjohnwu.magisk.ui.module.ModuleViewModel
|
||||||
import com.topjohnwu.magisk.extensions.get
|
|
||||||
import com.topjohnwu.magisk.extensions.toggle
|
|
||||||
import com.topjohnwu.magisk.model.entity.module.Module
|
|
||||||
import com.topjohnwu.magisk.model.entity.module.Repo
|
|
||||||
import com.topjohnwu.magisk.utils.KObservableField
|
import com.topjohnwu.magisk.utils.KObservableField
|
||||||
|
|
||||||
class ModuleRvItem(val item: Module) : ComparableRvItem<ModuleRvItem>() {
|
object SafeModeNotice : ComparableRvItem<SafeModeNotice>() {
|
||||||
|
override val layoutRes = R.layout.item_safe_mode_notice
|
||||||
|
|
||||||
override val layoutRes: Int = R.layout.item_module
|
override fun onBindingBound(binding: ViewDataBinding) {
|
||||||
|
super.onBindingBound(binding)
|
||||||
|
val params = binding.root.layoutParams as? StaggeredGridLayoutManager.LayoutParams
|
||||||
|
params?.isFullSpan = true
|
||||||
|
}
|
||||||
|
|
||||||
val lastActionNotice = KObservableField("")
|
override fun contentSameAs(other: SafeModeNotice) = this == other
|
||||||
val isChecked = KObservableField(item.enable)
|
override fun itemSameAs(other: SafeModeNotice) = this === other
|
||||||
val isDeletable = KObservableField(item.remove)
|
}
|
||||||
|
|
||||||
|
object InstallModule : ComparableRvItem<InstallModule>() {
|
||||||
|
override val layoutRes = R.layout.item_module_download
|
||||||
|
|
||||||
|
override fun onBindingBound(binding: ViewDataBinding) {
|
||||||
|
super.onBindingBound(binding)
|
||||||
|
val params = binding.root.layoutParams as? StaggeredGridLayoutManager.LayoutParams
|
||||||
|
params?.isFullSpan = true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun contentSameAs(other: InstallModule) = this == other
|
||||||
|
override fun itemSameAs(other: InstallModule) = this === other
|
||||||
|
}
|
||||||
|
|
||||||
|
class SectionTitle(
|
||||||
|
val title: Int,
|
||||||
|
_button: Int = 0,
|
||||||
|
_icon: Int = 0
|
||||||
|
) : ObservableItem<SectionTitle>() {
|
||||||
|
override val layoutRes = R.layout.item_section_md2
|
||||||
|
|
||||||
|
var button = _button
|
||||||
|
@Bindable get
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
notifyChange(BR.button)
|
||||||
|
}
|
||||||
|
var icon = _icon
|
||||||
|
@Bindable get
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
notifyChange(BR.icon)
|
||||||
|
}
|
||||||
|
var hasButton = button != 0 || icon != 0
|
||||||
|
@Bindable get
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
notifyChange(BR.hasButton)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindingBound(binding: ViewDataBinding) {
|
||||||
|
super.onBindingBound(binding)
|
||||||
|
val params = binding.root.layoutParams as? StaggeredGridLayoutManager.LayoutParams
|
||||||
|
params?.isFullSpan = true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun itemSameAs(other: SectionTitle): Boolean = this === other
|
||||||
|
override fun contentSameAs(other: SectionTitle): Boolean = this === other
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class RepoItem(val item: Repo) : ObservableItem<RepoItem>() {
|
||||||
|
override val layoutRes: Int = R.layout.item_repo_md2
|
||||||
|
|
||||||
|
val progress = KObservableField(0)
|
||||||
|
var isUpdate = false
|
||||||
|
@Bindable get
|
||||||
|
protected set(value) {
|
||||||
|
field = value
|
||||||
|
notifyChange(BR.update)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun contentSameAs(other: RepoItem): Boolean = item == other.item
|
||||||
|
override fun itemSameAs(other: RepoItem): Boolean = item.id == other.item.id
|
||||||
|
|
||||||
|
class Update(item: Repo) : RepoItem(item) {
|
||||||
init {
|
init {
|
||||||
isChecked.addOnPropertyChangedCallback {
|
isUpdate = true
|
||||||
when (it) {
|
|
||||||
true -> {
|
|
||||||
item.enable = true
|
|
||||||
notice(R.string.disable_file_removed)
|
|
||||||
}
|
|
||||||
false -> {
|
|
||||||
item.enable = false
|
|
||||||
notice(R.string.disable_file_created)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
isDeletable.addOnPropertyChangedCallback {
|
|
||||||
when (it) {
|
|
||||||
true -> {
|
|
||||||
item.remove = true
|
|
||||||
notice(R.string.remove_file_created)
|
|
||||||
}
|
|
||||||
false -> {
|
|
||||||
item.remove = false
|
|
||||||
notice(R.string.remove_file_deleted)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
when {
|
|
||||||
item.updated -> notice(R.string.update_file_created)
|
|
||||||
item.remove -> notice(R.string.remove_file_created)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun toggle() = isChecked.toggle()
|
class Remote(item: Repo) : RepoItem(item)
|
||||||
fun toggleDelete() = isDeletable.toggle()
|
}
|
||||||
|
|
||||||
private fun notice(@StringRes info: Int) {
|
class ModuleItem(val item: Module) : ObservableItem<ModuleItem>(), Observable {
|
||||||
lastActionNotice.value = get<Resources>().getString(info)
|
|
||||||
|
override val layoutRes = R.layout.item_module_md2
|
||||||
|
|
||||||
|
@get:Bindable
|
||||||
|
var repo: Repo? = null
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
notifyChange(BR.repo)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun contentSameAs(other: ModuleRvItem): Boolean = item.version == other.item.version
|
@get:Bindable
|
||||||
|
var isEnabled = item.enable
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
item.enable = value
|
||||||
|
notifyChange(BR.enabled)
|
||||||
|
}
|
||||||
|
@get:Bindable
|
||||||
|
var isRemoved = item.remove
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
item.remove = value
|
||||||
|
notifyChange(BR.removed)
|
||||||
|
}
|
||||||
|
|
||||||
|
val isUpdated get() = item.updated
|
||||||
|
val isModified get() = isRemoved || item.updated
|
||||||
|
|
||||||
|
fun toggle() {
|
||||||
|
isEnabled = !isEnabled
|
||||||
|
}
|
||||||
|
|
||||||
|
fun delete(viewModel: ModuleViewModel) {
|
||||||
|
isRemoved = !isRemoved
|
||||||
|
viewModel.updateActiveState()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun contentSameAs(other: ModuleItem): Boolean = item.version == other.item.version
|
||||||
&& item.versionCode == other.item.versionCode
|
&& item.versionCode == other.item.versionCode
|
||||||
&& item.description == other.item.description
|
&& item.description == other.item.description
|
||||||
&& item.name == other.item.name
|
&& item.name == other.item.name
|
||||||
|
|
||||||
override fun itemSameAs(other: ModuleRvItem): Boolean = item.id == other.item.id
|
override fun itemSameAs(other: ModuleItem): Boolean = item.id == other.item.id
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class RepoRvItem(val item: Repo) : ComparableRvItem<RepoRvItem>() {
|
abstract class ObservableItem<T> : ComparableRvItem<T>(), Observable {
|
||||||
|
|
||||||
override val layoutRes: Int = R.layout.item_repo
|
private val list = PropertyChangeRegistry()
|
||||||
|
|
||||||
override fun contentSameAs(other: RepoRvItem): Boolean = item == other.item
|
override fun removeOnPropertyChangedCallback(callback: Observable.OnPropertyChangedCallback?) {
|
||||||
|
list.remove(callback ?: return)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun addOnPropertyChangedCallback(callback: Observable.OnPropertyChangedCallback?) {
|
||||||
|
list.add(callback ?: return)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun notifyChange(id: Int) = list.notifyChange(this, id)
|
||||||
|
|
||||||
override fun itemSameAs(other: RepoRvItem): Boolean = item.id == other.item.id
|
|
||||||
}
|
}
|
@ -2,51 +2,52 @@ package com.topjohnwu.magisk.model.entity.recycler
|
|||||||
|
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
|
import com.topjohnwu.magisk.core.model.MagiskPolicy
|
||||||
import com.topjohnwu.magisk.databinding.ComparableRvItem
|
import com.topjohnwu.magisk.databinding.ComparableRvItem
|
||||||
import com.topjohnwu.magisk.extensions.addOnPropertyChangedCallback
|
|
||||||
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.events.PolicyEnableEvent
|
|
||||||
import com.topjohnwu.magisk.model.events.PolicyUpdateEvent
|
import com.topjohnwu.magisk.model.events.PolicyUpdateEvent
|
||||||
|
import com.topjohnwu.magisk.ui.superuser.SuperuserViewModel
|
||||||
import com.topjohnwu.magisk.utils.KObservableField
|
import com.topjohnwu.magisk.utils.KObservableField
|
||||||
import com.topjohnwu.magisk.utils.RxBus
|
|
||||||
|
|
||||||
class PolicyRvItem(val item: MagiskPolicy, val icon: Drawable) : ComparableRvItem<PolicyRvItem>() {
|
class PolicyItem(val item: MagiskPolicy, val icon: Drawable) : ComparableRvItem<PolicyItem>() {
|
||||||
|
override val layoutRes = R.layout.item_policy_md2
|
||||||
override val layoutRes: Int = R.layout.item_policy
|
|
||||||
|
|
||||||
val isExpanded = KObservableField(false)
|
val isExpanded = KObservableField(false)
|
||||||
val isEnabled = KObservableField(item.policy == MagiskPolicy.ALLOW)
|
val isEnabled = KObservableField(item.policy == MagiskPolicy.ALLOW)
|
||||||
val shouldNotify = KObservableField(item.notification)
|
val shouldNotify = KObservableField(item.notification)
|
||||||
val shouldLog = KObservableField(item.logging)
|
val shouldLog = KObservableField(item.logging)
|
||||||
|
|
||||||
fun toggle() = isExpanded.toggle()
|
private val updatedPolicy
|
||||||
|
|
||||||
private val rxBus: RxBus by inject()
|
|
||||||
|
|
||||||
private val currentStateItem
|
|
||||||
get() = item.copy(
|
get() = item.copy(
|
||||||
policy = if (isEnabled.value) MagiskPolicy.ALLOW else MagiskPolicy.DENY,
|
policy = if (isEnabled.value) MagiskPolicy.ALLOW else MagiskPolicy.DENY,
|
||||||
notification = shouldNotify.value,
|
notification = shouldNotify.value,
|
||||||
logging = shouldLog.value
|
logging = shouldLog.value
|
||||||
)
|
)
|
||||||
|
|
||||||
init {
|
fun toggle(viewModel: SuperuserViewModel) {
|
||||||
isEnabled.addOnPropertyChangedCallback {
|
if (isExpanded.value) {
|
||||||
it ?: return@addOnPropertyChangedCallback
|
toggle()
|
||||||
rxBus.post(PolicyEnableEvent(this@PolicyRvItem, it))
|
return
|
||||||
}
|
|
||||||
shouldNotify.addOnPropertyChangedCallback {
|
|
||||||
it ?: return@addOnPropertyChangedCallback
|
|
||||||
rxBus.post(PolicyUpdateEvent.Notification(currentStateItem))
|
|
||||||
}
|
|
||||||
shouldLog.addOnPropertyChangedCallback {
|
|
||||||
it ?: return@addOnPropertyChangedCallback
|
|
||||||
rxBus.post(PolicyUpdateEvent.Log(currentStateItem))
|
|
||||||
}
|
}
|
||||||
|
isEnabled.toggle()
|
||||||
|
viewModel.togglePolicy(this, isEnabled.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun contentSameAs(other: PolicyRvItem): Boolean = itemSameAs(other)
|
fun toggle() {
|
||||||
override fun itemSameAs(other: PolicyRvItem): Boolean = item.uid == other.item.uid
|
isExpanded.toggle()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toggleNotify(viewModel: SuperuserViewModel) {
|
||||||
|
shouldNotify.toggle()
|
||||||
|
viewModel.updatePolicy(PolicyUpdateEvent.Notification(updatedPolicy))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toggleLog(viewModel: SuperuserViewModel) {
|
||||||
|
shouldLog.toggle()
|
||||||
|
viewModel.updatePolicy(PolicyUpdateEvent.Log(updatedPolicy))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun contentSameAs(other: PolicyItem) = itemSameAs(other)
|
||||||
|
override fun itemSameAs(other: PolicyItem) = item.uid == other.item.uid
|
||||||
|
|
||||||
}
|
}
|
@ -0,0 +1,191 @@
|
|||||||
|
package com.topjohnwu.magisk.model.entity.recycler
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.res.Resources
|
||||||
|
import android.view.MotionEvent
|
||||||
|
import android.view.View
|
||||||
|
import androidx.annotation.CallSuper
|
||||||
|
import androidx.databinding.Bindable
|
||||||
|
import androidx.databinding.ViewDataBinding
|
||||||
|
import androidx.recyclerview.widget.StaggeredGridLayoutManager
|
||||||
|
import com.topjohnwu.magisk.BR
|
||||||
|
import com.topjohnwu.magisk.R
|
||||||
|
import com.topjohnwu.magisk.utils.TransitiveText
|
||||||
|
import com.topjohnwu.magisk.view.MagiskDialog
|
||||||
|
import org.koin.core.KoinComponent
|
||||||
|
import org.koin.core.get
|
||||||
|
import kotlin.properties.ObservableProperty
|
||||||
|
import kotlin.reflect.KProperty
|
||||||
|
|
||||||
|
sealed class SettingsItem : ObservableItem<SettingsItem>() {
|
||||||
|
|
||||||
|
open val icon: Int get() = 0
|
||||||
|
open val title: TransitiveText get() = TransitiveText.EMPTY
|
||||||
|
|
||||||
|
@get:Bindable
|
||||||
|
open val description: TransitiveText get() = TransitiveText.EMPTY
|
||||||
|
|
||||||
|
@get:Bindable
|
||||||
|
var isEnabled by bindable(true, BR.enabled)
|
||||||
|
|
||||||
|
protected open val isFullSpan get() = false
|
||||||
|
|
||||||
|
@CallSuper
|
||||||
|
open fun onPressed(view: View, callback: Callback) {
|
||||||
|
callback.onItemChanged(view, this)
|
||||||
|
|
||||||
|
// notify only after the callback invocation; callback can invalidate the backing data,
|
||||||
|
// which wouldn't be recognized with reverse approach
|
||||||
|
notifyChange(BR.description)
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun refresh() {}
|
||||||
|
|
||||||
|
override fun onBindingBound(binding: ViewDataBinding) {
|
||||||
|
super.onBindingBound(binding)
|
||||||
|
if (isFullSpan) {
|
||||||
|
val params = binding.root.layoutParams as? StaggeredGridLayoutManager.LayoutParams
|
||||||
|
params?.isFullSpan = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun itemSameAs(other: SettingsItem) = this === other
|
||||||
|
override fun contentSameAs(other: SettingsItem) = itemSameAs(other)
|
||||||
|
|
||||||
|
protected inline fun <T> bindable(
|
||||||
|
initialValue: T,
|
||||||
|
fieldId: Int,
|
||||||
|
crossinline setter: (T) -> Unit = {}
|
||||||
|
) = object : ObservableProperty<T>(initialValue) {
|
||||||
|
override fun afterChange(property: KProperty<*>, oldValue: T, newValue: T) {
|
||||||
|
setter(newValue)
|
||||||
|
notifyChange(fieldId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---
|
||||||
|
|
||||||
|
interface Callback {
|
||||||
|
fun onItemPressed(view: View, item: SettingsItem)
|
||||||
|
fun onItemChanged(view: View, item: SettingsItem)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---
|
||||||
|
|
||||||
|
abstract class Value<T> : SettingsItem() {
|
||||||
|
|
||||||
|
@get:Bindable
|
||||||
|
abstract var value: T
|
||||||
|
|
||||||
|
protected inline fun bindableValue(
|
||||||
|
initialValue: T,
|
||||||
|
crossinline setter: (T) -> Unit
|
||||||
|
) = bindable(initialValue, BR.value, setter)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class Toggle : Value<Boolean>() {
|
||||||
|
|
||||||
|
override val layoutRes = R.layout.item_settings_toggle
|
||||||
|
|
||||||
|
override fun onPressed(view: View, callback: Callback) {
|
||||||
|
callback.onItemPressed(view, this)
|
||||||
|
value = !value
|
||||||
|
super.onPressed(view, callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onTouched(view: View, callback: Callback, event: MotionEvent): Boolean {
|
||||||
|
if (event.action == MotionEvent.ACTION_UP) {
|
||||||
|
onPressed(view, callback)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class Input : Value<String>(), KoinComponent {
|
||||||
|
|
||||||
|
override val layoutRes = R.layout.item_settings_input
|
||||||
|
open val showStrip = true
|
||||||
|
|
||||||
|
protected val resources get() = get<Resources>()
|
||||||
|
protected abstract val intermediate: String?
|
||||||
|
|
||||||
|
override fun onPressed(view: View, callback: Callback) {
|
||||||
|
callback.onItemPressed(view, this)
|
||||||
|
MagiskDialog(view.context)
|
||||||
|
.applyTitle(title.getText(resources))
|
||||||
|
.applyView(getView(view.context))
|
||||||
|
.applyButton(MagiskDialog.ButtonType.POSITIVE) {
|
||||||
|
titleRes = android.R.string.ok
|
||||||
|
onClick {
|
||||||
|
intermediate?.let { result ->
|
||||||
|
preventDismiss = false
|
||||||
|
value = result
|
||||||
|
it.dismiss()
|
||||||
|
super.onPressed(view, callback)
|
||||||
|
return@onClick
|
||||||
|
}
|
||||||
|
preventDismiss = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.applyButton(MagiskDialog.ButtonType.NEGATIVE) {
|
||||||
|
titleRes = android.R.string.cancel
|
||||||
|
}
|
||||||
|
.reveal()
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract fun getView(context: Context): View
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class Selector : Value<Int>(), KoinComponent {
|
||||||
|
|
||||||
|
override val layoutRes = R.layout.item_settings_selector
|
||||||
|
|
||||||
|
protected val resources get() = get<Resources>()
|
||||||
|
|
||||||
|
abstract val entries: Array<out CharSequence>
|
||||||
|
abstract val entryValues: Array<out CharSequence>
|
||||||
|
|
||||||
|
@get:Bindable
|
||||||
|
val selectedEntry
|
||||||
|
get() = entries.getOrNull(value)
|
||||||
|
|
||||||
|
override fun onPressed(view: View, callback: Callback) {
|
||||||
|
if (entries.isEmpty() || entryValues.isEmpty()) return
|
||||||
|
callback.onItemPressed(view, this)
|
||||||
|
MagiskDialog(view.context)
|
||||||
|
.applyTitle(title.getText(resources))
|
||||||
|
.applyButton(MagiskDialog.ButtonType.NEGATIVE) {
|
||||||
|
titleRes = android.R.string.cancel
|
||||||
|
}
|
||||||
|
.applyAdapter(entries) {
|
||||||
|
value = it
|
||||||
|
notifyChange(BR.selectedEntry)
|
||||||
|
super.onPressed(view, callback)
|
||||||
|
}
|
||||||
|
.reveal()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class Blank : SettingsItem() {
|
||||||
|
|
||||||
|
override val layoutRes = R.layout.item_settings_blank
|
||||||
|
|
||||||
|
override fun onPressed(view: View, callback: Callback) {
|
||||||
|
callback.onItemPressed(view, this)
|
||||||
|
super.onPressed(view, callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class Section : SettingsItem() {
|
||||||
|
|
||||||
|
override val layoutRes = R.layout.item_settings_section
|
||||||
|
override val isFullSpan get() = true
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
package com.topjohnwu.magisk.model.entity.recycler
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.R
|
||||||
|
import com.topjohnwu.magisk.databinding.ComparableRvItem
|
||||||
|
|
||||||
|
sealed class TappableHeadlineItem : ComparableRvItem<TappableHeadlineItem>() {
|
||||||
|
|
||||||
|
abstract val title: Int
|
||||||
|
abstract val icon: Int
|
||||||
|
|
||||||
|
override val layoutRes = R.layout.item_tappable_headline
|
||||||
|
|
||||||
|
override fun itemSameAs(other: TappableHeadlineItem) =
|
||||||
|
this === other
|
||||||
|
|
||||||
|
override fun contentSameAs(other: TappableHeadlineItem) =
|
||||||
|
title == other.title && icon == other.icon
|
||||||
|
|
||||||
|
// --- listener
|
||||||
|
|
||||||
|
interface Listener {
|
||||||
|
|
||||||
|
fun onItemPressed(item: TappableHeadlineItem)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- objects
|
||||||
|
|
||||||
|
object Hide : TappableHeadlineItem() {
|
||||||
|
override val title = R.string.magisk_hide_md2
|
||||||
|
override val icon = R.drawable.ic_hide_md2
|
||||||
|
}
|
||||||
|
|
||||||
|
object Safetynet : TappableHeadlineItem() {
|
||||||
|
override val title = R.string.safetyNet
|
||||||
|
override val icon = R.drawable.ic_safetynet_md2
|
||||||
|
}
|
||||||
|
|
||||||
|
object ThemeMode : TappableHeadlineItem() {
|
||||||
|
override val title = R.string.settings_dark_mode_title
|
||||||
|
override val icon = R.drawable.ic_day_night
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
package com.topjohnwu.magisk.model.entity.recycler
|
||||||
|
|
||||||
|
import androidx.databinding.ViewDataBinding
|
||||||
|
import androidx.recyclerview.widget.StaggeredGridLayoutManager
|
||||||
|
import com.topjohnwu.magisk.R
|
||||||
|
import com.topjohnwu.magisk.databinding.ComparableRvItem
|
||||||
|
|
||||||
|
class TextItem(val text: Int) : ComparableRvItem<TextItem>() {
|
||||||
|
override val layoutRes = R.layout.item_text
|
||||||
|
|
||||||
|
override fun onBindingBound(binding: ViewDataBinding) {
|
||||||
|
super.onBindingBound(binding)
|
||||||
|
val params = binding.root.layoutParams as? StaggeredGridLayoutManager.LayoutParams
|
||||||
|
params?.isFullSpan = true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun contentSameAs(other: TextItem) = text == other.text
|
||||||
|
override fun itemSameAs(other: TextItem) = contentSameAs(other)
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
package com.topjohnwu.magisk.model.entity.recycler
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.R
|
||||||
|
import com.topjohnwu.magisk.databinding.ComparableRvItem
|
||||||
|
import com.topjohnwu.magisk.ui.theme.Theme
|
||||||
|
|
||||||
|
class ThemeItem(val theme: Theme) : ComparableRvItem<ThemeItem>() {
|
||||||
|
|
||||||
|
override val layoutRes = R.layout.item_theme
|
||||||
|
|
||||||
|
override fun contentSameAs(other: ThemeItem) = itemSameAs(other)
|
||||||
|
override fun itemSameAs(other: ThemeItem) = theme == other.theme
|
||||||
|
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user