Merge branch 'md2'

This commit is contained in:
topjohnwu 2020-01-22 14:55:06 +08:00
commit c1dad11cb3
407 changed files with 14350 additions and 5781 deletions

1
.gitattributes vendored
View File

@ -17,3 +17,4 @@ tools/** binary
*.apk binary *.apk binary
*.png binary *.png binary
*.jpg binary *.jpg binary
*.ttf binary

View File

@ -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'
} }

View File

@ -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

View File

@ -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 -->

View File

@ -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 {

View File

@ -1,7 +0,0 @@
package a;
import com.topjohnwu.magisk.ui.MainActivity;
public class b extends MainActivity {
/* stub */
}

View File

@ -1,7 +0,0 @@
package a;
import com.topjohnwu.magisk.ui.SplashActivity;
public class c extends SplashActivity {
/* stub */
}

View File

@ -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);
}
}

View File

@ -1,7 +0,0 @@
package a;
import com.topjohnwu.magisk.ui.flash.FlashActivity;
public class f extends FlashActivity {
/* stub */
}

View File

@ -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) {

View File

@ -1,7 +0,0 @@
package a;
import com.topjohnwu.magisk.model.receiver.GeneralReceiver;
public class h extends GeneralReceiver {
/* stub */
}

View File

@ -1,7 +0,0 @@
package a;
import com.topjohnwu.magisk.model.download.DownloadService;
public class j extends DownloadService {
/* stub */
}

View File

@ -1,7 +0,0 @@
package a;
import com.topjohnwu.magisk.ui.surequest.SuRequestActivity;
public class m extends SuRequestActivity {
/* stub */
}

View 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) {}
}
}

View File

@ -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();
}
}

View File

@ -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)
}
}
}

View File

@ -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()
}

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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

View File

@ -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,39 +111,67 @@ 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
Utils.ensureDownloadPath(downloadPath) ?: get<Context>().getExternalFilesDir(null)!! get() =
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()

View File

@ -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"

View File

@ -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

View File

@ -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

View File

@ -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 {

View File

@ -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()
} }

View File

@ -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()

View File

@ -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 {
@ -93,7 +56,7 @@ abstract class BaseActivity<ViewModel : BaseViewModel, Binding : ViewDataBinding
} }
override fun onRequestPermissionsResult( override fun onRequestPermissionsResult(
requestCode: Int, permissions: Array<out String>, grantResults: IntArray) { requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
var success = true var success = true
for (res in grantResults) { for (res in grantResults) {
if (res != PackageManager.PERMISSION_GRANTED) { if (res != PackageManager.PERMISSION_GRANTED) {
@ -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)
} }
} }

View File

@ -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 {

View File

@ -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 {

View File

@ -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

View File

@ -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) =
PendingIntent.getActivity(context, nextInt(), intent, PendingIntent.FLAG_ONE_SHOT) setContentIntent(
.let { setContentIntent(it) } PendingIntent.getActivity(context, nextInt(), intent, PendingIntent.FLAG_ONE_SHOT)
)
@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) =
PendingIntent.getActivity(context, nextInt(), intent, PendingIntent.FLAG_ONE_SHOT) addAction(icon, getString(title),
.let { addAction(icon, getString(title), it) } PendingIntent.getActivity(context, nextInt(), intent, PendingIntent.FLAG_ONE_SHOT)
)
// --- // ---

View File

@ -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

View 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
@ -41,4 +41,4 @@ fun InputStream.toModule(file: File, installer: InputStream) {
entry = zin.nextEntry entry = zin.nextEntry
} }
} }
} }

View 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

View File

@ -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,12 +61,12 @@ 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()
.doOnSuccess { stream.toModule(subject.file, it.byteStream()) } .doOnSuccess { stream.toModule(subject.file, it.byteStream()) }
.ignoreElement() .ignoreElement()
else -> Completable.fromAction { stream.writeTo(subject.file) } else -> Completable.fromAction { stream.writeTo(subject.file) }
} }
}.doOnComplete { }.doOnComplete {
@ -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
}
} }
} }

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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() {

View File

@ -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() {

View File

@ -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(

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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"

View File

@ -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

View File

@ -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()
}
}

View File

@ -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

View 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

View 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)!!
}

View File

@ -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

View File

@ -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")

View File

@ -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

View File

@ -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

View File

@ -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
@ -41,4 +41,4 @@ class ProgressInputStream(
} }
return sz return sz
} }
} }

View File

@ -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

View File

@ -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
}
}

View File

@ -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
)
} }
} }

View File

@ -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

View File

@ -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))

View File

@ -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()
) )

View 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>
}

View File

@ -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

View File

@ -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
@ -78,4 +78,4 @@ interface GithubApiServices {
@Query("sort") sort: String = "pushed", @Query("sort") sort: String = "pushed",
@Query("per_page") count: Int = 100): Flowable<Result<List<GithubRepoInfo>>> @Query("per_page") count: Int = 100): Flowable<Result<List<GithubRepoInfo>>>
} }

View File

@ -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

View File

@ -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) }
}
} }

View File

@ -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

View File

@ -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
@ -12,4 +12,4 @@ class StringRepository(
fun getMetadata(repo: Repo) = api.fetchModuleInfo(repo.id, "module.prop") fun getMetadata(repo: Repo) = api.fetchModuleInfo(repo.id, "module.prop")
fun getReadme(repo: Repo) = api.fetchModuleInfo(repo.id, "README.md") fun getReadme(repo: Repo) = api.fetchModuleInfo(repo.id, "README.md")
} }

View File

@ -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 {

View File

@ -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()) }
} }

View File

@ -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

View File

@ -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())
} }

View File

@ -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) })

View File

@ -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() }

View File

@ -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)

View File

@ -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
@ -11,4 +11,6 @@ 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()

View File

@ -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

View File

@ -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
)
}

View File

@ -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)
@ -11,4 +15,9 @@ fun View.addOnGlobalLayoutListener(oneShot: Boolean = false, callback: () -> Uni
if (oneShot) viewTreeObserver.removeOnGlobalLayoutListener(this) if (oneShot) viewTreeObserver.removeOnGlobalLayoutListener(this)
callback() callback()
} }
}) })
fun ViewGroup.startAnimations() {
val transition = AutoTransition().setInterpolator(FastOutSlowInInterpolator()).setDuration(400)
TransitionManager.beginDelayedTransition(this, transition)
}

View File

@ -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

View File

@ -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()
}

View File

@ -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()
} }

View File

@ -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()
}
}
}

View File

@ -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>
)

View File

@ -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,

View File

@ -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
@ -103,4 +103,4 @@ sealed class DownloadSubject : Parcelable {
} }
} }

View File

@ -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) {
@ -23,4 +23,8 @@ 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
} }

View File

@ -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)
val itemsCheckedPercent = Observer(itemsChecked) {
(itemsChecked.value.toFloat() / items.size * 100).roundToInt()
}
private val itemsProcess get() = items.filterIsInstance<HideProcessRvItem>() /** [toggle] depends on this functionality */
private val isHidden get() = itemsChecked.value == items.size
private val currentState
get() = when (itemsProcess.count { it.isHidden.value }) {
items.size -> IndeterminateState.CHECKED
in 1 until items.size -> IndeterminateState.INDETERMINATE
else -> IndeterminateState.UNCHECKED
}
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
}

View File

@ -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
}

View File

@ -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
} }

View File

@ -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) }
init { object InstallModule : ComparableRvItem<InstallModule>() {
isChecked.addOnPropertyChangedCallback { override val layoutRes = R.layout.item_module_download
when (it) {
true -> { override fun onBindingBound(binding: ViewDataBinding) {
item.enable = true super.onBindingBound(binding)
notice(R.string.disable_file_removed) val params = binding.root.layoutParams as? StaggeredGridLayoutManager.LayoutParams
} params?.isFullSpan = true
false -> { }
item.enable = false
notice(R.string.disable_file_created) 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)
} }
isDeletable.addOnPropertyChangedCallback { var icon = _icon
when (it) { @Bindable get
true -> { set(value) {
item.remove = true field = value
notice(R.string.remove_file_created) notifyChange(BR.icon)
}
false -> {
item.remove = false
notice(R.string.remove_file_deleted)
}
}
} }
when { var hasButton = button != 0 || icon != 0
item.updated -> notice(R.string.update_file_created) @Bindable get
item.remove -> notice(R.string.remove_file_created) 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 {
isUpdate = true
} }
} }
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)
}
@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
} }
override fun contentSameAs(other: ModuleRvItem): Boolean = item.version == other.item.version 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 itemSameAs(other: RepoRvItem): Boolean = item.id == other.item.id override fun addOnPropertyChangedCallback(callback: Observable.OnPropertyChangedCallback?) {
} list.add(callback ?: return)
}
fun notifyChange(id: Int) = list.notifyChange(this, id)
}

View File

@ -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
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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)
}

View File

@ -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