Commit 6ccbc272 authored by Viktor De Pasquale's avatar Viktor De Pasquale

Merge remote-tracking branch 'john/master' into feature/redesign

# Conflicts:
#	app/build.gradle
#	app/src/main/AndroidManifest.xml
#	app/src/main/java/com/topjohnwu/magisk/model/events/ViewEvents.kt
#	app/src/main/java/com/topjohnwu/magisk/model/navigation/MagiskNavigationEvent.kt
#	app/src/main/java/com/topjohnwu/magisk/ui/SplashActivity.kt
#	app/src/main/java/com/topjohnwu/magisk/ui/settings/SettingsFragment.kt
#	app/src/main/java/com/topjohnwu/magisk/view/MagiskDialog.kt
#	app/src/main/res/layout/dialog_magisk_base.xml
parents 0eb28c32 7342fc23
......@@ -33,7 +33,6 @@ Furthermore, Magisk provides a **Systemless Interface** to alter the system (or
Default string resources for Magisk Manager are scattered throughout
- `app/src/main/res/values/strings.xml`
- `stub/src/main/res/values/strings.xml`
- `shared/src/main/res/values/strings.xml`
Translate each and place them in the respective locations (`<module>/src/main/res/values-<lang>/strings.xml`).
......
......@@ -60,14 +60,26 @@ dependencies {
implementation 'com.github.topjohnwu:jtar:1.0.0'
implementation 'com.jakewharton.timber:timber:4.7.1'
implementation 'com.github.skoumalcz:teanity:0.3.3'
implementation 'com.ncapdevi:frag-nav:3.2.0'
implementation 'com.github.pwittchen:reactivenetwork-rx2:3.0.6'
def vMarkwon = '3.1.0'
implementation "ru.noties.markwon:core:${vMarkwon}"
implementation "ru.noties.markwon:html:${vMarkwon}"
implementation "ru.noties.markwon:image-svg:${vMarkwon}"
implementation 'io.reactivex.rxjava2:rxjava:2.2.13'
implementation 'io.reactivex.rxjava2:rxkotlin:2.4.0'
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
implementation "org.jetbrains.kotlin:kotlin-stdlib:${vKotlin}"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${vKotlin}"
def vBAdapt = '3.1.1'
def bindingAdapter = 'me.tatarka.bindingcollectionadapter2:bindingcollectionadapter'
implementation "${bindingAdapter}:${vBAdapt}"
implementation "${bindingAdapter}-recyclerview:${vBAdapt}"
def vMarkwon = '4.1.1'
implementation "io.noties.markwon:core:${vMarkwon}"
implementation "io.noties.markwon:html:${vMarkwon}"
implementation "io.noties.markwon:image:${vMarkwon}"
implementation 'com.caverock:androidsvg:1.4'
def vLibsu = '2.5.1'
implementation "com.github.topjohnwu.libsu:core:${vLibsu}"
......@@ -78,13 +90,13 @@ dependencies {
implementation "org.koin:koin-android:${vKoin}"
implementation "org.koin:koin-androidx-viewmodel:${vKoin}"
def vRetrofit = '2.6.1'
def vRetrofit = '2.6.2'
implementation "com.squareup.retrofit2:retrofit:${vRetrofit}"
implementation "com.squareup.retrofit2:converter-moshi:${vRetrofit}"
implementation "com.squareup.retrofit2:converter-scalars:${vRetrofit}"
implementation "com.squareup.retrofit2:adapter-rxjava2:${vRetrofit}"
def vOkHttp = '3.12.2'
def vOkHttp = '3.12.6'
implementation "com.squareup.okhttp3:okhttp:${vOkHttp}"
implementation "com.squareup.okhttp3:logging-interceptor:${vOkHttp}"
......@@ -100,19 +112,21 @@ dependencies {
replacedBy('com.github.topjohnwu:room-runtime')
}
}
def vRoom = "2.1.0"
def vRoom = "2.2.0"
implementation "com.github.topjohnwu:room-runtime:${vRoom}"
kapt "androidx.room:room-compiler:${vRoom}"
implementation "org.jetbrains.kotlin:kotlin-stdlib:${kotlinVer}"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${kotlinVer}"
def vNav = "2.1.0"
implementation "androidx.navigation:navigation-fragment-ktx:$vNav"
implementation "androidx.navigation:navigation-ui-ktx:$vNav"
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.browser:browser:1.0.0'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0-alpha03'
implementation 'androidx.preference:preference:1.1.0'
implementation 'androidx.recyclerview:recyclerview:1.1.0-beta04'
implementation 'androidx.recyclerview:recyclerview:1.0.0'
implementation 'androidx.work:work-runtime:2.2.0'
implementation 'androidx.transition:transition:1.2.0-rc01'
implementation 'androidx.transition:transition:1.2.0'
implementation 'androidx.multidex:multidex:2.0.1'
implementation 'androidx.core:core-ktx:1.1.0'
implementation 'com.google.android.material:material:1.1.0-beta01'
}
......@@ -29,7 +29,7 @@
}
# DelegateWorker
-keep,allowobfuscation class * extends com.topjohnwu.magisk.model.worker.DelegateWorker
-keep,allowobfuscation class * extends com.topjohnwu.magisk.base.DelegateWorker
# BootSigner
-keepclassmembers class com.topjohnwu.signing.BootSigner { *; }
......
<?xml version="1.0" encoding="utf-8"?>
<!--
** Special Requirements **
This AndroidManifest.xml will be copied into the stub
APK to allow APK delegation. This is why these special
requirements exist.
* Class names *
Class names a.a, a.c, a.e should not be changed as they are used
externally. All other class names can be changed.
* Resource IDs *
All resource IDs referred in AndroidManifest.xml is required to be
included into the "shared" module to make the ID match with stub.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.topjohnwu.magisk">
......@@ -11,25 +29,17 @@
<application
android:name="a.e"
android:appComponentFactory="a.a"
android:allowBackup="true"
android:theme="@style/MagiskTheme"
android:usesCleartextTraffic="true"
tools:ignore="UnusedAttribute,GoogleAppIndexingWarning">
tools:ignore="UnusedAttribute,GoogleAppIndexingWarning"
tools:replace="android:appComponentFactory">
<!-- Activities -->
<!-- Splash -->
<activity
android:name="a.b"
android:configChanges="orientation|screenSize"
android:exported="true" />
<activity
android:name="a.i"
android:exported="true"
android:theme="@style/Foundation.Default" />
<activity
android:name="a.c"
android:configChanges="orientation|screenSize"
android:exported="true"
android:theme="@style/SplashTheme">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
......@@ -37,6 +47,18 @@
</intent-filter>
</activity>
<!-- Main -->
<activity
android:name="a.b"
android:configChanges="orientation|screenSize"
android:exported="true" />
<activity
android:name="a.i"
android:exported="true"
android:theme="@style/Foundation.Default" />
<activity-alias
android:name="a.s"
android:targetActivity="a.c">
......@@ -46,11 +68,12 @@
</intent-filter>
</activity-alias>
<!-- Flashing -->
<activity
android:name="a.f"
android:configChanges="keyboardHidden|orientation|screenSize"
android:screenOrientation="nosensor"
android:theme="@style/MagiskTheme.Flashing" />
android:screenOrientation="nosensor" />
<!-- Superuser -->
......@@ -58,8 +81,7 @@
android:name="a.m"
android:directBootAware="true"
android:excludeFromRecents="true"
android:exported="false"
android:theme="@style/MagiskTheme.SU" />
android:exported="false" />
<!-- Receiver -->
......@@ -78,9 +100,10 @@
</intent-filter>
</receiver>
<!-- Service -->
<!-- DownloadService -->
<service android:name="a.j"
<service
android:name="a.j"
android:exported="false" />
<!-- Hardcode GMS version -->
......
package a;
import androidx.annotation.Keep;
import androidx.core.app.AppComponentFactory;
import com.topjohnwu.magisk.utils.PatchAPK;
import com.topjohnwu.signing.BootSigner;
import androidx.annotation.Keep;
@Keep
public class a extends BootSigner {
public class a extends AppComponentFactory {
public static boolean patchAPK(String in, String out, String pkg) {
return PatchAPK.patch(in, out, pkg);
}
public static void main(String[] args) throws Exception {
BootSigner.main(args);
}
}
......@@ -6,7 +6,8 @@ import androidx.annotation.NonNull;
import androidx.work.Worker;
import androidx.work.WorkerParameters;
import com.topjohnwu.magisk.model.worker.DelegateWorker;
import com.topjohnwu.magisk.base.DelegateWorker;
import com.topjohnwu.magisk.utils.ResourceMgrKt;
import java.lang.reflect.ParameterizedType;
......@@ -18,7 +19,7 @@ public abstract class w<T extends DelegateWorker> extends Worker {
@SuppressWarnings("unchecked")
w(@NonNull Context context, @NonNull WorkerParameters workerParams) {
super(context, workerParams);
super(ResourceMgrKt.wrap(context, false), workerParams);
try {
base = ((Class<T>) ((ParameterizedType) getClass().getGenericSuperclass())
.getActualTypeArguments()[0]).newInstance();
......
......@@ -13,9 +13,11 @@ import com.topjohnwu.magisk.data.database.RepoDatabase_Impl
import com.topjohnwu.magisk.di.ActivityTracker
import com.topjohnwu.magisk.di.koinModules
import com.topjohnwu.magisk.extensions.get
import com.topjohnwu.magisk.net.Networking
import com.topjohnwu.magisk.utils.LocaleManager
import com.topjohnwu.magisk.utils.RootUtils
import com.topjohnwu.magisk.extensions.unwrap
import com.topjohnwu.magisk.utils.ResourceMgr
import com.topjohnwu.magisk.utils.RootInit
import com.topjohnwu.magisk.utils.isRunningAsStub
import com.topjohnwu.magisk.utils.wrap
import com.topjohnwu.superuser.Shell
import org.koin.android.ext.koin.androidContext
import org.koin.core.context.startKoin
......@@ -27,7 +29,7 @@ open class App : Application() {
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)
Shell.Config.setFlags(Shell.FLAG_MOUNT_MASTER or Shell.FLAG_USE_MAGISK_BUSYBOX)
Shell.Config.verboseLogging(BuildConfig.DEBUG)
Shell.Config.addInitializers(RootUtils::class.java)
Shell.Config.addInitializers(RootInit::class.java)
Shell.Config.setTimeout(2)
Room.setFactory {
when (it) {
......@@ -39,24 +41,42 @@ open class App : Application() {
}
override fun attachBaseContext(base: Context) {
super.attachBaseContext(base)
// Basic setup
if (BuildConfig.DEBUG)
MultiDex.install(base)
Timber.plant(Timber.DebugTree())
// Some context magic
val app: Application
val impl: Context
if (base is Application) {
isRunningAsStub = true
app = base
impl = base.baseContext
} else {
app = this
impl = base
}
ResourceMgr.init(impl)
super.attachBaseContext(impl.wrap())
// Normal startup
startKoin {
androidContext(this@App)
androidContext(baseContext)
modules(koinModules)
}
ResourceMgr.reload()
app.registerActivityLifecycleCallbacks(get<ActivityTracker>())
}
registerActivityLifecycleCallbacks(get<ActivityTracker>())
Networking.init(base)
LocaleManager.setLocale(this)
// This is required as some platforms expect ContextImpl
override fun getBaseContext(): Context {
return super.getBaseContext().unwrap()
}
override fun onConfigurationChanged(newConfig: Configuration) {
ResourceMgr.reload(newConfig)
if (!isRunningAsStub)
super.onConfigurationChanged(newConfig)
LocaleManager.setLocale(this)
}
}
......@@ -13,8 +13,8 @@ object Const {
// Versions
const val SNET_EXT_VER = 13
const val SNET_REVISION = "5adbc435ce93ded953c30ebe587edfd50b5503bc"
const val BOOTCTL_REVISION = "9c5dfc1b8245c0b5b524901ef0ff0f8335757b77"
const val SNET_REVISION = "a6c47f86f10b310358afa9dbe837037dd5d561df"
const val BOOTCTL_REVISION = "a6c47f86f10b310358afa9dbe837037dd5d561df"
// Misc
const val ANDROID_MANIFEST = "AndroidManifest.xml"
......
package com.topjohnwu.magisk.base
import android.Manifest
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.content.res.Configuration
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
import androidx.collection.SparseArrayCompat
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.databinding.DataBindingUtil
import androidx.databinding.ViewDataBinding
import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.Config
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.base.viewmodel.BaseViewModel
import com.topjohnwu.magisk.extensions.set
import com.topjohnwu.magisk.model.events.EventHandler
import com.topjohnwu.magisk.model.permissions.PermissionRequestBuilder
import com.topjohnwu.magisk.utils.currentLocale
import com.topjohnwu.magisk.utils.wrap
import kotlin.random.Random
typealias RequestCallback = BaseActivity<*, *>.(Int, Intent?) -> Unit
abstract class BaseActivity<ViewModel : BaseViewModel, Binding : ViewDataBinding> :
AppCompatActivity(), EventHandler {
protected lateinit var binding: Binding
protected abstract val layoutRes: Int
protected abstract val viewModel: ViewModel
protected open val themeRes: Int = R.style.MagiskTheme
protected open val snackbarView get() = binding.root
private val resultCallbacks by lazy { SparseArrayCompat<RequestCallback>() }
init {
val theme = if (Config.darkTheme) {
AppCompatDelegate.MODE_NIGHT_YES
} else {
AppCompatDelegate.MODE_NIGHT_NO
}
AppCompatDelegate.setDefaultNightMode(theme)
}
override fun applyOverrideConfiguration(config: Configuration?) {
// Force applying our preferred local
config?.setLocale(currentLocale)
super.applyOverrideConfiguration(config)
}
override fun attachBaseContext(base: Context) {
super.attachBaseContext(base.wrap(false))
}
override fun onCreate(savedInstanceState: Bundle?) {
setTheme(themeRes)
super.onCreate(savedInstanceState)
viewModel.viewEvents.observe(this, viewEventObserver)
binding = DataBindingUtil.setContentView<Binding>(this, layoutRes).apply {
setVariable(BR.viewModel, viewModel)
lifecycleOwner = this@BaseActivity
}
}
fun withPermissions(vararg permissions: String, builder: PermissionRequestBuilder.() -> Unit) {
val request = PermissionRequestBuilder().apply(builder).build()
val ungranted = permissions.filter {
ContextCompat.checkSelfPermission(this, it) != PackageManager.PERMISSION_GRANTED
}
if (ungranted.isEmpty()) {
request.onSuccess()
} else {
val requestCode = Random.nextInt(256, 512)
resultCallbacks[requestCode] = { result, _ ->
if (result > 0)
request.onSuccess()
else
request.onFailure()
}
ActivityCompat.requestPermissions(this, ungranted.toTypedArray(), requestCode)
}
}
fun withExternalRW(builder: PermissionRequestBuilder.() -> Unit) {
withPermissions(Manifest.permission.WRITE_EXTERNAL_STORAGE, builder = builder)
}
override fun onRequestPermissionsResult(
requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
var success = true
for (res in grantResults) {
if (res != PackageManager.PERMISSION_GRANTED) {
success = false
break
}
}
resultCallbacks[requestCode]?.apply {
resultCallbacks.remove(requestCode)
invoke(this@BaseActivity, if (success) 1 else -1, null)
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
resultCallbacks[requestCode]?.apply {
resultCallbacks.remove(requestCode)
invoke(this@BaseActivity, resultCode, data)
}
}
fun startActivityForResult(intent: Intent, requestCode: Int, listener: RequestCallback) {
resultCallbacks[requestCode] = listener
startActivityForResult(intent, requestCode)
}
}
package com.topjohnwu.magisk.base
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.CallSuper
import androidx.databinding.DataBindingUtil
import androidx.databinding.ViewDataBinding
import androidx.fragment.app.Fragment
import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.base.viewmodel.BaseViewModel
import com.topjohnwu.magisk.model.events.EventHandler
import com.topjohnwu.magisk.model.events.ViewEvent
abstract class BaseFragment<ViewModel : BaseViewModel, Binding : ViewDataBinding> :
Fragment(), EventHandler {
protected val activity get() = requireActivity() as BaseActivity<*, *>
protected lateinit var binding: Binding
protected abstract val layoutRes: Int
protected abstract val viewModel: ViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel.viewEvents.observe(this, viewEventObserver)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = DataBindingUtil.inflate<Binding>(inflater, layoutRes, container, false).apply {
setVariable(BR.viewModel, viewModel)
lifecycleOwner = this@BaseFragment
}
return binding.root
}
@CallSuper
override fun onEventDispatched(event: ViewEvent) {
super.onEventDispatched(event)
activity.onEventDispatched(event)
}
open fun onBackPressed(): Boolean = false
}
package com.topjohnwu.magisk.ui.base
package com.topjohnwu.magisk.base
import android.annotation.SuppressLint
import android.content.SharedPreferences
import android.os.Build
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.children
import androidx.core.view.isVisible
import androidx.preference.*
import androidx.recyclerview.widget.RecyclerView
import com.topjohnwu.magisk.R
import org.koin.android.ext.android.inject
abstract class BasePreferenceFragment : PreferenceFragmentCompat(),
SharedPreferences.OnSharedPreferenceChangeListener {
protected val prefs: SharedPreferences by inject()
protected val activity get() = requireActivity() as MagiskActivity<*, *>
protected val activity get() = requireActivity() as BaseActivity<*, *>
override fun onCreateView(
inflater: LayoutInflater,
......@@ -35,28 +31,26 @@ abstract class BasePreferenceFragment : PreferenceFragmentCompat(),
super.onDestroyView()
}
override fun onCreateAdapter(preferenceScreen: PreferenceScreen): RecyclerView.Adapter<*> {
return object : PreferenceGroupAdapter(preferenceScreen) {
@SuppressLint("RestrictedApi")
override fun onBindViewHolder(holder: PreferenceViewHolder, position: Int) {
super.onBindViewHolder(holder, position)
when (val preference = getItem(position)) {
is PreferenceCategory -> setZeroPaddingToLayoutChildren(holder.itemView)
else -> holder.itemView.findViewById<View>(R.id.icon_frame)?.isVisible =
preference.icon != null
}
}
}
private fun setAllPreferencesToAvoidHavingExtraSpace(preference: Preference) {
preference.isIconSpaceReserved = false
if (preference is PreferenceGroup)
for (i in 0 until preference.preferenceCount)
setAllPreferencesToAvoidHavingExtraSpace(preference.getPreference(i))
}
private fun setZeroPaddingToLayoutChildren(view: View) {
(view as? ViewGroup)?.children?.forEach {
setZeroPaddingToLayoutChildren(it)
} ?: return
override fun setPreferenceScreen(preferenceScreen: PreferenceScreen?) {
if (preferenceScreen != null)
setAllPreferencesToAvoidHavingExtraSpace(preferenceScreen)
super.setPreferenceScreen(preferenceScreen)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1)
view.setPaddingRelative(0, view.paddingTop, view.paddingEnd, view.paddingBottom)
else
view.setPadding(0, view.paddingTop, view.paddingRight, view.paddingBottom)
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)
}
}
}
package com.topjohnwu.magisk.base
import android.content.BroadcastReceiver
import android.content.Context
import android.content.ContextWrapper
import android.content.Intent
import com.topjohnwu.magisk.utils.wrap
import org.koin.core.KoinComponent
abstract class BaseReceiver : BroadcastReceiver(), KoinComponent {
final override fun onReceive(context: Context, intent: Intent?) {
onReceive(context.wrap() as ContextWrapper, intent)
}
abstract fun onReceive(context: ContextWrapper, intent: Intent?)
}
package com.topjohnwu.magisk.base
import android.app.Service
import android.content.Context
import com.topjohnwu.magisk.utils.wrap
import org.koin.core.KoinComponent
abstract class BaseService : Service(), KoinComponent {
override fun attachBaseContext(base: Context) {
super.attachBaseContext(base.wrap())
}
}
package com.topjohnwu.magisk.model.worker
package com.topjohnwu.magisk.base
import android.content.Context
import android.net.Network
......
package com.topjohnwu.magisk.ui.base
package com.topjohnwu.magisk.base.viewmodel
import android.app.Activity
import com.github.pwittchen.reactivenetwork.library.rx2.ReactiveNetwork
import com.skoumal.teanity.extensions.doOnSubscribeUi
import com.skoumal.teanity.extensions.subscribeK
import com.skoumal.teanity.util.KObservableField
import com.skoumal.teanity.viewmodel.LoadingViewModel
import com.topjohnwu.magisk.extensions.doOnSubscribeUi
import com.topjohnwu.magisk.extensions.get
import com.topjohnwu.magisk.extensions.subscribeK
import com.topjohnwu.magisk.model.events.BackPressEvent
import com.topjohnwu.magisk.model.events.PermissionEvent
import com.topjohnwu.magisk.model.events.ViewActionEvent
import com.topjohnwu.magisk.utils.KObservableField
import io.reactivex.Observable
import io.reactivex.subjects.PublishSubject
abstract class MagiskViewModel(
abstract class BaseViewModel(
initialState: State = State.LOADING
) : LoadingViewModel(initialState) {
val isConnected = KObservableField(true)
val isConnected = KObservableField(false)
init {
ReactiveNetwork.observeNetworkConnectivity(get())
......
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
}
\ No newline at end of file
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)
}
}
\ No newline at end of file
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
}
\ No newline at end of file
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)
}
}
\ No newline at end of file
......@@ -19,10 +19,10 @@ interface GithubRawServices {
@GET("$MAGISK_FILES/master/beta.json")
fun fetchBetaUpdate(): Single<UpdateInfo>
@GET("$MAGISK_FILES/master/canary_builds/release.json")
@GET("$MAGISK_FILES/canary/release.json")
fun fetchCanaryUpdate(): Single<UpdateInfo>
@GET("$MAGISK_FILES/master/canary_builds/canary.json")
@GET("$MAGISK_FILES/canary/debug.json")
fun fetchCanaryDebugUpdate(): Single<UpdateInfo>
@GET
......
package com.topjohnwu.magisk.databinding
import android.view.View
import androidx.core.view.isGone
import androidx.core.view.isInvisible
import androidx.databinding.BindingAdapter
@BindingAdapter("gone")
fun setGone(view: View, gone: Boolean) {
view.isGone = gone
}
@BindingAdapter("invisible")
fun setInvisible(view: View, invisible: Boolean) {
view.isInvisible = invisible
}
@BindingAdapter("goneUnless")
fun setGoneUnless(view: View, goneUnless: Boolean) {
setGone(view, goneUnless.not())
}
@BindingAdapter("invisibleUnless")
fun setInvisibleUnless(view: View, invisibleUnless: Boolean) {
setInvisible(view, invisibleUnless.not())
}
package com.topjohnwu.magisk.databinding
import android.graphics.drawable.GradientDrawable
import android.graphics.drawable.InsetDrawable
import androidx.databinding.BindingAdapter
import androidx.recyclerview.widget.RecyclerView
import com.topjohnwu.magisk.extensions.startEndToLeftRight
import com.topjohnwu.magisk.extensions.toPx
import com.topjohnwu.magisk.utils.KItemDecoration
import kotlin.math.roundToInt
@BindingAdapter(
"dividerColor",
"dividerHorizontal",
"dividerSize",
"dividerAfterLast",
"dividerMarginStart",
"dividerMarginEnd",
"dividerMarginTop",
"dividerMarginBottom",
requireAll = false
)
fun setDivider(
view: RecyclerView,
color: Int,
horizontal: Boolean,
_size: Float,
_afterLast: Boolean?,
marginStartF: Float,
marginEndF: Float,
marginTopF: Float,
marginBottomF: Float
) {
val orientation = if (horizontal) RecyclerView.HORIZONTAL else RecyclerView.VERTICAL
val size = if (_size > 0) _size.roundToInt() else 1.toPx()
val (width, height) = if (horizontal) size to 1 else 1 to size
val afterLast = _afterLast ?: true
val marginStart = marginStartF.roundToInt()
val marginEnd = marginEndF.roundToInt()
val marginTop = marginTopF.roundToInt()
val marginBottom = marginBottomF.roundToInt()
val (marginLeft, marginRight) = view.context.startEndToLeftRight(marginStart, marginEnd)
val drawable = GradientDrawable().apply {
setSize(width, height)
shape = GradientDrawable.RECTANGLE
setColor(color)
}.let {
InsetDrawable(it, marginLeft, marginTop, marginRight, marginBottom)
}
val decoration = KItemDecoration(view.context, orientation)
.setDeco(drawable)
.apply { showAfterLast = afterLast }
view.addItemDecoration(decoration)
}
package com.topjohnwu.magisk.databinding
import androidx.databinding.ViewDataBinding
import me.tatarka.bindingcollectionadapter2.BindingRecyclerViewAdapter
open class BindingBoundAdapter : BindingRecyclerViewAdapter<RvItem>() {
override fun onBindBinding(binding: ViewDataBinding, variableId: Int, layoutRes: Int, position: Int, item: RvItem) {
super.onBindBinding(binding, variableId, layoutRes, position, item)
item.onBindingBound(binding)
}
}
\ No newline at end of file
package com.topjohnwu.magisk.databinding
import androidx.annotation.CallSuper
import androidx.databinding.ViewDataBinding
import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.utils.DiffObservableList
import me.tatarka.bindingcollectionadapter2.ItemBinding
abstract class RvItem {
abstract val layoutRes: Int
@CallSuper
open fun bind(binding: ItemBinding<*>) {
binding.set(BR.item, layoutRes)
}
/**
* This callback is useful if you want to manipulate your views directly.
* If you want to use this callback, you must set [me.tatarka.bindingcollectionadapter2.BindingRecyclerViewAdapter]
* on your RecyclerView and call it from there. You can use [BindingBoundAdapter] for your convenience.
*/
open fun onBindingBound(binding: ViewDataBinding) {}
}
abstract class ComparableRvItem<in T> : RvItem() {
abstract fun itemSameAs(other: T): Boolean
abstract fun contentSameAs(other: T): Boolean
@Suppress("UNCHECKED_CAST")
open fun genericItemSameAs(other: Any): Boolean = other::class == this::class && itemSameAs(other as T)
@Suppress("UNCHECKED_CAST")
open fun genericContentSameAs(other: Any): Boolean = other::class == this::class && contentSameAs(other as T)
companion object {
val callback = object : DiffObservableList.Callback<ComparableRvItem<*>> {
override fun areItemsTheSame(
oldItem: ComparableRvItem<*>,
newItem: ComparableRvItem<*>
) = oldItem.genericItemSameAs(newItem)
override fun areContentsTheSame(
oldItem: ComparableRvItem<*>,
newItem: ComparableRvItem<*>
) = oldItem.genericContentSameAs(newItem)
}
}
}
\ No newline at end of file
......@@ -7,7 +7,7 @@ import android.content.Context
import android.os.Build
import android.os.Bundle
import androidx.preference.PreferenceManager
import com.skoumal.teanity.rxbus.RxBus
import com.topjohnwu.magisk.utils.RxBus
import org.koin.core.qualifier.named
import org.koin.dsl.module
......
package com.topjohnwu.magisk.di
import android.content.Context
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.Moshi
import com.topjohnwu.magisk.BuildConfig
import com.topjohnwu.magisk.Const
import com.topjohnwu.magisk.data.network.GithubApiServices
import com.topjohnwu.magisk.data.network.GithubRawServices
import com.topjohnwu.magisk.net.Networking
import com.topjohnwu.magisk.net.NoSSLv3SocketFactory
import io.noties.markwon.Markwon
import io.noties.markwon.html.HtmlPlugin
import io.noties.markwon.image.ImagesPlugin
import io.noties.markwon.image.network.OkHttpNetworkSchemeHandler
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import org.koin.dsl.module
......@@ -16,14 +23,15 @@ import retrofit2.converter.scalars.ScalarsConverterFactory
import se.ansman.kotshi.KotshiJsonAdapterFactory
val networkingModule = module {
single { createOkHttpClient() }
single { createMoshiConverterFactory() }
single { createRetrofit(get(), get()) }
single { createOkHttpClient(get()) }
single { createRetrofit(get()) }
single { createApiService<GithubRawServices>(get(), Const.Url.GITHUB_RAW_URL) }
single { createApiService<GithubApiServices>(get(), Const.Url.GITHUB_API_URL) }
single { createMarkwon(get(), get()) }
}
fun createOkHttpClient(): OkHttpClient {
@Suppress("DEPRECATION")
fun createOkHttpClient(context: Context): OkHttpClient {
val builder = OkHttpClient.Builder()
if (BuildConfig.DEBUG) {
......@@ -33,6 +41,10 @@ fun createOkHttpClient(): OkHttpClient {
builder.addInterceptor(httpLoggingInterceptor)
}
if (!Networking.init(context)) {
builder.sslSocketFactory(NoSSLv3SocketFactory())
}
return builder.build()
}
......@@ -43,13 +55,10 @@ fun createMoshiConverterFactory(): MoshiConverterFactory {
return MoshiConverterFactory.create(moshi)
}
fun createRetrofit(
okHttpClient: OkHttpClient,
converterFactory: MoshiConverterFactory
): Retrofit.Builder {
fun createRetrofit(okHttpClient: OkHttpClient): Retrofit.Builder {
return Retrofit.Builder()
.addConverterFactory(ScalarsConverterFactory.create())
.addConverterFactory(converterFactory)
.addConverterFactory(createMoshiConverterFactory())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(okHttpClient)
}
......@@ -63,3 +72,12 @@ inline fun <reified T> createApiService(retrofitBuilder: Retrofit.Builder, baseU
.build()
.create(T::class.java)
}
fun createMarkwon(context: Context, okHttpClient: OkHttpClient): Markwon {
return Markwon.builder(context)
.usePlugin(HtmlPlugin.create())
.usePlugin(ImagesPlugin.create {
it.addSchemeHandler(OkHttpNetworkSchemeHandler.create(okHttpClient))
})
.build()
}
package com.topjohnwu.magisk.extensions
import androidx.databinding.Observable
import androidx.databinding.ObservableBoolean
import androidx.databinding.ObservableField
import androidx.databinding.ObservableInt
fun <T> ObservableField<T>.addOnPropertyChangedCallback(
removeAfterChanged: Boolean = false,
callback: (T?) -> Unit
) {
addOnPropertyChangedCallback(object : Observable.OnPropertyChangedCallback() {
override fun onPropertyChanged(sender: Observable?, propertyId: Int) {
callback(get())
if (removeAfterChanged) removeOnPropertyChangedCallback(this)
}
})
}
fun ObservableInt.addOnPropertyChangedCallback(
removeAfterChanged: Boolean = false,
callback: (Int) -> Unit
) {
addOnPropertyChangedCallback(object : Observable.OnPropertyChangedCallback() {
override fun onPropertyChanged(sender: Observable?, propertyId: Int) {
callback(get())
if (removeAfterChanged) removeOnPropertyChangedCallback(this)
}
})
}
fun ObservableBoolean.addOnPropertyChangedCallback(
removeAfterChanged: Boolean = false,
callback: (Boolean) -> Unit
) {
addOnPropertyChangedCallback(object : Observable.OnPropertyChangedCallback() {
override fun onPropertyChanged(sender: Observable?, propertyId: Int) {
callback(get())
if (removeAfterChanged) removeOnPropertyChangedCallback(this)
}
})
}
inline fun <T> ObservableField<T>.update(block: (T?) -> Unit) {
set(get().apply(block))
}
inline fun <T> ObservableField<T>.updateNonNull(block: (T) -> Unit) {
update {
it ?: return@update
block(it)
}
}
inline fun ObservableInt.update(block: (Int) -> Unit) {
set(get().apply(block))
}
\ No newline at end of file
package com.topjohnwu.magisk.extensions
import android.content.res.Resources
import kotlin.math.ceil
import kotlin.math.roundToInt
fun Int.toDp(): Int = ceil(this / Resources.getSystem().displayMetrics.density).roundToInt()
fun Int.toPx(): Int = (this * Resources.getSystem().displayMetrics.density).roundToInt()
package com.topjohnwu.magisk.extensions
import android.os.Handler
import android.os.Looper
fun ui(body: () -> Unit) = Handler(Looper.getMainLooper()).post(body)
\ No newline at end of file
package com.topjohnwu.magisk.extensions
import androidx.databinding.ObservableField
import com.topjohnwu.magisk.utils.KObservableField
import io.reactivex.*
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposables
import io.reactivex.functions.BiFunction
import io.reactivex.schedulers.Schedulers
import androidx.databinding.Observable as BindingObservable
fun <T> Observable<T>.applySchedulers(
subscribeOn: Scheduler = Schedulers.io(),
observeOn: Scheduler = AndroidSchedulers.mainThread()
): Observable<T> = this.subscribeOn(subscribeOn).observeOn(observeOn)
fun <T> Flowable<T>.applySchedulers(
subscribeOn: Scheduler = Schedulers.io(),
observeOn: Scheduler = AndroidSchedulers.mainThread()
): Flowable<T> = this.subscribeOn(subscribeOn).observeOn(observeOn)
fun <T> Single<T>.applySchedulers(
subscribeOn: Scheduler = Schedulers.io(),
observeOn: Scheduler = AndroidSchedulers.mainThread()
): Single<T> = this.subscribeOn(subscribeOn).observeOn(observeOn)
fun <T> Maybe<T>.applySchedulers(
subscribeOn: Scheduler = Schedulers.io(),
observeOn: Scheduler = AndroidSchedulers.mainThread()
): Maybe<T> = this.subscribeOn(subscribeOn).observeOn(observeOn)
fun Completable.applySchedulers(
subscribeOn: Scheduler = Schedulers.io(),
observeOn: Scheduler = AndroidSchedulers.mainThread()
): Completable = this.subscribeOn(subscribeOn).observeOn(observeOn)
/*=== ALIASES FOR OBSERVABLES ===*/
typealias OnCompleteListener = () -> Unit
typealias OnSuccessListener<T> = (T) -> Unit
typealias OnErrorListener = (Throwable) -> Unit
/*=== ALIASES FOR OBSERVABLES ===*/
fun <T> Observable<T>.subscribeK(
onError: OnErrorListener = { it.printStackTrace() },
onComplete: OnCompleteListener = {},
onNext: OnSuccessListener<T> = {}
) = applySchedulers()
.subscribe(onNext, onError, onComplete)
fun <T> Single<T>.subscribeK(
onError: OnErrorListener = { it.printStackTrace() },
onNext: OnSuccessListener<T> = {}
) = applySchedulers()
.subscribe(onNext, onError)
fun <T> Maybe<T>.subscribeK(
onError: OnErrorListener = { it.printStackTrace() },
onComplete: OnCompleteListener = {},
onNext: OnSuccessListener<T> = {}
) = applySchedulers()
.subscribe(onNext, onError, onComplete)
fun <T> Flowable<T>.subscribeK(
onError: OnErrorListener = { it.printStackTrace() },
onComplete: OnCompleteListener = {},
onNext: OnSuccessListener<T> = {}
) = applySchedulers()
.subscribe(onNext, onError, onComplete)
fun Completable.subscribeK(
onError: OnErrorListener = { it.printStackTrace() },
onComplete: OnCompleteListener = {}
) = applySchedulers()
.subscribe(onComplete, onError)
fun <T> Observable<out T>.updateBy(
field: KObservableField<T?>
) = doOnNextUi { field.value = it }
.doOnErrorUi { field.value = null }
fun <T> Single<out T>.updateBy(
field: KObservableField<T?>
) = doOnSuccessUi { field.value = it }
.doOnErrorUi { field.value = null }
fun <T> Maybe<out T>.updateBy(
field: KObservableField<T?>
) = doOnSuccessUi { field.value = it }
.doOnErrorUi { field.value = null }
.doOnComplete { field.value = field.value }
fun <T> Flowable<out T>.updateBy(
field: KObservableField<T?>
) = doOnNextUi { field.value = it }
.doOnErrorUi { field.value = null }
fun Completable.updateBy(
field: KObservableField<Boolean>
) = doOnCompleteUi { field.value = true }
.doOnErrorUi { field.value = false }
fun <T> Observable<T>.doOnSubscribeUi(body: () -> Unit) =
doOnSubscribe { ui { body() } }
fun <T> Single<T>.doOnSubscribeUi(body: () -> Unit) =
doOnSubscribe { ui { body() } }
fun <T> Maybe<T>.doOnSubscribeUi(body: () -> Unit) =
doOnSubscribe { ui { body() } }
fun <T> Flowable<T>.doOnSubscribeUi(body: () -> Unit) =
doOnSubscribe { ui { body() } }
fun Completable.doOnSubscribeUi(body: () -> Unit) =
doOnSubscribe { ui { body() } }
fun <T> Observable<T>.doOnErrorUi(body: (Throwable) -> Unit) =
doOnError { ui { body(it) } }
fun <T> Single<T>.doOnErrorUi(body: (Throwable) -> Unit) =
doOnError { ui { body(it) } }
fun <T> Maybe<T>.doOnErrorUi(body: (Throwable) -> Unit) =
doOnError { ui { body(it) } }
fun <T> Flowable<T>.doOnErrorUi(body: (Throwable) -> Unit) =
doOnError { ui { body(it) } }
fun Completable.doOnErrorUi(body: (Throwable) -> Unit) =
doOnError { ui { body(it) } }
fun <T> Observable<T>.doOnNextUi(body: (T) -> Unit) =
doOnNext { ui { body(it) } }
fun <T> Flowable<T>.doOnNextUi(body: (T) -> Unit) =
doOnNext { ui { body(it) } }
fun <T> Single<T>.doOnSuccessUi(body: (T) -> Unit) =
doOnSuccess { ui { body(it) } }
fun <T> Maybe<T>.doOnSuccessUi(body: (T) -> Unit) =
doOnSuccess { ui { body(it) } }
fun <T> Maybe<T>.doOnCompleteUi(body: () -> Unit) =
doOnComplete { ui { body() } }
fun Completable.doOnCompleteUi(body: () -> Unit) =
doOnComplete { ui { body() } }
fun <T, R> Observable<List<T>>.mapList(
transformer: (T) -> R
) = flatMapIterable { it }
.map(transformer)
.toList()
fun <T, R> Single<List<T>>.mapList(
transformer: (T) -> R
) = flattenAsFlowable { it }
.map(transformer)
.toList()
fun <T, R> Maybe<List<T>>.mapList(
transformer: (T) -> R
) = flattenAsFlowable { it }
.map(transformer)
.toList()
fun <T, R> Flowable<List<T>>.mapList(
transformer: (T) -> R
) = flatMapIterable { it }
.map(transformer)
.toList()
fun <T> ObservableField<T>.toObservable(): Observable<T> {
val observableField = this
return Observable.create { emitter ->
observableField.get()?.let { emitter.onNext(it) }
val callback = object : BindingObservable.OnPropertyChangedCallback() {
override fun onPropertyChanged(sender: BindingObservable?, propertyId: Int) {
observableField.get()?.let { emitter.onNext(it) }
}
}
observableField.addOnPropertyChangedCallback(callback)
emitter.setDisposable(Disposables.fromAction {
observableField.removeOnPropertyChangedCallback(callback)
})
}
}
fun <T : Any> T.toSingle() = Single.just(this)
fun <T1, T2, R> zip(t1: Single<T1>, t2: Single<T2>, zipper: (T1, T2) -> R) =
Single.zip(t1, t2, BiFunction<T1, T2, R> { rt1, rt2 -> zipper(rt1, rt2) })
package com.topjohnwu.magisk.extensions
import android.content.Context
import android.content.res.ColorStateList
import android.view.View
import android.widget.TextView
import androidx.annotation.ColorInt
import androidx.annotation.ColorRes
import androidx.annotation.StringRes
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.core.view.ViewCompat
import androidx.fragment.app.Fragment
import com.google.android.material.snackbar.Snackbar
fun AppCompatActivity.snackbar(
view: View,
@StringRes messageRes: Int,
length: Int = Snackbar.LENGTH_SHORT,
f: Snackbar.() -> Unit = {}
) {
snackbar(view, getString(messageRes), length, f)
}
fun AppCompatActivity.snackbar(
view: View,
message: String,
length: Int = Snackbar.LENGTH_SHORT,
f: Snackbar.() -> Unit = {}
) = Snackbar.make(view, message, length)
.apply(f)
.show()
fun Fragment.snackbar(
view: View,
@StringRes messageRes: Int,
length: Int = Snackbar.LENGTH_SHORT,
f: Snackbar.() -> Unit = {}
) {
snackbar(view, getString(messageRes), length, f)
}
fun Fragment.snackbar(
view: View,
message: String,
length: Int = Snackbar.LENGTH_SHORT,
f: Snackbar.() -> Unit = {}
) = Snackbar.make(view, message, length)
.apply(f)
.show()
fun Snackbar.action(init: KSnackbar.() -> Unit) = apply {
val config = KSnackbar().apply(init)
setAction(config.title(context), config.onClickListener)
when {
config.hasValidColor -> setActionTextColor(config.color(context) ?: return@apply)
config.hasValidColorStateList -> setActionTextColor(config.colorStateList(context) ?: return@apply)
}
}
class KSnackbar {
var colorRes: Int = -1
var colorStateListRes: Int = -1
var title: CharSequence = ""
var titleRes: Int = -1
internal var onClickListener: (View) -> Unit = {}
internal val hasValidColor get() = colorRes != -1
internal val hasValidColorStateList get() = colorStateListRes != -1
fun onClicked(listener: (View) -> Unit) {
onClickListener = listener
}
internal fun title(context: Context) = if (title.isBlank()) context.getString(titleRes) else title
internal fun colorStateList(context: Context) = context.colorStateListCompat(colorStateListRes)
internal fun color(context: Context) = context.colorCompat(colorRes)
}
@Deprecated("Kotlin DSL version is preferred", ReplaceWith("action {}"))
fun Snackbar.action(
@StringRes actionRes: Int,
@ColorRes colorRes: Int? = null,
listener: (View) -> Unit
) {
view.resources.getString(actionRes)
colorRes?.let { ContextCompat.getColor(view.context, colorRes) }
action {}
}
@Deprecated("Kotlin DSL version is preferred", ReplaceWith("action {}"))
fun Snackbar.action(action: String, @ColorInt color: Int? = null, listener: (View) -> Unit) {
setAction(action, listener)
color?.let { setActionTextColor(color) }
}
fun Snackbar.textColorRes(@ColorRes colorRes: Int) {
textColor(context.colorCompat(colorRes) ?: return)
}
fun Snackbar.textColor(@ColorInt color: Int) {
val tv = view.findViewById<TextView>(com.google.android.material.R.id.snackbar_text)
tv.setTextColor(color)
}
fun Snackbar.backgroundColorRes(@ColorRes colorRes: Int) {
backgroundColor(context.colorCompat(colorRes) ?: return)
}
fun Snackbar.backgroundColor(@ColorInt color: Int) {
ViewCompat.setBackgroundTintList(
view,
ColorStateList.valueOf(color)
)
}
fun Snackbar.alert() {
textColor(0xF44336)
}
fun Snackbar.success() {
textColor(0x4CAF50)
}
\ No newline at end of file
package com.topjohnwu.magisk.extensions
import android.content.ComponentName
import android.content.Context
import android.content.ContextWrapper
import android.content.Intent
import android.content.pm.ApplicationInfo
import android.content.pm.ComponentInfo
......@@ -8,13 +10,23 @@ import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.content.pm.PackageManager.*
import android.content.res.Configuration
import android.content.res.Resources
import android.database.Cursor
import android.net.Uri
import android.os.Build
import android.provider.OpenableColumns
import android.view.View
import androidx.annotation.ColorRes
import androidx.annotation.DrawableRes
import androidx.core.content.ContextCompat
import androidx.core.net.toUri
import com.topjohnwu.magisk.utils.DynamicClassLoader
import com.topjohnwu.magisk.utils.FileProvider
import com.topjohnwu.magisk.utils.Utils
import com.topjohnwu.magisk.utils.currentLocale
import java.io.File
import java.io.FileNotFoundException
import java.util.*
val packageName: String get() = get<Context>().packageName
......@@ -85,6 +97,105 @@ fun Context.readUri(uri: Uri) =
fun Intent.startActivity(context: Context) = context.startActivity(this)
fun Intent.toCommand(args: MutableList<String>) {
if (action != null) {
args.add("-a")
args.add(action!!)
}
if (component != null) {
args.add("-n")
args.add(component!!.flattenToString())
}
if (data != null) {
args.add("-d")
args.add(dataString!!)
}
if (categories != null) {
for (cat in categories) {
args.add("-c")
args.add(cat)
}
}
if (type != null) {
args.add("-t")
args.add(type!!)
}
val extras = extras
if (extras != null) {
loop@ for (key in extras.keySet()) {
val v = extras.get(key) ?: continue
var value: Any = v
val arg: String
when {
v is String -> arg = "--es"
v is Boolean -> arg = "--ez"
v is Int -> arg = "--ei"
v is Long -> arg = "--el"
v is Float -> arg = "--ef"
v is Uri -> arg = "--eu"
v is ComponentName -> {
arg = "--ecn"
value = v.flattenToString()
}
v is ArrayList<*> -> {
if (v.size <= 0)
/* Impossible to know the type due to type erasure */
continue@loop
arg = if (v[0] is Int)
"--eial"
else if (v[0] is Long)
"--elal"
else if (v[0] is Float)
"--efal"
else if (v[0] is String)
"--esal"
else
continue@loop /* Unsupported */
val sb = StringBuilder()
for (o in v) {
sb.append(o.toString().replace(",", "\\,"))
sb.append(',')
}
// Remove trailing comma
sb.deleteCharAt(sb.length - 1)
value = sb
}
v.javaClass.isArray -> {
arg = if (v is IntArray)
"--eia"
else if (v is LongArray)
"--ela"
else if (v is FloatArray)
"--efa"
else if (v is Array<*> && v.isArrayOf<String>())
"--esa"
else
continue@loop /* Unsupported */
val sb = StringBuilder()
val len = java.lang.reflect.Array.getLength(v)
for (i in 0 until len) {
sb.append(java.lang.reflect.Array.get(v, i)!!.toString().replace(",", "\\,"))
sb.append(',')
}
// Remove trailing comma
sb.deleteCharAt(sb.length - 1)
value = sb
}
else -> continue@loop
} /* Unsupported */
args.add(arg)
args.add(key)
args.add(value.toString())
}
}
args.add("-f")
args.add(flags.toString())
}
fun File.provide(context: Context = get()): Uri {
return FileProvider.getUriForFile(context, context.packageName + ".provider", this)
}
......@@ -119,3 +230,48 @@ fun ApplicationInfo.getLabel(pm: PackageManager): String {
return loadLabel(pm).toString()
}
fun Intent.exists(packageManager: PackageManager) = resolveActivity(packageManager) != null
fun Context.colorCompat(@ColorRes id: Int) = try {
ContextCompat.getColor(this, id)
} catch (e: Resources.NotFoundException) {
null
}
fun Context.colorStateListCompat(@ColorRes id: Int) = try {
ContextCompat.getColorStateList(this, id)
} catch (e: Resources.NotFoundException) {
null
}
fun Context.drawableCompat(@DrawableRes id: Int) = ContextCompat.getDrawable(this, id)
/**
* Pass [start] and [end] dimensions, function will return left and right
* with respect to RTL layout direction
*/
fun Context.startEndToLeftRight(start: Int, end: Int): Pair<Int, Int> {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 &&
resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_RTL
) {
return end to start
}
return start to end
}
fun Context.openUrl(url: String) = Utils.openLink(this, url.toUri())
@Suppress("FunctionName")
inline fun <reified T> T.DynamicClassLoader(apk: File)
= DynamicClassLoader(apk, T::class.java.classLoader)
fun Context.unwrap() : Context {
var context = this
while (true) {
if (context is ContextWrapper)
context = context.baseContext
else
break
}
return context
}
package com.topjohnwu.magisk.extensions
import com.skoumal.teanity.util.KObservableField
import com.topjohnwu.magisk.utils.KObservableField
fun KObservableField<Boolean>.toggle() {
......
......@@ -2,8 +2,7 @@ package com.topjohnwu.magisk.extensions
import androidx.collection.SparseArrayCompat
import androidx.databinding.ObservableList
import com.skoumal.teanity.extensions.subscribeK
import com.skoumal.teanity.util.DiffObservableList
import com.topjohnwu.magisk.utils.DiffObservableList
import io.reactivex.disposables.Disposable
fun <T> MutableList<T>.update(newList: List<T>) {
......
package com.topjohnwu.magisk.extensions
import io.reactivex.Single
import io.reactivex.functions.BiFunction
fun <T : Any> T.toSingle() = Single.just(this)
fun <T1, T2, R> zip(t1: Single<T1>, t2: Single<T2>, zipper: (T1, T2) -> R) =
Single.zip(t1, t2, BiFunction<T1, T2, R> { rt1, rt2 -> zipper(rt1, rt2) })
\ No newline at end of file
......@@ -2,7 +2,7 @@ package com.topjohnwu.magisk.model.binding
import androidx.databinding.ViewDataBinding
import androidx.recyclerview.widget.RecyclerView
import com.skoumal.teanity.databinding.ComparableRvItem
import com.topjohnwu.magisk.databinding.ComparableRvItem
import com.topjohnwu.magisk.model.entity.recycler.LenientRvItem
import me.tatarka.bindingcollectionadapter2.BindingRecyclerViewAdapter
......
......@@ -10,6 +10,7 @@ import androidx.core.app.NotificationCompat
import com.topjohnwu.magisk.ClassMap
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.extensions.chooser
import com.topjohnwu.magisk.extensions.exists
import com.topjohnwu.magisk.extensions.provide
import com.topjohnwu.magisk.model.entity.internal.Configuration.*
import com.topjohnwu.magisk.model.entity.internal.Configuration.Flash.Secondary
......@@ -17,6 +18,7 @@ import com.topjohnwu.magisk.model.entity.internal.DownloadSubject
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject.*
import com.topjohnwu.magisk.ui.flash.FlashActivity
import com.topjohnwu.magisk.utils.APKInstall
import org.koin.core.get
import java.io.File
import kotlin.random.Random.Default.nextInt
......@@ -76,8 +78,14 @@ open class DownloadService : RemoteFileService() {
private fun NotificationCompat.Builder.addActionsInternal(subject: Magisk)
= when (val conf = subject.configuration) {
Download -> addAction(0, R.string.download_open_parent, fileIntent(subject.file.parentFile!!))
.addAction(0, R.string.download_open_self, fileIntent(subject.file))
Download -> this.apply {
fileIntent(subject.file.parentFile!!)
.takeIf { it.exists(get()) }
?.let { addAction(0, R.string.download_open_parent, it.chooser()) }
fileIntent(subject.file)
.takeIf { it.exists(get()) }
?.let { addAction(0, R.string.download_open_self, it.chooser()) }
}
Uninstall -> setContentIntent(FlashActivity.uninstallIntent(context, subject.file))
is Flash -> setContentIntent(FlashActivity.flashIntent(context, subject.file, conf is Secondary))
is Patch -> setContentIntent(FlashActivity.patchIntent(context, subject.file, conf.fileUri))
......@@ -86,8 +94,14 @@ open class DownloadService : RemoteFileService() {
private fun NotificationCompat.Builder.addActionsInternal(subject: Module)
= when (subject.configuration) {
Download -> addAction(0, R.string.download_open_parent, fileIntent(subject.file.parentFile!!))
.addAction(0, R.string.download_open_self, fileIntent(subject.file))
Download -> this.apply {
fileIntent(subject.file.parentFile!!)
.takeIf { it.exists(get()) }
?.let { addAction(0, R.string.download_open_parent, it.chooser()) }
fileIntent(subject.file)
.takeIf { it.exists(get()) }
?.let { addAction(0, R.string.download_open_self, it.chooser()) }
}
is Flash -> setContentIntent(FlashActivity.installIntent(context, subject.file))
else -> this
}
......@@ -115,7 +129,6 @@ open class DownloadService : RemoteFileService() {
.setDataAndType(file.provide(this), file.type)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
.chooser()
}
class Builder {
......
......@@ -5,13 +5,13 @@ import com.topjohnwu.magisk.BuildConfig
import com.topjohnwu.magisk.ClassMap
import com.topjohnwu.magisk.Config
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.extensions.DynamicClassLoader
import com.topjohnwu.magisk.model.entity.internal.Configuration.APK.Restore
import com.topjohnwu.magisk.model.entity.internal.Configuration.APK.Upgrade
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject
import com.topjohnwu.magisk.ui.SplashActivity
import com.topjohnwu.magisk.utils.DynamicClassLoader
import com.topjohnwu.magisk.utils.PatchAPK
import com.topjohnwu.magisk.utils.RootUtils
import com.topjohnwu.magisk.utils.Utils
import com.topjohnwu.superuser.Shell
import timber.log.Timber
import java.io.File
......@@ -54,7 +54,7 @@ private fun RemoteFileService.restore(apk: File, id: Int) {
if (Shell.su("pm install $apk").exec().isSuccess) {
val component = ComponentName(BuildConfig.APPLICATION_ID,
ClassMap.get<Class<*>>(SplashActivity::class.java).name)
RootUtils.rmAndLaunch(packageName, component)
Utils.rmAndLaunch(packageName, component)
}
}
......
package com.topjohnwu.magisk.model.download
import android.app.Notification
import android.app.Service
import android.content.Intent
import android.os.IBinder
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import com.topjohnwu.magisk.base.BaseService
import org.koin.core.KoinComponent
import java.util.*
import kotlin.random.Random.Default.nextInt
abstract class NotificationService : Service() {
abstract class NotificationService : BaseService(), KoinComponent {
abstract val defaultNotification: NotificationCompat.Builder
......
......@@ -3,11 +3,11 @@ package com.topjohnwu.magisk.model.download
import android.app.Activity
import android.content.Intent
import androidx.core.app.NotificationCompat
import com.skoumal.teanity.extensions.subscribeK
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.data.network.GithubRawServices
import com.topjohnwu.magisk.di.NullActivity
import com.topjohnwu.magisk.extensions.get
import com.topjohnwu.magisk.extensions.subscribeK
import com.topjohnwu.magisk.extensions.writeTo
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject.*
......
package com.topjohnwu.magisk.model.entity
data class Version(val version: String, val versionCode: Int)
\ No newline at end of file
......@@ -48,7 +48,7 @@ class Module(path: String) : BaseModule() {
}
if (name.isEmpty()) {
name = id;
name = id
}
}
......@@ -65,7 +65,7 @@ class Module(path: String) : BaseModule() {
val module = Module(Const.MAGISK_PATH + "/" + file.name)
moduleList.add(module)
}
return moduleList
return moduleList.sortedBy { it.name }
}
}
}
package com.topjohnwu.magisk.model.entity.recycler
import com.skoumal.teanity.databinding.ComparableRvItem
import com.skoumal.teanity.extensions.addOnPropertyChangedCallback
import com.skoumal.teanity.rxbus.RxBus
import com.skoumal.teanity.util.DiffObservableList
import com.skoumal.teanity.util.KObservableField
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.databinding.ComparableRvItem
import com.topjohnwu.magisk.extensions.addOnPropertyChangedCallback
import com.topjohnwu.magisk.extensions.inject
import com.topjohnwu.magisk.extensions.toggle
import com.topjohnwu.magisk.model.entity.HideAppInfo
import com.topjohnwu.magisk.model.entity.HideTarget
import com.topjohnwu.magisk.model.entity.state.IndeterminateState
import com.topjohnwu.magisk.model.events.HideProcessEvent
import com.topjohnwu.magisk.utils.DiffObservableList
import com.topjohnwu.magisk.utils.KObservableField
import com.topjohnwu.magisk.utils.RxBus
class HideRvItem(val item: HideAppInfo, targets: List<HideTarget>) :
ComparableRvItem<HideRvItem>() {
......
......@@ -2,7 +2,7 @@ package com.topjohnwu.magisk.model.entity.recycler
import androidx.databinding.ViewDataBinding
import androidx.recyclerview.widget.RecyclerView
import com.skoumal.teanity.databinding.ComparableRvItem
import com.topjohnwu.magisk.databinding.ComparableRvItem
/**
* This item addresses issues where enclosing recycler has to be invalidated or generally
......
package com.topjohnwu.magisk.model.entity.recycler
import com.skoumal.teanity.databinding.ComparableRvItem
import com.skoumal.teanity.util.DiffObservableList
import com.skoumal.teanity.util.KObservableField
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.databinding.ComparableRvItem
import com.topjohnwu.magisk.extensions.timeFormatMedium
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.WrappedMagiskLog
import com.topjohnwu.magisk.utils.DiffObservableList
import com.topjohnwu.magisk.utils.KObservableField
class LogRvItem : ComparableRvItem<LogRvItem>() {
override val layoutRes: Int = R.layout.item_page_log
......
......@@ -2,14 +2,14 @@ package com.topjohnwu.magisk.model.entity.recycler
import android.content.res.Resources
import androidx.annotation.StringRes
import com.skoumal.teanity.databinding.ComparableRvItem
import com.skoumal.teanity.extensions.addOnPropertyChangedCallback
import com.skoumal.teanity.util.KObservableField
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.databinding.ComparableRvItem
import com.topjohnwu.magisk.extensions.addOnPropertyChangedCallback
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
class ModuleRvItem(val item: Module) : ComparableRvItem<ModuleRvItem>() {
......
package com.topjohnwu.magisk.model.entity.recycler
import android.graphics.drawable.Drawable
import com.skoumal.teanity.databinding.ComparableRvItem
import com.skoumal.teanity.extensions.addOnPropertyChangedCallback
import com.skoumal.teanity.rxbus.RxBus
import com.skoumal.teanity.util.KObservableField
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.databinding.ComparableRvItem
import com.topjohnwu.magisk.extensions.addOnPropertyChangedCallback
import com.topjohnwu.magisk.extensions.inject
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.utils.KObservableField
import com.topjohnwu.magisk.utils.RxBus
class PolicyRvItem(val item: MagiskPolicy, val icon: Drawable) : ComparableRvItem<PolicyRvItem>() {
......
package com.topjohnwu.magisk.model.entity.recycler
import com.skoumal.teanity.databinding.ComparableRvItem
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.databinding.ComparableRvItem
class SectionRvItem(val text: String) : ComparableRvItem<SectionRvItem>() {
override val layoutRes: Int = R.layout.item_section
......
package com.topjohnwu.magisk.model.entity.recycler
import com.skoumal.teanity.databinding.ComparableRvItem
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.databinding.ComparableRvItem
class SpinnerRvItem(val item: String) : ComparableRvItem<SpinnerRvItem>() {
......
package com.topjohnwu.magisk.model.events
internal interface EventHandler {
/**
* Called for all [ViewEvent]s published by associated viewModel.
* For [SimpleViewEvent]s, both this and [onSimpleEventDispatched]
* methods are called - you can choose the way how you handle them.
*/
fun onEventDispatched(event: ViewEvent) {}
/**
* Called for all [SimpleViewEvent]s published by associated viewModel.
* Both this and [onEventDispatched] methods are called - you can choose
* the way how you handle them.
*/
fun onSimpleEventDispatched(event: Int) {}
val viewEventObserver get() = ViewEventObserver {
onEventDispatched(it)
if (it is SimpleViewEvent) {
onSimpleEventDispatched(it.event)
}
}
}
package com.topjohnwu.magisk.model.events
import com.skoumal.teanity.rxbus.RxBus
import com.topjohnwu.magisk.model.entity.MagiskPolicy
import com.topjohnwu.magisk.model.entity.recycler.HideProcessRvItem
import com.topjohnwu.magisk.model.entity.recycler.ModuleRvItem
import com.topjohnwu.magisk.model.entity.recycler.PolicyRvItem
import com.topjohnwu.magisk.utils.RxBus
class HideProcessEvent(val item: HideProcessRvItem) : RxBus.Event
......
package com.topjohnwu.magisk.model.events
class SimpleViewEvent(
val event: Int
) : ViewEvent()
\ No newline at end of file
package com.topjohnwu.magisk.model.events
import android.content.Context
import androidx.annotation.StringRes
import com.google.android.material.snackbar.Snackbar
class SnackbarEvent private constructor(
@StringRes private val messageRes: Int,
private val messageString: String?,
val length: Int,
val f: Snackbar.() -> Unit
) : ViewEvent() {
constructor(
@StringRes messageRes: Int,
length: Int = Snackbar.LENGTH_SHORT,
f: Snackbar.() -> Unit = {}
) : this(messageRes, null, length, f)
constructor(
message: String,
length: Int = Snackbar.LENGTH_SHORT,
f: Snackbar.() -> Unit = {}
) : this(-1, message, length, f)
fun message(context: Context): String = messageString ?: context.getString(messageRes)
}
package com.topjohnwu.magisk.model.events
import androidx.lifecycle.Observer
/**
* Observer for [ViewEvent]s, which automatically checks if event was handled
*/
class ViewEventObserver(private val onEventUnhandled: (ViewEvent) -> Unit) : Observer<ViewEvent> {
override fun onChanged(event: ViewEvent?) {
event?.let {
if (!it.handled) {
it.handled = true
onEventUnhandled(it)
}
}
}
}
\ No newline at end of file
......@@ -12,6 +12,16 @@ import com.topjohnwu.magisk.model.entity.module.Repo
import com.topjohnwu.magisk.model.permissions.PermissionRequestBuilder
import io.reactivex.subjects.PublishSubject
/**
* Class for passing events from ViewModels to Activities/Fragments
* Variable [handled] used so each event is handled only once
* (see https://medium.com/google-developers/livedata-with-snackbar-navigation-and-other-events-the-singleliveevent-case-ac2622673150)
* Use [ViewEventObserver] for observing these events
*/
abstract class ViewEvent {
var handled = false
}
data class OpenLinkEvent(val url: String) : ViewEvent()
......
......@@ -5,12 +5,14 @@ import androidx.annotation.AnimRes
import androidx.annotation.AnimatorRes
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import com.skoumal.teanity.viewevents.NavigationDslMarker
import com.skoumal.teanity.viewevents.ViewEvent
import com.topjohnwu.magisk.model.events.ViewEvent
import com.topjohnwu.magisk.model.events.ActivityExecutor
import com.topjohnwu.magisk.redesign.compat.CompatActivity
import kotlin.reflect.KClass
@DslMarker
annotation class NavigationDslMarker
class MagiskNavigationEvent(
val navDirections: MagiskNavDirectionsBuilder,
val navOptions: MagiskNavOptions,
......
package com.topjohnwu.magisk.model.receiver
import android.content.BroadcastReceiver
import android.content.Context
import android.content.ContextWrapper
import android.content.Intent
import com.topjohnwu.magisk.ClassMap
import com.topjohnwu.magisk.Config
import com.topjohnwu.magisk.Const
import com.topjohnwu.magisk.Info
import com.topjohnwu.magisk.base.BaseReceiver
import com.topjohnwu.magisk.data.database.PolicyDao
import com.topjohnwu.magisk.data.database.base.su
import com.topjohnwu.magisk.extensions.inject
import com.topjohnwu.magisk.extensions.reboot
import com.topjohnwu.magisk.model.download.DownloadService
import com.topjohnwu.magisk.model.entity.ManagerJson
......@@ -20,8 +19,9 @@ import com.topjohnwu.magisk.utils.SuLogger
import com.topjohnwu.magisk.view.Notifications
import com.topjohnwu.magisk.view.Shortcuts
import com.topjohnwu.superuser.Shell
import org.koin.core.inject
open class GeneralReceiver : BroadcastReceiver() {
open class GeneralReceiver : BaseReceiver() {
private val policyDB: PolicyDao by inject()
......@@ -36,7 +36,7 @@ open class GeneralReceiver : BroadcastReceiver() {
return intent.data?.encodedSchemeSpecificPart.orEmpty()
}
override fun onReceive(context: Context, intent: Intent?) {
override fun onReceive(context: ContextWrapper, intent: Intent?) {
intent ?: return
when (intent.action ?: return) {
Intent.ACTION_REBOOT, Intent.ACTION_BOOT_COMPLETED -> {
......
......@@ -3,9 +3,9 @@ package com.topjohnwu.magisk.model.update
import androidx.work.ListenableWorker
import com.topjohnwu.magisk.BuildConfig
import com.topjohnwu.magisk.Info
import com.topjohnwu.magisk.base.DelegateWorker
import com.topjohnwu.magisk.data.repository.MagiskRepository
import com.topjohnwu.magisk.extensions.inject
import com.topjohnwu.magisk.model.worker.DelegateWorker
import com.topjohnwu.magisk.view.Notifications
import com.topjohnwu.superuser.Shell
......
......@@ -2,11 +2,11 @@ package com.topjohnwu.magisk.tasks
import android.content.Context
import android.net.Uri
import com.skoumal.teanity.extensions.subscribeK
import com.topjohnwu.magisk.Const
import com.topjohnwu.magisk.extensions.fileName
import com.topjohnwu.magisk.extensions.inject
import com.topjohnwu.magisk.extensions.readUri
import com.topjohnwu.magisk.extensions.subscribeK
import com.topjohnwu.magisk.utils.unzip
import com.topjohnwu.superuser.Shell
import io.reactivex.Single
......
......@@ -6,7 +6,6 @@ import android.os.Build
import android.text.TextUtils
import androidx.annotation.MainThread
import androidx.annotation.WorkerThread
import com.skoumal.teanity.extensions.subscribeK
import com.topjohnwu.magisk.Config
import com.topjohnwu.magisk.Info
import com.topjohnwu.magisk.data.network.GithubRawServices
......@@ -25,9 +24,11 @@ import org.kamranzafar.jtar.TarHeader
import org.kamranzafar.jtar.TarInputStream
import org.kamranzafar.jtar.TarOutputStream
import timber.log.Timber
import java.io.*
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.io.InputStream
import java.nio.ByteBuffer
import java.util.*
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
......@@ -40,7 +41,7 @@ abstract class MagiskInstaller {
private val console: MutableList<String>
private val logs: MutableList<String>
private var isTar = false
private var tarOut: TarOutputStream? = null
private val service: GithubRawServices by inject()
private val context: Context by inject()
......@@ -151,7 +152,9 @@ abstract class MagiskInstaller {
private fun handleTar(input: InputStream) {
console.add("- Processing tar file")
var vbmeta = false
withStreams(TarInputStream(input), TarOutputStream(destFile)) { tarIn, tarOut ->
val tarOut = TarOutputStream(destFile)
this.tarOut = tarOut
TarInputStream(input).use { tarIn ->
lateinit var entry: TarEntry
while (tarIn.nextEntry?.let { entry = it } != null) {
if (entry.name.contains("boot.img") || entry.name.contains("recovery.img")) {
......@@ -215,8 +218,7 @@ abstract class MagiskInstaller {
return false
}
it.reset()
if (Arrays.equals(magic, "ustar".toByteArray())) {
isTar = true
if (magic.contentEquals("ustar".toByteArray())) {
destFile = File(Config.downloadDirectory, "magisk_patched.tar")
handleTar(it)
} else {
......@@ -293,15 +295,13 @@ abstract class MagiskInstaller {
protected fun storeBoot(): Boolean {
val patched = SuFile.open(installDir, "new-boot.img")
try {
val os: OutputStream
if (isTar) {
os = TarOutputStream(destFile, true)
os.putNextEntry(newEntry(
val os = tarOut?.let {
it.putNextEntry(newEntry(
if (srcBoot.contains("recovery")) "recovery.img" else "boot.img",
patched.length()))
} else {
os = destFile.outputStream()
}
tarOut = null
it
} ?: destFile.outputStream()
patched.suInputStream().use { it.copyTo(os); os.close() }
} catch (e: IOException) {
console.add("! Failed to output to $destFile")
......
......@@ -4,15 +4,24 @@ import android.content.Intent
import android.os.Bundle
import androidx.core.view.GravityCompat
import androidx.fragment.app.Fragment
import com.skoumal.teanity.extensions.addOnPropertyChangedCallback
import androidx.fragment.app.FragmentTransaction
import com.ncapdevi.fragnav.FragNavController
import com.ncapdevi.fragnav.FragNavTransactionOptions
import com.topjohnwu.magisk.ClassMap
import com.topjohnwu.magisk.Config
import com.topjohnwu.magisk.Const.Key.OPEN_SECTION
import com.topjohnwu.magisk.Info
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.base.BaseActivity
import com.topjohnwu.magisk.base.BaseFragment
import com.topjohnwu.magisk.databinding.ActivityMainBinding
import com.topjohnwu.magisk.extensions.addOnPropertyChangedCallback
import com.topjohnwu.magisk.extensions.snackbar
import com.topjohnwu.magisk.model.events.*
import com.topjohnwu.magisk.model.navigation.MagiskAnimBuilder
import com.topjohnwu.magisk.model.navigation.MagiskNavigationEvent
import com.topjohnwu.magisk.model.navigation.Navigation
import com.topjohnwu.magisk.ui.base.MagiskActivity
import com.topjohnwu.magisk.model.navigation.Navigator
import com.topjohnwu.magisk.ui.hide.MagiskHideFragment
import com.topjohnwu.magisk.ui.home.HomeFragment
import com.topjohnwu.magisk.ui.log.LogFragment
......@@ -23,15 +32,22 @@ import com.topjohnwu.magisk.ui.superuser.SuperuserFragment
import com.topjohnwu.magisk.utils.Utils
import com.topjohnwu.superuser.Shell
import org.koin.androidx.viewmodel.ext.android.viewModel
import timber.log.Timber
import kotlin.reflect.KClass
open class MainActivity : MagiskActivity<MainViewModel, ActivityMainBinding>() {
open class MainActivity : BaseActivity<MainViewModel, ActivityMainBinding>(), Navigator,
FragNavController.RootFragmentListener, FragNavController.TransactionListener {
override val layoutRes: Int = R.layout.activity_main
override val viewModel: MainViewModel by viewModel()
override val navHostId: Int = R.id.main_nav_host
override val defaultPosition: Int = 0
private val navHostId: Int = R.id.main_nav_host
private val defaultPosition: Int = 0
private val navigationController by lazy {
FragNavController(supportFragmentManager, navHostId)
}
private val isRootFragment get() =
navigationController.currentStackIndex != defaultPosition
override val baseFragments: List<KClass<out Fragment>> = listOf(
HomeFragment::class,
......@@ -43,10 +59,6 @@ open class MainActivity : MagiskActivity<MainViewModel, ActivityMainBinding>() {
SettingsFragment::class
)
/*override fun getDarkTheme(): Int {
return R.style.AppTheme_Dark
}*/
override fun onCreate(savedInstanceState: Bundle?) {
if (!SplashActivity.DONE) {
startActivity(Intent(this, ClassMap[SplashActivity::class.java]))
......@@ -54,6 +66,13 @@ open class MainActivity : MagiskActivity<MainViewModel, ActivityMainBinding>() {
}
super.onCreate(savedInstanceState)
navigationController.apply {
rootFragmentListener = this@MainActivity
transactionListener = this@MainActivity
initialize(defaultPosition, savedInstanceState)
}
checkHideSection()
setSupportActionBar(binding.mainInclude.mainToolbar)
......@@ -68,6 +87,11 @@ open class MainActivity : MagiskActivity<MainViewModel, ActivityMainBinding>() {
}
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
navigationController.onSaveInstanceState(outState)
}
override fun setTitle(title: CharSequence?) {
supportActionBar?.title = title
}
......@@ -76,25 +100,46 @@ open class MainActivity : MagiskActivity<MainViewModel, ActivityMainBinding>() {
supportActionBar?.setTitle(titleId)
}
override fun onTabTransaction(fragment: Fragment?, index: Int) {
val fragmentId = when (fragment) {
is HomeFragment -> R.id.magiskFragment
is SuperuserFragment -> R.id.superuserFragment
is MagiskHideFragment -> R.id.magiskHideFragment
is ModulesFragment -> R.id.modulesFragment
is ReposFragment -> R.id.reposFragment
is LogFragment -> R.id.logFragment
is SettingsFragment -> R.id.settings
else -> return
}
binding.navView.setCheckedItem(fragmentId)
}
override fun onBackPressed() {
if (binding.drawerLayout.isDrawerOpen(binding.navView)) {
binding.drawerLayout.closeDrawer(binding.navView)
} else {
super.onBackPressed()
val fragment = navigationController.currentFrag as? BaseFragment<*, *>
if (fragment?.onBackPressed() == true) {
return
}
try {
navigationController.popFragment()
} catch (e: UnsupportedOperationException) {
when {
isRootFragment -> {
val options = FragNavTransactionOptions.newBuilder()
.transition(FragmentTransaction.TRANSIT_FRAGMENT_CLOSE)
.build()
navigationController.switchTab(defaultPosition, options)
}
else -> super.onBackPressed()
}
}
}
}
override fun onEventDispatched(event: ViewEvent) {
super.onEventDispatched(event)
when (event) {
is SnackbarEvent -> snackbar(snackbarView, event.message(this), event.length, event.f)
is BackPressEvent -> onBackPressed()
is MagiskNavigationEvent -> navigateTo(event)
is ViewActionEvent -> event.action(this)
is PermissionEvent -> withPermissions(*event.permissions.toTypedArray()) {
onSuccess { event.callback.onNext(true) }
onFailure {
event.callback.onNext(false)
event.callback.onError(SecurityException("User refused permissions"))
}
}
}
}
......@@ -120,4 +165,84 @@ open class MainActivity : MagiskActivity<MainViewModel, ActivityMainBinding>() {
menu.findItem(R.id.superuserFragment).isVisible =
Utils.showSuperUser()
}
private fun FragNavTransactionOptions.Builder.customAnimations(options: MagiskAnimBuilder) =
customAnimations(options.enter, options.exit, options.popEnter, options.popExit).apply {
if (!options.anySet) {
transition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
}
}
override val numberOfRootFragments: Int get() = baseFragments.size
override fun getRootFragment(index: Int) = baseFragments[index].java.newInstance()
override fun onTabTransaction(fragment: Fragment?, index: Int) {
val fragmentId = when (fragment) {
is HomeFragment -> R.id.magiskFragment
is SuperuserFragment -> R.id.superuserFragment
is MagiskHideFragment -> R.id.magiskHideFragment
is ModulesFragment -> R.id.modulesFragment
is ReposFragment -> R.id.reposFragment
is LogFragment -> R.id.logFragment
is SettingsFragment -> R.id.settings
else -> return
}
binding.navView.setCheckedItem(fragmentId)
}
override fun navigateTo(event: MagiskNavigationEvent) {
val directions = event.navDirections
navigationController.defaultTransactionOptions = FragNavTransactionOptions.newBuilder()
.customAnimations(event.animOptions)
.build()
navigationController.currentStack
?.indexOfFirst { it.javaClass == event.navOptions.popUpTo }
?.let { if (it == -1) null else it } // invalidate if class is not found
?.let { if (event.navOptions.inclusive) it + 1 else it }
?.let { navigationController.popFragments(it) }
when (directions.isActivity) {
true -> navigateToActivity(event)
else -> navigateToFragment(event)
}
}
private fun navigateToActivity(event: MagiskNavigationEvent) {
val destination = event.navDirections.destination?.java ?: let {
Timber.e("Cannot navigate to null destination")
return
}
val options = event.navOptions
Intent(this, destination)
.putExtras(event.navDirections.args)
.apply {
if (options.singleTop) addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
if (options.clearTask) addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
}
.let { startActivity(it) }
}
private fun navigateToFragment(event: MagiskNavigationEvent) {
val destination = event.navDirections.destination?.java ?: let {
Timber.e("Cannot navigate to null destination")
return
}
when (val index = baseFragments.indexOfFirst { it.java.name == destination.name }) {
-1 -> destination.newInstance()
.apply { arguments = event.navDirections.args }
.let { navigationController.pushFragment(it) }
// When it's desired that fragments of same class are put on top of one another edit this
else -> navigationController.switchTab(index)
}
}
override fun onFragmentTransaction(
fragment: Fragment?,
transactionType: FragNavController.TransactionType
) = Unit
}
......@@ -2,11 +2,11 @@ package com.topjohnwu.magisk.ui
import android.view.MenuItem
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.base.viewmodel.BaseViewModel
import com.topjohnwu.magisk.model.navigation.Navigation
import com.topjohnwu.magisk.ui.base.MagiskViewModel
class MainViewModel : MagiskViewModel() {
class MainViewModel : BaseViewModel() {
fun navPressed() = Navigation.Main.OPEN_NAV.publish()
......
package com.topjohnwu.magisk.ui
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.text.TextUtils
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import com.topjohnwu.magisk.*
import com.topjohnwu.magisk.model.navigation.Navigation
import com.topjohnwu.magisk.utils.Utils
import com.topjohnwu.magisk.utils.wrap
import com.topjohnwu.magisk.view.Notifications
import com.topjohnwu.magisk.view.Shortcuts
import com.topjohnwu.superuser.Shell
open class SplashActivity : AppCompatActivity() {
open class SplashActivity : Activity() {
override fun attachBaseContext(base: Context) {
super.attachBaseContext(base.wrap())
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
......
package com.topjohnwu.magisk.ui.base
import android.Manifest
import android.content.Context
import android.content.Intent
import android.content.res.Configuration
import android.os.Bundle
import androidx.annotation.CallSuper
import androidx.appcompat.app.AppCompatDelegate
import androidx.collection.SparseArrayCompat
import androidx.core.net.toUri
import androidx.databinding.ViewDataBinding
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentTransaction
import com.karumi.dexter.Dexter
import com.karumi.dexter.MultiplePermissionsReport
import com.karumi.dexter.PermissionToken
import com.karumi.dexter.listener.PermissionRequest
import com.karumi.dexter.listener.multi.MultiplePermissionsListener
import com.ncapdevi.fragnav.FragNavController
import com.ncapdevi.fragnav.FragNavTransactionOptions
import com.skoumal.teanity.view.TeanityActivity
import com.skoumal.teanity.viewevents.ViewEvent
import com.topjohnwu.magisk.Config
import com.topjohnwu.magisk.extensions.set
import com.topjohnwu.magisk.model.events.BackPressEvent
import com.topjohnwu.magisk.model.events.PermissionEvent
import com.topjohnwu.magisk.model.events.ViewActionEvent
import com.topjohnwu.magisk.model.navigation.MagiskAnimBuilder
import com.topjohnwu.magisk.model.navigation.MagiskNavigationEvent
import com.topjohnwu.magisk.model.navigation.Navigator
import com.topjohnwu.magisk.model.permissions.PermissionRequestBuilder
import com.topjohnwu.magisk.utils.LocaleManager
import com.topjohnwu.magisk.utils.Utils
import com.topjohnwu.magisk.utils.currentLocale
import timber.log.Timber
import kotlin.reflect.KClass
typealias RequestCallback = MagiskActivity<*, *>.(Int, Intent?) -> Unit
abstract class MagiskActivity<ViewModel : MagiskViewModel, Binding : ViewDataBinding> :
TeanityActivity<ViewModel, Binding>(), FragNavController.RootFragmentListener,
Navigator, FragNavController.TransactionListener {
override val numberOfRootFragments: Int get() = baseFragments.size
override val baseFragments: List<KClass<out Fragment>> = listOf()
private val resultCallbacks = SparseArrayCompat<RequestCallback>()
protected open val defaultPosition: Int = 0
protected val navigationController get() = if (navHostId == 0) null else _navigationController
private val _navigationController by lazy {
if (navHostId == 0) throw IllegalStateException("Did you forget to override \"navHostId\"?")
FragNavController(supportFragmentManager, navHostId)
}
private val isRootFragment
get() = navigationController?.let { it.currentStackIndex != defaultPosition } ?: false
init {
val theme = if (Config.darkTheme) {
AppCompatDelegate.MODE_NIGHT_YES
} else {
AppCompatDelegate.MODE_NIGHT_NO
}
AppCompatDelegate.setDefaultNightMode(theme)
}
override fun applyOverrideConfiguration(config: Configuration?) {
// Force applying our preferred local
config?.setLocale(currentLocale)
super.applyOverrideConfiguration(config)
}
override fun attachBaseContext(base: Context) {
super.attachBaseContext(LocaleManager.getLocaleContext(base))
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
navigationController?.apply {
rootFragmentListener = this@MagiskActivity
transactionListener = this@MagiskActivity
initialize(defaultPosition, savedInstanceState)
}
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
navigationController?.onSaveInstanceState(outState)
}
@CallSuper
override fun onEventDispatched(event: ViewEvent) {
super.onEventDispatched(event)
when (event) {
is BackPressEvent -> onBackPressed()
is MagiskNavigationEvent -> navigateTo(event)
is ViewActionEvent -> event.action(this)
is PermissionEvent -> withPermissions(*event.permissions.toTypedArray()) {
onSuccess { event.callback.onNext(true) }
onFailure {
event.callback.onNext(false)
event.callback.onError(SecurityException("User refused permissions"))
}
}
}
}
override fun getRootFragment(index: Int) = baseFragments[index].java.newInstance()
override fun navigateTo(event: MagiskNavigationEvent) {
val directions = event.navDirections
navigationController?.defaultTransactionOptions = FragNavTransactionOptions.newBuilder()
.customAnimations(event.animOptions)
.build()
navigationController?.currentStack
?.indexOfFirst { it.javaClass == event.navOptions.popUpTo }
?.let { if (it == -1) null else it } // invalidate if class is not found
?.let { if (event.navOptions.inclusive) it + 1 else it }
?.let { navigationController?.popFragments(it) }
when (directions.isActivity) {
true -> navigateToActivity(event)
else -> navigateToFragment(event)
}
}
private fun navigateToActivity(event: MagiskNavigationEvent) {
val destination = event.navDirections.destination?.java ?: let {
Timber.e("Cannot navigate to null destination")
return
}
val options = event.navOptions
Intent(this, destination)
.putExtras(event.navDirections.args)
.apply {
if (options.singleTop) addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
if (options.clearTask) addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
}
.let { startActivity(it) }
}
private fun navigateToFragment(event: MagiskNavigationEvent) {
val destination = event.navDirections.destination?.java ?: let {
Timber.e("Cannot navigate to null destination")
return
}
when (val index = baseFragments.indexOfFirst { it.java.name == destination.name }) {
-1 -> destination.newInstance()
.apply { arguments = event.navDirections.args }
.let { navigationController?.pushFragment(it) }
// When it's desired that fragments of same class are put on top of one another edit this
else -> navigationController?.switchTab(index)
}
}
override fun onBackPressed() {
val fragment = navigationController?.currentFrag as? MagiskFragment<*, *>
if (fragment?.onBackPressed() == true) {
return
}
try {
navigationController?.popFragment() ?: throw UnsupportedOperationException()
} catch (e: UnsupportedOperationException) {
when {
isRootFragment -> {
val options = FragNavTransactionOptions.newBuilder()
.transition(FragmentTransaction.TRANSIT_FRAGMENT_CLOSE)
.build()
navigationController?.switchTab(defaultPosition, options)
}
else -> super.onBackPressed()
}
}
}
override fun onFragmentTransaction(
fragment: Fragment?,
transactionType: FragNavController.TransactionType
) = Unit
override fun onTabTransaction(fragment: Fragment?, index: Int) = Unit
fun openUrl(url: String) = Utils.openLink(this, url.toUri())
fun withPermissions(vararg permissions: String, builder: PermissionRequestBuilder.() -> Unit) {
val request = PermissionRequestBuilder().apply(builder).build()
Dexter.withActivity(this)
.withPermissions(*permissions)
.withListener(object : MultiplePermissionsListener {
override fun onPermissionsChecked(report: MultiplePermissionsReport) {
if (report.areAllPermissionsGranted()) {
request.onSuccess()
} else {
request.onFailure()
}
}
override fun onPermissionRationaleShouldBeShown(
permissions: MutableList<PermissionRequest>,
token: PermissionToken
) = token.continuePermissionRequest()
}).check()
}
fun withExternalRW(builder: PermissionRequestBuilder.() -> Unit) {
withPermissions(Manifest.permission.WRITE_EXTERNAL_STORAGE, builder = builder)
}
private fun FragNavTransactionOptions.Builder.customAnimations(options: MagiskAnimBuilder) =
customAnimations(options.enter, options.exit, options.popEnter, options.popExit).apply {
if (!options.anySet) {
transition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
resultCallbacks[requestCode]?.apply {
resultCallbacks.remove(requestCode)
invoke(this@MagiskActivity, resultCode, data)
}
}
fun startActivityForResult(
intent: Intent,
requestCode: Int,
listener: RequestCallback
) {
resultCallbacks[requestCode] = listener
startActivityForResult(intent, requestCode)
}
}
package com.topjohnwu.magisk.ui.base
import androidx.annotation.CallSuper
import androidx.databinding.ViewDataBinding
import androidx.fragment.app.Fragment
import com.skoumal.teanity.view.TeanityFragment
import com.skoumal.teanity.viewevents.ViewEvent
import com.topjohnwu.magisk.model.events.BackPressEvent
import com.topjohnwu.magisk.model.events.PermissionEvent
import com.topjohnwu.magisk.model.events.ViewActionEvent
import com.topjohnwu.magisk.model.navigation.MagiskNavigationEvent
import com.topjohnwu.magisk.model.navigation.Navigator
import com.topjohnwu.magisk.model.permissions.PermissionRequestBuilder
import kotlin.reflect.KClass
abstract class MagiskFragment<ViewModel : MagiskViewModel, Binding : ViewDataBinding> :
TeanityFragment<ViewModel, Binding>(), Navigator {
protected val activity get() = requireActivity() as MagiskActivity<*, *>
// We don't need nested fragments
override val baseFragments: List<KClass<Fragment>> = listOf()
override fun navigateTo(event: MagiskNavigationEvent) = activity.navigateTo(event)
@CallSuper
override fun onEventDispatched(event: ViewEvent) {
super.onEventDispatched(event)
when (event) {
is BackPressEvent -> activity.onBackPressed()
is MagiskNavigationEvent -> navigateTo(event)
is ViewActionEvent -> event.action(requireActivity())
is PermissionEvent -> activity.withPermissions(*event.permissions.toTypedArray()) {
onSuccess { event.callback.onNext(true) }
onFailure {
event.callback.onNext(false)
event.callback.onError(SecurityException("User refused permissions"))
}
}
}
}
fun withPermissions(vararg permissions: String, builder: PermissionRequestBuilder.() -> Unit) {
activity.withPermissions(*permissions, builder = builder)
}
fun openLink(url: String) = activity.openUrl(url)
open fun onBackPressed(): Boolean = false
}
......@@ -9,15 +9,21 @@ import androidx.core.net.toUri
import com.topjohnwu.magisk.ClassMap
import com.topjohnwu.magisk.Const
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.base.BaseActivity
import com.topjohnwu.magisk.databinding.ActivityFlashBinding
import com.topjohnwu.magisk.ui.base.MagiskActivity
import com.topjohnwu.magisk.extensions.snackbar
import com.topjohnwu.magisk.model.events.BackPressEvent
import com.topjohnwu.magisk.model.events.PermissionEvent
import com.topjohnwu.magisk.model.events.SnackbarEvent
import com.topjohnwu.magisk.model.events.ViewEvent
import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koin.core.parameter.parametersOf
import java.io.File
open class FlashActivity : MagiskActivity<FlashViewModel, ActivityFlashBinding>() {
open class FlashActivity : BaseActivity<FlashViewModel, ActivityFlashBinding>() {
override val layoutRes: Int = R.layout.activity_flash
override val themeRes: Int = R.style.MagiskTheme_Flashing
override val viewModel: FlashViewModel by viewModel {
val uri = intent.data ?: let { finish(); Uri.EMPTY }
val additionalUri = intent.getParcelableExtra(Const.Key.FLASH_DATA) ?: uri
......@@ -37,6 +43,21 @@ open class FlashActivity : MagiskActivity<FlashViewModel, ActivityFlashBinding>(
super.onBackPressed()
}
override fun onEventDispatched(event: ViewEvent) {
super.onEventDispatched(event)
when (event) {
is SnackbarEvent -> snackbar(snackbarView, event.message(this), event.length, event.f)
is BackPressEvent -> onBackPressed()
is PermissionEvent -> withPermissions(*event.permissions.toTypedArray()) {
onSuccess { event.callback.onNext(true) }
onFailure {
event.callback.onNext(false)
event.callback.onError(SecurityException("User refused permissions"))
}
}
}
}
companion object {
private fun intent(context: Context) = Intent(context, ClassMap[FlashActivity::class.java])
......
......@@ -7,21 +7,20 @@ import android.net.Uri
import android.os.Handler
import androidx.core.os.postDelayed
import androidx.databinding.ObservableArrayList
import com.skoumal.teanity.databinding.ComparableRvItem
import com.skoumal.teanity.extensions.subscribeK
import com.skoumal.teanity.util.DiffObservableList
import com.skoumal.teanity.util.KObservableField
import com.skoumal.teanity.viewevents.SnackbarEvent
import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.Config
import com.topjohnwu.magisk.Const
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.base.viewmodel.BaseViewModel
import com.topjohnwu.magisk.databinding.ComparableRvItem
import com.topjohnwu.magisk.extensions.*
import com.topjohnwu.magisk.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.MagiskViewModel
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
......@@ -32,7 +31,7 @@ class FlashViewModel(
installer: Uri,
uri: Uri,
private val resources: Resources
) : MagiskViewModel(), FlashResultListener {
) : BaseViewModel(), FlashResultListener {
val canShowReboot = Shell.rootAccess()
val showRestartTitle = KObservableField(false)
......
package com.topjohnwu.magisk.ui.hide
import android.content.pm.ApplicationInfo
import com.skoumal.teanity.databinding.ComparableRvItem
import com.skoumal.teanity.extensions.addOnPropertyChangedCallback
import com.skoumal.teanity.extensions.subscribeK
import com.skoumal.teanity.rxbus.RxBus
import com.skoumal.teanity.util.DiffObservableList
import com.skoumal.teanity.util.KObservableField
import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.base.viewmodel.BaseViewModel
import com.topjohnwu.magisk.data.repository.MagiskRepository
import com.topjohnwu.magisk.databinding.ComparableRvItem
import com.topjohnwu.magisk.extensions.addOnPropertyChangedCallback
import com.topjohnwu.magisk.extensions.subscribeK
import com.topjohnwu.magisk.extensions.toSingle
import com.topjohnwu.magisk.extensions.update
import com.topjohnwu.magisk.model.entity.recycler.HideProcessRvItem
import com.topjohnwu.magisk.model.entity.recycler.HideRvItem
import com.topjohnwu.magisk.model.entity.state.IndeterminateState
import com.topjohnwu.magisk.model.events.HideProcessEvent
import com.topjohnwu.magisk.ui.base.MagiskViewModel
import com.topjohnwu.magisk.utils.DiffObservableList
import com.topjohnwu.magisk.utils.KObservableField
import com.topjohnwu.magisk.utils.RxBus
import me.tatarka.bindingcollectionadapter2.OnItemBind
import timber.log.Timber
class HideViewModel(
private val magiskRepo: MagiskRepository,
rxBus: RxBus
) : MagiskViewModel() {
) : BaseViewModel() {
val query = KObservableField("")
val isShowSystem = KObservableField(false)
......
......@@ -6,11 +6,11 @@ import android.view.MenuItem
import android.widget.SearchView
import com.topjohnwu.magisk.Config
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.base.BaseFragment
import com.topjohnwu.magisk.databinding.FragmentMagiskHideBinding
import com.topjohnwu.magisk.ui.base.MagiskFragment
import org.koin.androidx.viewmodel.ext.android.viewModel
class MagiskHideFragment : MagiskFragment<HideViewModel, FragmentMagiskHideBinding>(),
class MagiskHideFragment : BaseFragment<HideViewModel, FragmentMagiskHideBinding>(),
SearchView.OnQueryTextListener {
override val layoutRes: Int = R.layout.fragment_magisk_hide
......
package com.topjohnwu.magisk.ui.home
import android.content.Context
import com.skoumal.teanity.extensions.subscribeK
import com.skoumal.teanity.viewevents.ViewEvent
import com.topjohnwu.magisk.BuildConfig
import com.topjohnwu.magisk.Const
import com.topjohnwu.magisk.Info
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.base.BaseActivity
import com.topjohnwu.magisk.base.BaseFragment
import com.topjohnwu.magisk.data.repository.MagiskRepository
import com.topjohnwu.magisk.databinding.FragmentMagiskBinding
import com.topjohnwu.magisk.extensions.inject
import com.topjohnwu.magisk.extensions.DynamicClassLoader
import com.topjohnwu.magisk.extensions.openUrl
import com.topjohnwu.magisk.extensions.subscribeK
import com.topjohnwu.magisk.extensions.writeTo
import com.topjohnwu.magisk.model.events.*
import com.topjohnwu.magisk.ui.base.MagiskActivity
import com.topjohnwu.magisk.ui.base.MagiskFragment
import com.topjohnwu.magisk.utils.DynamicClassLoader
import com.topjohnwu.magisk.utils.SafetyNetHelper
import com.topjohnwu.magisk.view.MarkDownWindow
import com.topjohnwu.magisk.view.dialogs.*
import com.topjohnwu.superuser.Shell
import dalvik.system.DexFile
import io.reactivex.Completable
import org.koin.android.ext.android.inject
import org.koin.androidx.viewmodel.ext.android.viewModel
import java.io.File
import java.lang.reflect.InvocationHandler
class HomeFragment : MagiskFragment<HomeViewModel, FragmentMagiskBinding>(),
class HomeFragment : BaseFragment<HomeViewModel, FragmentMagiskBinding>(),
SafetyNetHelper.Callback {
override val layoutRes: Int = R.layout.fragment_magisk
......@@ -33,13 +33,14 @@ class HomeFragment : MagiskFragment<HomeViewModel, FragmentMagiskBinding>(),
private val magiskRepo: MagiskRepository by inject()
private val EXT_APK by lazy { File("${activity.filesDir.parent}/snet", "snet.jar") }
private val EXT_DEX by lazy { File(EXT_APK.parent, "snet.dex") }
override fun onResponse(responseCode: Int) = viewModel.finishSafetyNetCheck(responseCode)
override fun onEventDispatched(event: ViewEvent) {
super.onEventDispatched(event)
when (event) {
is OpenLinkEvent -> openLink(event.url)
is OpenLinkEvent -> activity.openUrl(event.url)
is ManagerInstallEvent -> installManager()
is MagiskInstallEvent -> installMagisk()
is UninstallEvent -> uninstall()
......@@ -62,7 +63,7 @@ class HomeFragment : MagiskFragment<HomeViewModel, FragmentMagiskBinding>(),
return
}
MagiskInstallDialog(requireActivity() as MagiskActivity<*, *>).show()
MagiskInstallDialog(requireActivity() as BaseActivity<*, *>).show()
}
private fun installManager() = ManagerInstallDialog(requireActivity()).show()
......@@ -94,7 +95,7 @@ class HomeFragment : MagiskFragment<HomeViewModel, FragmentMagiskBinding>(),
private fun updateSafetyNet(dieOnError: Boolean) {
Completable.fromAction {
val loader = DynamicClassLoader(EXT_APK)
val dex = DexFile.loadDex(EXT_APK.path, EXT_APK.parent, 0)
val dex = DexFile.loadDex(EXT_APK.path, EXT_DEX.path, 0)
// Scan through the dex and find our helper class
var helperClass: Class<*>? = null
......
package com.topjohnwu.magisk.ui.home
import android.content.pm.PackageManager
import com.skoumal.teanity.extensions.addOnPropertyChangedCallback
import com.skoumal.teanity.extensions.doOnSubscribeUi
import com.skoumal.teanity.extensions.subscribeK
import com.skoumal.teanity.util.KObservableField
import com.topjohnwu.magisk.*
import com.topjohnwu.magisk.base.viewmodel.BaseViewModel
import com.topjohnwu.magisk.data.repository.MagiskRepository
import com.topjohnwu.magisk.extensions.get
import com.topjohnwu.magisk.extensions.packageName
import com.topjohnwu.magisk.extensions.res
import com.topjohnwu.magisk.extensions.toggle
import com.topjohnwu.magisk.extensions.*
import com.topjohnwu.magisk.model.events.*
import com.topjohnwu.magisk.model.observer.Observer
import com.topjohnwu.magisk.ui.base.MagiskViewModel
import com.topjohnwu.magisk.utils.KObservableField
import com.topjohnwu.magisk.utils.SafetyNetHelper
import com.topjohnwu.superuser.Shell
import io.reactivex.Completable
enum class SafetyNetState {
LOADING, PASS, FAILED, IDLE
......@@ -31,7 +26,7 @@ enum class MagiskItem {
class HomeViewModel(
private val magiskRepo: MagiskRepository
) : MagiskViewModel(State.LOADED) {
) : BaseViewModel(State.LOADED) {
val hasGMS = runCatching {
get<PackageManager>().getPackageInfo("com.google.android.gms", 0); true
......@@ -43,10 +38,7 @@ class HomeViewModel(
val isKeepVerity = KObservableField(Info.keepVerity)
val isRecovery = KObservableField(Info.recovery)
private val _magiskState = KObservableField(MagiskState.LOADING)
val magiskState = Observer(_magiskState, isConnected) {
if (isConnected.value) _magiskState.value else MagiskState.UP_TO_DATE
}
val magiskState = KObservableField(MagiskState.LOADING)
val magiskStateText = Observer(magiskState) {
when (magiskState.value) {
MagiskState.NOT_INSTALLED -> R.string.magisk_version_error.res()
......@@ -179,24 +171,28 @@ class HomeViewModel(
}
fun refresh() {
refreshVersions()
hasRoot.value = Shell.rootAccess()
val fetchUpdate = if (isConnected.value)
magiskRepo.fetchUpdate().ignoreElement()
else
Completable.complete()
magiskRepo.fetchUpdate()
Completable.fromAction {
Info.loadMagiskInfo()
}.andThen(fetchUpdate)
.applyViewModel(this)
.doOnSubscribeUi {
_magiskState.value = MagiskState.LOADING
magiskState.value = MagiskState.LOADING
_managerState.value = MagiskState.LOADING
ctsState.value = SafetyNetState.IDLE
basicIntegrityState.value = SafetyNetState.IDLE
safetyNetTitle.value = R.string.safetyNet_check_text
}
.subscribeK {
}.subscribeK {
updateSelf()
ensureEnv()
refreshVersions()
}
hasRoot.value = Shell.rootAccess()
}
private fun refreshVersions() {
......@@ -211,7 +207,7 @@ class HomeViewModel(
}
private fun updateSelf() {
_magiskState.value = when (Info.magiskVersionCode) {
magiskState.value = when (Info.magiskVersionCode) {
in Int.MIN_VALUE until 0 -> MagiskState.NOT_INSTALLED
!in Info.remote.magisk.versionCode..Int.MAX_VALUE -> MagiskState.OBSOLETE
else -> MagiskState.UP_TO_DATE
......
......@@ -6,14 +6,14 @@ import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import com.skoumal.teanity.viewevents.ViewEvent
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.base.BaseFragment
import com.topjohnwu.magisk.databinding.FragmentLogBinding
import com.topjohnwu.magisk.model.events.PageChangedEvent
import com.topjohnwu.magisk.ui.base.MagiskFragment
import com.topjohnwu.magisk.model.events.ViewEvent
import org.koin.androidx.viewmodel.ext.android.viewModel
class LogFragment : MagiskFragment<LogViewModel, FragmentLogBinding>() {
class LogFragment : BaseFragment<LogViewModel, FragmentLogBinding>() {
override val layoutRes: Int = R.layout.fragment_log
override val viewModel: LogViewModel by viewModel()
......
package com.topjohnwu.magisk.ui.log
import android.content.res.Resources
import com.skoumal.teanity.databinding.ComparableRvItem
import com.skoumal.teanity.extensions.addOnPropertyChangedCallback
import com.skoumal.teanity.extensions.doOnSubscribeUi
import com.skoumal.teanity.extensions.subscribeK
import com.skoumal.teanity.util.DiffObservableList
import com.skoumal.teanity.util.KObservableField
import com.skoumal.teanity.viewevents.SnackbarEvent
import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.Config
import com.topjohnwu.magisk.Const
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.base.viewmodel.BaseViewModel
import com.topjohnwu.magisk.data.repository.LogRepository
import com.topjohnwu.magisk.databinding.ComparableRvItem
import com.topjohnwu.magisk.extensions.addOnPropertyChangedCallback
import com.topjohnwu.magisk.extensions.doOnSubscribeUi
import com.topjohnwu.magisk.extensions.subscribeK
import com.topjohnwu.magisk.model.binding.BindingAdapter
import com.topjohnwu.magisk.model.entity.recycler.ConsoleRvItem
import com.topjohnwu.magisk.model.entity.recycler.LogItemRvItem
import com.topjohnwu.magisk.model.entity.recycler.LogRvItem
import com.topjohnwu.magisk.model.entity.recycler.MagiskLogRvItem
import com.topjohnwu.magisk.model.events.PageChangedEvent
import com.topjohnwu.magisk.ui.base.MagiskViewModel
import com.topjohnwu.magisk.model.events.SnackbarEvent
import com.topjohnwu.magisk.utils.DiffObservableList
import com.topjohnwu.magisk.utils.KObservableField
import com.topjohnwu.superuser.Shell
import me.tatarka.bindingcollectionadapter2.BindingViewPagerAdapter
import me.tatarka.bindingcollectionadapter2.OnItemBind
......@@ -30,7 +30,7 @@ import java.util.*
class LogViewModel(
private val resources: Resources,
private val logRepo: LogRepository
) : MagiskViewModel(), BindingViewPagerAdapter.PageTitles<ComparableRvItem<*>> {
) : BaseViewModel(), BindingViewPagerAdapter.PageTitles<ComparableRvItem<*>> {
val itemsAdapter = BindingAdapter()
val items = DiffObservableList(ComparableRvItem.callback)
......
package com.topjohnwu.magisk.ui.module
import android.content.res.Resources
import com.skoumal.teanity.databinding.ComparableRvItem
import com.skoumal.teanity.extensions.addOnPropertyChangedCallback
import com.skoumal.teanity.extensions.doOnSuccessUi
import com.skoumal.teanity.extensions.subscribeK
import com.skoumal.teanity.util.DiffObservableList
import com.skoumal.teanity.util.KObservableField
import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.base.viewmodel.BaseViewModel
import com.topjohnwu.magisk.data.database.RepoDao
import com.topjohnwu.magisk.extensions.toSingle
import com.topjohnwu.magisk.extensions.update
import com.topjohnwu.magisk.databinding.ComparableRvItem
import com.topjohnwu.magisk.extensions.*
import com.topjohnwu.magisk.model.entity.module.Module
import com.topjohnwu.magisk.model.entity.recycler.ModuleRvItem
import com.topjohnwu.magisk.model.entity.recycler.RepoRvItem
......@@ -20,7 +15,8 @@ import com.topjohnwu.magisk.model.events.InstallModuleEvent
import com.topjohnwu.magisk.model.events.OpenChangelogEvent
import com.topjohnwu.magisk.model.events.OpenFilePickerEvent
import com.topjohnwu.magisk.tasks.RepoUpdater
import com.topjohnwu.magisk.ui.base.MagiskViewModel
import com.topjohnwu.magisk.utils.DiffObservableList
import com.topjohnwu.magisk.utils.KObservableField
import io.reactivex.Single
import io.reactivex.disposables.Disposable
import me.tatarka.bindingcollectionadapter2.OnItemBind
......@@ -29,7 +25,7 @@ class ModuleViewModel(
private val resources: Resources,
private val repoUpdater: RepoUpdater,
private val repoDB: RepoDao
) : MagiskViewModel() {
) : BaseViewModel() {
val query = KObservableField("")
......
......@@ -8,19 +8,19 @@ import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import com.skoumal.teanity.viewevents.ViewEvent
import com.topjohnwu.magisk.ClassMap
import com.topjohnwu.magisk.Const
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.base.BaseFragment
import com.topjohnwu.magisk.databinding.FragmentModulesBinding
import com.topjohnwu.magisk.extensions.reboot
import com.topjohnwu.magisk.model.events.OpenFilePickerEvent
import com.topjohnwu.magisk.ui.base.MagiskFragment
import com.topjohnwu.magisk.model.events.ViewEvent
import com.topjohnwu.magisk.ui.flash.FlashActivity
import com.topjohnwu.superuser.Shell
import org.koin.androidx.viewmodel.ext.android.sharedViewModel
class ModulesFragment : MagiskFragment<ModuleViewModel, FragmentModulesBinding>() {
class ModulesFragment : BaseFragment<ModuleViewModel, FragmentModulesBinding>() {
override val layoutRes: Int = R.layout.fragment_modules
override val viewModel: ModuleViewModel by sharedViewModel()
......
......@@ -6,9 +6,9 @@ import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.widget.SearchView
import com.skoumal.teanity.viewevents.ViewEvent
import com.topjohnwu.magisk.Config
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.base.BaseFragment
import com.topjohnwu.magisk.databinding.FragmentReposBinding
import com.topjohnwu.magisk.model.download.DownloadService
import com.topjohnwu.magisk.model.entity.internal.Configuration
......@@ -16,12 +16,12 @@ import com.topjohnwu.magisk.model.entity.internal.DownloadSubject
import com.topjohnwu.magisk.model.entity.module.Repo
import com.topjohnwu.magisk.model.events.InstallModuleEvent
import com.topjohnwu.magisk.model.events.OpenChangelogEvent
import com.topjohnwu.magisk.ui.base.MagiskFragment
import com.topjohnwu.magisk.model.events.ViewEvent
import com.topjohnwu.magisk.view.MarkDownWindow
import com.topjohnwu.magisk.view.dialogs.CustomAlertDialog
import org.koin.androidx.viewmodel.ext.android.sharedViewModel
class ReposFragment : MagiskFragment<ModuleViewModel, FragmentReposBinding>(),
class ReposFragment : BaseFragment<ModuleViewModel, FragmentReposBinding>(),
SearchView.OnQueryTextListener {
override val layoutRes: Int = R.layout.fragment_repos
......
......@@ -13,21 +13,20 @@ import androidx.preference.ListPreference
import androidx.preference.Preference
import androidx.preference.PreferenceCategory
import androidx.preference.SwitchPreferenceCompat
import com.skoumal.teanity.extensions.subscribeK
import com.skoumal.teanity.util.KObservableField
import com.topjohnwu.magisk.BuildConfig
import com.topjohnwu.magisk.Config
import com.topjohnwu.magisk.Const
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.base.BasePreferenceFragment
import com.topjohnwu.magisk.data.database.RepoDao
import com.topjohnwu.magisk.databinding.CustomDownloadDialogBinding
import com.topjohnwu.magisk.extensions.subscribeK
import com.topjohnwu.magisk.extensions.toLangTag
import com.topjohnwu.magisk.model.download.DownloadService
import com.topjohnwu.magisk.model.entity.internal.Configuration
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject
import com.topjohnwu.magisk.model.observer.Observer
import com.topjohnwu.magisk.net.Networking
import com.topjohnwu.magisk.ui.base.BasePreferenceFragment
import com.topjohnwu.magisk.utils.*
import com.topjohnwu.magisk.view.dialogs.FingerprintAuthDialog
import com.topjohnwu.superuser.Shell
......@@ -204,7 +203,7 @@ class SettingsFragment : BasePreferenceFragment() {
Shell.su("magiskhide --disable").submit()
}
Config.Key.LOCALE -> {
LocaleManager.setLocale(activity.application)
ResourceMgr.reload()
activity.recreate()
}
Config.Key.CHECK_UPDATES -> Utils.scheduleUpdateCheck(activity)
......@@ -233,7 +232,7 @@ class SettingsFragment : BasePreferenceFragment() {
val values = mutableListOf<String>()
names.add(
LocaleManager.getString(defaultLocale, R.string.system_default)
ResourceMgr.getString(defaultLocale, R.string.system_default)
)
values.add("")
......
package com.topjohnwu.magisk.ui.superuser
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.base.BaseFragment
import com.topjohnwu.magisk.databinding.FragmentSuperuserBinding
import com.topjohnwu.magisk.ui.base.MagiskFragment
import org.koin.androidx.viewmodel.ext.android.viewModel
class SuperuserFragment :
MagiskFragment<SuperuserViewModel, FragmentSuperuserBinding>() {
BaseFragment<SuperuserViewModel, FragmentSuperuserBinding>() {
override val layoutRes: Int = R.layout.fragment_superuser
override val viewModel: SuperuserViewModel by viewModel()
......
......@@ -2,22 +2,22 @@ package com.topjohnwu.magisk.ui.superuser
import android.content.pm.PackageManager
import android.content.res.Resources
import com.skoumal.teanity.databinding.ComparableRvItem
import com.skoumal.teanity.extensions.applySchedulers
import com.skoumal.teanity.extensions.subscribeK
import com.skoumal.teanity.rxbus.RxBus
import com.skoumal.teanity.util.DiffObservableList
import com.skoumal.teanity.viewevents.SnackbarEvent
import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.base.viewmodel.BaseViewModel
import com.topjohnwu.magisk.data.database.PolicyDao
import com.topjohnwu.magisk.databinding.ComparableRvItem
import com.topjohnwu.magisk.extensions.applySchedulers
import com.topjohnwu.magisk.extensions.subscribeK
import com.topjohnwu.magisk.extensions.toggle
import com.topjohnwu.magisk.model.entity.MagiskPolicy
import com.topjohnwu.magisk.model.entity.recycler.PolicyRvItem
import com.topjohnwu.magisk.model.events.PolicyEnableEvent
import com.topjohnwu.magisk.model.events.PolicyUpdateEvent
import com.topjohnwu.magisk.ui.base.MagiskViewModel
import com.topjohnwu.magisk.model.events.SnackbarEvent
import com.topjohnwu.magisk.utils.DiffObservableList
import com.topjohnwu.magisk.utils.FingerprintHelper
import com.topjohnwu.magisk.utils.RxBus
import com.topjohnwu.magisk.view.dialogs.CustomAlertDialog
import com.topjohnwu.magisk.view.dialogs.FingerprintAuthDialog
import io.reactivex.Single
......@@ -29,7 +29,7 @@ class SuperuserViewModel(
private val packageManager: PackageManager,
private val resources: Resources,
rxBus: RxBus
) : MagiskViewModel() {
) : BaseViewModel() {
val items = DiffObservableList(ComparableRvItem.callback)
val itemBinding = ItemBinding.of<ComparableRvItem<*>> { itemBinding, _, item ->
......
......@@ -5,19 +5,20 @@ import android.os.Build
import android.os.Bundle
import android.text.TextUtils
import android.view.Window
import com.skoumal.teanity.viewevents.ViewEvent
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.base.BaseActivity
import com.topjohnwu.magisk.databinding.ActivityRequestBinding
import com.topjohnwu.magisk.model.entity.MagiskPolicy
import com.topjohnwu.magisk.model.events.DieEvent
import com.topjohnwu.magisk.model.events.ViewEvent
import com.topjohnwu.magisk.model.receiver.GeneralReceiver
import com.topjohnwu.magisk.ui.base.MagiskActivity
import com.topjohnwu.magisk.utils.SuLogger
import org.koin.androidx.viewmodel.ext.android.viewModel
open class SuRequestActivity : MagiskActivity<SuRequestViewModel, ActivityRequestBinding>() {
open class SuRequestActivity : BaseActivity<SuRequestViewModel, ActivityRequestBinding>() {
override val layoutRes: Int = R.layout.activity_request
override val themeRes: Int = R.style.MagiskTheme_SU
override val viewModel: SuRequestViewModel by viewModel()
override fun onBackPressed() {
......
......@@ -9,21 +9,21 @@ import android.graphics.drawable.Drawable
import android.hardware.fingerprint.FingerprintManager
import android.os.CountDownTimer
import android.text.TextUtils
import com.skoumal.teanity.databinding.ComparableRvItem
import com.skoumal.teanity.extensions.addOnPropertyChangedCallback
import com.skoumal.teanity.util.DiffObservableList
import com.skoumal.teanity.util.KObservableField
import com.topjohnwu.magisk.BuildConfig
import com.topjohnwu.magisk.Config
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.base.viewmodel.BaseViewModel
import com.topjohnwu.magisk.data.database.PolicyDao
import com.topjohnwu.magisk.databinding.ComparableRvItem
import com.topjohnwu.magisk.extensions.addOnPropertyChangedCallback
import com.topjohnwu.magisk.extensions.now
import com.topjohnwu.magisk.model.entity.MagiskPolicy
import com.topjohnwu.magisk.model.entity.recycler.SpinnerRvItem
import com.topjohnwu.magisk.model.entity.toPolicy
import com.topjohnwu.magisk.model.events.DieEvent
import com.topjohnwu.magisk.ui.base.MagiskViewModel
import com.topjohnwu.magisk.utils.DiffObservableList
import com.topjohnwu.magisk.utils.FingerprintHelper
import com.topjohnwu.magisk.utils.KObservableField
import com.topjohnwu.magisk.utils.SuConnector
import me.tatarka.bindingcollectionadapter2.BindingListViewAdapter
import me.tatarka.bindingcollectionadapter2.ItemBinding
......@@ -36,7 +36,7 @@ class SuRequestViewModel(
private val policyDB: PolicyDao,
private val timeoutPrefs: SharedPreferences,
private val resources: Resources
) : MagiskViewModel() {
) : BaseViewModel() {
val icon = KObservableField<Drawable?>(null)
val title = KObservableField("")
......
......@@ -21,9 +21,9 @@ import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager.widget.ViewPager
import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.google.android.material.navigation.NavigationView
import com.skoumal.teanity.extensions.subscribeK
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.extensions.replaceRandomWithSpecial
import com.topjohnwu.magisk.extensions.subscribeK
import com.topjohnwu.magisk.model.entity.state.IndeterminateState
import io.reactivex.Observable
import io.reactivex.disposables.Disposable
......
package com.topjohnwu.magisk.utils
import androidx.annotation.MainThread
import androidx.databinding.ListChangeRegistry
import androidx.databinding.ObservableList
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListUpdateCallback
import java.util.*
import kotlin.collections.ArrayList
/**
* @param callback The callback that controls the behavior of the DiffObservableList.
* @param detectMoves True if DiffUtil should try to detect moved items, false otherwise.
*/
open class DiffObservableList<T>(
private val callback: Callback<T>,
private val detectMoves: Boolean = true
) : AbstractList<T>(), ObservableList<T> {
private val LIST_LOCK = Object()
private var list: MutableList<T> = ArrayList()
private val listeners = ListChangeRegistry()
private val listCallback = ObservableListUpdateCallback()
override val size: Int get() = list.size
/**
* Calculates the list of update operations that can convert this list into the given one.
*
* @param newItems The items that this list will be set to.
* @return A DiffResult that contains the information about the edit sequence to covert this
* list into the given one.
*/
fun calculateDiff(newItems: List<T>): DiffUtil.DiffResult {
val frozenList = synchronized(LIST_LOCK) {
ArrayList(list)
}
return doCalculateDiff(frozenList, newItems)
}
private fun doCalculateDiff(oldItems: List<T>, newItems: List<T>?): DiffUtil.DiffResult {
return DiffUtil.calculateDiff(object : DiffUtil.Callback() {
override fun getOldListSize() = oldItems.size
override fun getNewListSize() = newItems?.size ?: 0
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
val oldItem = oldItems[oldItemPosition]
val newItem = newItems!![newItemPosition]
return callback.areItemsTheSame(oldItem, newItem)
}
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
val oldItem = oldItems[oldItemPosition]
val newItem = newItems!![newItemPosition]
return callback.areContentsTheSame(oldItem, newItem)
}
}, detectMoves)
}
/**
* Updates the contents of this list to the given one using the DiffResults to dispatch change
* notifications.
*
* @param newItems The items to set this list to.
* @param diffResult The diff results to dispatch change notifications.
*/
@MainThread
fun update(newItems: List<T>, diffResult: DiffUtil.DiffResult) {
synchronized(LIST_LOCK) {
list = newItems.toMutableList()
}
diffResult.dispatchUpdatesTo(listCallback)
}
/**
* Sets this list to the given items. This is a convenience method for calling [ ][.calculateDiff] followed by [.update].
*
*
* **Warning!** If the lists are large this operation may be too slow for the main thread. In
* that case, you should call [.calculateDiff] on a background thread and then
* [.update] on the main thread.
*
* @param newItems The items to set this list to.
*/
@MainThread
fun update(newItems: List<T>) {
val diffResult = doCalculateDiff(list, newItems)
list = newItems.toMutableList()
diffResult.dispatchUpdatesTo(listCallback)
}
override fun addOnListChangedCallback(listener: ObservableList.OnListChangedCallback<out ObservableList<T>>) {
listeners.add(listener)
}
override fun removeOnListChangedCallback(listener: ObservableList.OnListChangedCallback<out ObservableList<T>>) {
listeners.remove(listener)
}
override fun get(index: Int): T {
return list[index]
}
override fun add(element: T): Boolean {
list.add(element)
notifyAdd(size - 1, 1)
return true
}
override fun add(index: Int, element: T) {
list.add(index, element)
notifyAdd(index, 1)
}
override fun addAll(elements: Collection<T>): Boolean {
val oldSize = size
val added = list.addAll(elements)
if (added) {
notifyAdd(oldSize, size - oldSize)
}
return added
}
override fun addAll(index: Int, elements: Collection<T>): Boolean {
val added = list.addAll(index, elements)
if (added) {
notifyAdd(index, elements.size)
}
return added
}
override fun clear() {
val oldSize = size
list.clear()
if (oldSize != 0) {
notifyRemove(0, oldSize)
}
}
override fun remove(element: T): Boolean {
val index = indexOf(element)
return if (index >= 0) {
removeAt(index)
true
} else {
false
}
}
override fun removeAt(index: Int): T {
val element = list.removeAt(index)
notifyRemove(index, 1)
return element
}
fun removeLast(): T? {
if (size > 0) {
val index = size - 1
return removeAt(index)
}
return null
}
override fun set(index: Int, element: T): T {
val old = list.set(index, element)
listeners.notifyChanged(this, index, 1)
return old
}
private fun notifyAdd(start: Int, count: Int) {
listeners.notifyInserted(this, start, count)
}
private fun notifyRemove(start: Int, count: Int) {
listeners.notifyRemoved(this, start, count)
}
/**
* A Callback class used by DiffUtil while calculating the diff between two lists.
*/
interface Callback<T> {
/**
* Called by the DiffUtil to decide whether two object represent the same Item.
*
*
* For example, if your items have unique ids, this method should check their id equality.
*
* @param oldItem The old item.
* @param newItem The new item.
* @return True if the two items represent the same object or false if they are different.
*/
fun areItemsTheSame(oldItem: T, newItem: T): Boolean
/**
* Called by the DiffUtil when it wants to check whether two items have the same data.
* DiffUtil uses this information to detect if the contents of an item has changed.
*
*
* DiffUtil uses this method to check equality instead of [Object.equals] so
* that you can change its behavior depending on your UI.
*
*
* This method is called only if [.areItemsTheSame] returns `true` for
* these items.
*
* @param oldItem The old item.
* @param newItem The new item which replaces the old item.
* @return True if the contents of the items are the same or false if they are different.
*/
fun areContentsTheSame(oldItem: T, newItem: T): Boolean
}
inner class ObservableListUpdateCallback : ListUpdateCallback {
override fun onChanged(position: Int, count: Int, payload: Any?) {
listeners.notifyChanged(this@DiffObservableList, position, count)
}
override fun onMoved(fromPosition: Int, toPosition: Int) {
listeners.notifyMoved(this@DiffObservableList, fromPosition, toPosition, 1)
}
override fun onInserted(position: Int, count: Int) {
modCount += 1
listeners.notifyInserted(this@DiffObservableList, position, count)
}
override fun onRemoved(position: Int, count: Int) {
modCount += 1
listeners.notifyRemoved(this@DiffObservableList, position, count)
}
}
}
\ No newline at end of file
package com.topjohnwu.magisk.utils
import dalvik.system.DexClassLoader
import java.io.File
import java.io.IOException
import java.net.URL
import java.util.*
@Suppress("FunctionName")
inline fun <reified T> T.DynamicClassLoader(apk: File) = DynamicClassLoader(apk, T::class.java.classLoader)
class DynamicClassLoader(apk: File, parent: ClassLoader?)
: DexClassLoader(apk.path, apk.parent, null, parent) {
private val base by lazy { Any::class.java.classLoader!! }
@Throws(ClassNotFoundException::class)
override fun loadClass(name: String, resolve: Boolean) : Class<*>
= findLoadedClass(name) ?: runCatching {
base.loadClass(name)
}.getOrElse {
runCatching {
findClass(name)
}.getOrElse { err ->
runCatching {
parent.loadClass(name)
}.getOrElse { throw err }
}
}
override fun getResource(name: String) = base.getResource(name)
?: findResource(name)
?: parent?.getResource(name)
@Throws(IOException::class)
override fun getResources(name: String): Enumeration<URL> {
val resources = mutableListOf(
base.getResources(name),
findResources(name), parent.getResources(name))
return object : Enumeration<URL> {
override fun hasMoreElements(): Boolean {
while (true) {
if (resources.isEmpty())
return false
if (!resources[0].hasMoreElements()) {
resources.removeAt(0)
} else {
return true
}
}
}
override fun nextElement(): URL {
if (!hasMoreElements())
throw NoSuchElementException()
return resources[0].nextElement()
}
}
}
}
package com.topjohnwu.magisk.utils
import android.content.Context
import android.graphics.Canvas
import android.graphics.Rect
import android.graphics.drawable.Drawable
import android.view.View
import androidx.annotation.DrawableRes
import androidx.core.view.get
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.RecyclerView
import com.topjohnwu.magisk.extensions.drawableCompat
class KItemDecoration(
private val context: Context,
@RecyclerView.Orientation private val orientation: Int
) :
RecyclerView.ItemDecoration() {
private val bounds = Rect()
private var divider: Drawable? = null
var showAfterLast = true
fun setDeco(@DrawableRes drawable: Int) = apply {
setDeco(context.drawableCompat(drawable))
}
fun setDeco(drawable: Drawable?) = apply {
divider = drawable
}
override fun onDraw(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) {
parent.layoutManager ?: return
divider?.let {
if (orientation == DividerItemDecoration.VERTICAL) {
drawVertical(canvas, parent, it)
} else {
drawHorizontal(canvas, parent, it)
}
}
}
private fun drawVertical(canvas: Canvas, parent: RecyclerView, drawable: Drawable) {
canvas.save()
val left: Int
val right: Int
if (parent.clipToPadding) {
left = parent.paddingLeft
right = parent.width - parent.paddingRight
canvas.clipRect(
left, parent.paddingTop, right,
parent.height - parent.paddingBottom
)
} else {
left = 0
right = parent.width
}
val to = if (showAfterLast) parent.childCount else parent.childCount - 1
(0 until to)
.map { parent[it] }
.forEach { child ->
parent.getDecoratedBoundsWithMargins(child, bounds)
val bottom = bounds.bottom + Math.round(child.translationY)
val top = bottom - drawable.intrinsicHeight
drawable.setBounds(left, top, right, bottom)
drawable.draw(canvas)
}
canvas.restore()
}
private fun drawHorizontal(canvas: Canvas, parent: RecyclerView, drawable: Drawable) {
canvas.save()
val top: Int
val bottom: Int
if (parent.clipToPadding) {
top = parent.paddingTop
bottom = parent.height - parent.paddingBottom
canvas.clipRect(
parent.paddingLeft, top,
parent.width - parent.paddingRight, bottom
)
} else {
top = 0
bottom = parent.height
}
val to = if (showAfterLast) parent.childCount else parent.childCount - 1
(0 until to)
.map { parent[it] }
.forEach { child ->
parent.layoutManager!!.getDecoratedBoundsWithMargins(child, bounds)
val right = bounds.right + Math.round(child.translationX)
val left = right - drawable.intrinsicWidth
drawable.setBounds(left, top, right, bottom)
drawable.draw(canvas)
}
canvas.restore()
}
override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
if (parent.getChildAdapterPosition(view) == state.itemCount - 1) {
outRect.setEmpty()
return
}
if (orientation == RecyclerView.VERTICAL) {
outRect.set(0, 0, 0, divider?.intrinsicHeight ?: 0)
} else {
outRect.set(0, 0, divider?.intrinsicWidth ?: 0, 0)
}
}
}
\ No newline at end of file
package com.topjohnwu.magisk.utils
import androidx.databinding.Observable
import androidx.databinding.ObservableField
import java.io.Serializable
/**
* Kotlin version of [ObservableField].
* You can define if wrapped type is Nullable or not.
* You can use kotlin get/set syntax for value
*/
class KObservableField<T> : ObservableField<T>, Serializable {
var value: T
set(value) {
if (field != value) {
field = value
notifyChange()
}
}
constructor(init: T) {
value = init
}
constructor(init: T, vararg dependencies: Observable) : super(*dependencies) {
value = init
}
@Deprecated(
message = "Needed for data binding, use KObservableField.value syntax from code",
replaceWith = ReplaceWith("value")
)
override fun get(): T {
return value
}
@Deprecated(
message = "Needed for data binding, use KObservableField.value = ... syntax from code",
replaceWith = ReplaceWith("value = newValue")
)
override fun set(newValue: T) {
value = newValue
}
override fun toString(): String {
return "KObservableField(value=$value)"
}
}
\ No newline at end of file
......@@ -3,8 +3,8 @@ package com.topjohnwu.magisk.utils
import android.content.ComponentName
import android.content.Context
import android.widget.Toast
import com.skoumal.teanity.extensions.subscribeK
import com.topjohnwu.magisk.*
import com.topjohnwu.magisk.extensions.subscribeK
import com.topjohnwu.magisk.ui.SplashActivity
import com.topjohnwu.magisk.view.Notifications
import com.topjohnwu.signing.JarMap
......@@ -102,7 +102,7 @@ object PatchAPK {
Config.suManager = pkg
Config.export()
RootUtils.rmAndLaunch(BuildConfig.APPLICATION_ID,
Utils.rmAndLaunch(BuildConfig.APPLICATION_ID,
ComponentName(pkg, ClassMap.get<Class<*>>(SplashActivity::class.java).name))
return true
......
@file:Suppress("DEPRECATION")
package com.topjohnwu.magisk.utils
import android.annotation.SuppressLint
import android.content.Context
import android.content.ContextWrapper
import android.content.res.AssetManager
import android.content.res.Configuration
import android.content.res.Resources
import androidx.annotation.StringRes
import com.topjohnwu.magisk.Config
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.extensions.get
import com.topjohnwu.magisk.extensions.inject
import com.topjohnwu.magisk.extensions.langTagToLocale
import com.topjohnwu.superuser.internal.InternalUtils
import io.reactivex.Single
import java.util.*
var currentLocale = Locale.getDefault()!!
var isRunningAsStub = false
var currentLocale: Locale = Locale.getDefault()
private set
val defaultLocale = Locale.getDefault()!!
@SuppressLint("ConstantLocale")
val defaultLocale: Locale = Locale.getDefault()
val availableLocales = Single.fromCallable {
val compareId = R.string.app_changelog
val res: Resources by inject()
mutableListOf<Locale>().apply {
// Add default locale
add(Locale.ENGLISH)
......@@ -30,10 +33,18 @@ val availableLocales = Single.fromCallable {
add(Locale.TAIWAN)
add(Locale("pt", "BR"))
val config = Configuration()
val metrics = ResourceMgr.resource.displayMetrics
val res = Resources(ResourceMgr.resource.assets, metrics, config)
// Other locales
val otherLocales = res.assets.locales
val otherLocales = ResourceMgr.resource.assets.locales
.map { it.langTagToLocale() }
.distinctBy { LocaleManager.getString(it, compareId) }
.distinctBy {
config.setLocale(it)
res.updateConfiguration(config, metrics)
res.getString(compareId)
}
listOf("", "").toTypedArray()
......@@ -44,25 +55,72 @@ val availableLocales = Single.fromCallable {
})
}.cache()!!
object LocaleManager {
private val addAssetPath by lazy {
AssetManager::class.java.getMethod("addAssetPath", String::class.java)
}
fun AssetManager.addAssetPath(path: String) {
addAssetPath.invoke(this, path)
}
fun setLocale(wrapper: ContextWrapper) {
fun Context.wrap(global: Boolean = true): Context
= if (!global) ResourceMgr.ResContext(this) else ResourceMgr.GlobalResContext(this)
object ResourceMgr {
lateinit var resource: Resources
private lateinit var resApk: String
fun init(context: Context) {
resource = context.resources
if (isRunningAsStub)
resApk = DynAPK.current(context).path
}
// Override locale and inject resources from dynamic APK
private fun Resources.patch(config: Configuration = Configuration(configuration)): Resources {
config.setLocale(currentLocale)
updateConfiguration(config, displayMetrics)
if (isRunningAsStub)
assets.addAssetPath(resApk)
return this
}
fun reload(config: Configuration = Configuration(resource.configuration)) {
val localeConfig = Config.locale
currentLocale = when {
localeConfig.isEmpty() -> defaultLocale
else -> localeConfig.langTagToLocale()
}
Locale.setDefault(currentLocale)
InternalUtils.replaceBaseContext(wrapper, getLocaleContext(wrapper, currentLocale))
resource.patch(config)
}
fun getLocaleContext(context: Context, locale: Locale = currentLocale): Context {
val config = Configuration(context.resources.configuration)
fun getString(locale: Locale, @StringRes id: Int): String {
val config = Configuration()
config.setLocale(locale)
return context.createConfigurationContext(config)
return Resources(resource.assets, resource.displayMetrics, config).getString(id)
}
fun getString(locale: Locale, @StringRes id: Int): String {
return getLocaleContext(get(), locale).getString(id)
open class GlobalResContext(base: Context) : ContextWrapper(base) {
open val mRes: Resources get() = resource
private val loader by lazy { javaClass.classLoader!! }
override fun getResources(): Resources {
return mRes
}
override fun getClassLoader(): ClassLoader {
return loader
}
override fun createConfigurationContext(config: Configuration): Context {
return ResContext(super.createConfigurationContext(config))
}
}
class ResContext(base: Context) : GlobalResContext(base) {
override val mRes by lazy { base.resources.patch() }
}
}
package com.topjohnwu.magisk.utils
import android.content.Context
import com.topjohnwu.magisk.Const
import com.topjohnwu.magisk.Info
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.extensions.rawResource
import com.topjohnwu.superuser.Shell
import com.topjohnwu.superuser.ShellUtils
import com.topjohnwu.superuser.io.SuFile
class RootInit : Shell.Initializer() {
override fun onInit(context: Context, shell: Shell): Boolean {
return init(context.wrap(), shell)
}
fun init(context: Context, shell: Shell): Boolean {
val job = shell.newJob()
if (shell.isRoot) {
job.add(context.rawResource(R.raw.util_functions))
.add(context.rawResource(R.raw.utils))
Const.MAGISK_DISABLE_FILE = SuFile("/cache/.disable_magisk")
Info.loadMagiskInfo()
} else {
job.add(context.rawResource(R.raw.nonroot_utils))
}
job.add("mount_partitions",
"get_flags",
"run_migrations",
"export BOOTMODE=true")
.exec()
Info.keepVerity = ShellUtils.fastCmd("echo \$KEEPVERITY").toBoolean()
Info.keepEnc = ShellUtils.fastCmd("echo \$KEEPFORCEENCRYPT").toBoolean()
Info.recovery = ShellUtils.fastCmd("echo \$RECOVERYMODE").toBoolean()
return true
}
}
package com.topjohnwu.magisk.utils
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.net.Uri
import com.topjohnwu.magisk.Const
import com.topjohnwu.magisk.Info
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.extensions.rawResource
import com.topjohnwu.magisk.extensions.toShellCmd
import com.topjohnwu.superuser.Shell
import com.topjohnwu.superuser.ShellUtils
import com.topjohnwu.superuser.io.SuFile
import java.util.*
import java.lang.reflect.Array as RArray
fun Intent.toCommand(args: MutableList<String>) {
if (action != null) {
args.add("-a")
args.add(action!!)
}
if (component != null) {
args.add("-n")
args.add(component!!.flattenToString())
}
if (data != null) {
args.add("-d")
args.add(dataString!!)
}
if (categories != null) {
for (cat in categories) {
args.add("-c")
args.add(cat)
}
}
if (type != null) {
args.add("-t")
args.add(type!!)
}
val extras = extras
if (extras != null) {
loop@ for (key in extras.keySet()) {
val v = extras.get(key) ?: continue
var value: Any = v
val arg: String
when {
v is String -> arg = "--es"
v is Boolean -> arg = "--ez"
v is Int -> arg = "--ei"
v is Long -> arg = "--el"
v is Float -> arg = "--ef"
v is Uri -> arg = "--eu"
v is ComponentName -> {
arg = "--ecn"
value = v.flattenToString()
}
v is ArrayList<*> -> {
if (v.size <= 0)
/* Impossible to know the type due to type erasure */
continue@loop
arg = if (v[0] is Int)
"--eial"
else if (v[0] is Long)
"--elal"
else if (v[0] is Float)
"--efal"
else if (v[0] is String)
"--esal"
else
continue@loop /* Unsupported */
val sb = StringBuilder()
for (o in v) {
sb.append(o.toString().replace(",", "\\,"))
sb.append(',')
}
// Remove trailing comma
sb.deleteCharAt(sb.length - 1)
value = sb
}
v.javaClass.isArray -> {
arg = if (v is IntArray)
"--eia"
else if (v is LongArray)
"--ela"
else if (v is FloatArray)
"--efa"
else if (v is Array<*> && v.isArrayOf<String>())
"--esa"
else
continue@loop /* Unsupported */
val sb = StringBuilder()
val len = RArray.getLength(v)
for (i in 0 until len) {
sb.append(RArray.get(v, i)!!.toString().replace(",", "\\,"))
sb.append(',')
}
// Remove trailing comma
sb.deleteCharAt(sb.length - 1)
value = sb
}
else -> continue@loop
} /* Unsupported */
args.add(arg)
args.add(key)
args.add(value.toString())
}
}
args.add("-f")
args.add(flags.toString())
}
fun startActivity(intent: Intent) {
if (intent.component == null)
return
val args = ArrayList<String>()
args.add("am")
args.add("start")
intent.toCommand(args)
Shell.su(args.toShellCmd()).exec()
}
class RootUtils : Shell.Initializer() {
override fun onInit(context: Context, shell: Shell): Boolean {
val job = shell.newJob()
if (shell.isRoot) {
job.add(context.rawResource(R.raw.util_functions))
.add(context.rawResource(R.raw.utils))
Const.MAGISK_DISABLE_FILE = SuFile("/cache/.disable_magisk")
Info.loadMagiskInfo()
} else {
job.add(context.rawResource(R.raw.nonroot_utils))
}
job.add("mount_partitions",
"get_flags",
"run_migrations",
"export BOOTMODE=true")
.exec()
Info.keepVerity = ShellUtils.fastCmd("echo \$KEEPVERITY").toBoolean()
Info.keepEnc = ShellUtils.fastCmd("echo \$KEEPFORCEENCRYPT").toBoolean()
Info.recovery = ShellUtils.fastCmd("echo \$RECOVERYMODE").toBoolean()
return true
}
companion object {
fun rmAndLaunch(rm: String, component: ComponentName) {
Shell.su("(rm_launch $rm ${component.flattenToString()})").exec()
}
}
}
package com.topjohnwu.magisk.utils
import io.reactivex.Observable
import io.reactivex.subjects.PublishSubject
class RxBus {
private val _bus = PublishSubject.create<Event>()
val bus: Observable<Event> get() = _bus
fun post(event: Event) {
_bus.onNext(event)
}
fun post(event: Int) {
_bus.onNext(SimpleEvent(event))
}
inline fun <reified T : Event> register(noinline predicate: (T) -> Boolean = { true }): Observable<T> {
return bus
.ofType(T::class.java)
.filter(predicate)
}
fun register(eventId: Int): Observable<Int> {
return bus
.ofType(SimpleEvent::class.java)
.map { it.eventId }
.filter { it == eventId }
}
interface Event
private class SimpleEvent(val eventId: Int) : Event
}
\ No newline at end of file
package com.topjohnwu.magisk.utils
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.res.Resources
......@@ -72,4 +73,8 @@ object Utils {
if ((exists() && isDirectory) || mkdirs()) this else null
}
fun rmAndLaunch(rm: String, component: ComponentName) {
Shell.su("(rm_launch $rm ${component.flattenToString()})").exec()
}
}
......@@ -4,23 +4,22 @@ import android.content.Context
import android.view.LayoutInflater
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import com.skoumal.teanity.extensions.subscribeK
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.data.repository.StringRepository
import com.topjohnwu.magisk.extensions.inject
import com.topjohnwu.magisk.extensions.subscribeK
import io.noties.markwon.Markwon
import io.reactivex.Completable
import io.reactivex.Single
import ru.noties.markwon.Markwon
import ru.noties.markwon.html.HtmlPlugin
import ru.noties.markwon.image.ImagesPlugin
import ru.noties.markwon.image.svg.SvgPlugin
import org.koin.core.KoinComponent
import org.koin.core.inject
import timber.log.Timber
import java.io.InputStream
import java.util.*
object MarkDownWindow {
object MarkDownWindow : KoinComponent {
private val stringRepo: StringRepository by inject()
private val markwon: Markwon by inject()
fun show(activity: Context, title: String?, url: String) {
show(activity, title, stringRepo.getString(url))
......@@ -35,25 +34,14 @@ object MarkDownWindow {
}
fun show(activity: Context, title: String?, content: Single<String>) {
val markwon = Markwon.builder(activity)
.usePlugin(HtmlPlugin.create())
.usePlugin(ImagesPlugin.create(activity))
.usePlugin(SvgPlugin.create(activity.resources))
.build()
val mv = LayoutInflater.from(activity).inflate(R.layout.markdown_window, null)
val tv = mv.findViewById<TextView>(R.id.md_txt)
content.map {
runCatching {
markwon.setMarkdown(tv, it)
}.onFailure {
Timber.e(it)
// Always wrap the actual exception as it could be ExceptionInInitializerError,
// which is a fatal error and RxJava will send it to the global handler and crash
throw MarkwonException(it)
}
}.ignoreElement().onErrorResumeNext {
// Nothing we can actually do other than show error message
Timber.e(it)
tv.setText(R.string.download_file_error)
Completable.complete()
}.subscribeK {
......@@ -64,6 +52,4 @@ object MarkDownWindow {
.show()
}
}
class MarkwonException(e: Throwable): Exception(e)
}
......@@ -8,9 +8,9 @@ import com.topjohnwu.magisk.Info
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.extensions.cachedFile
import com.topjohnwu.magisk.extensions.reboot
import com.topjohnwu.magisk.net.Networking
import com.topjohnwu.magisk.tasks.MagiskInstaller
import com.topjohnwu.magisk.utils.Utils
import com.topjohnwu.magisk.net.Networking
import com.topjohnwu.superuser.Shell
import com.topjohnwu.superuser.ShellUtils
import com.topjohnwu.superuser.internal.UiThreadHandler
......
......@@ -7,13 +7,13 @@ import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import com.topjohnwu.magisk.Const
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.base.BaseActivity
import com.topjohnwu.magisk.model.download.DownloadService
import com.topjohnwu.magisk.model.entity.internal.Configuration
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject
import com.topjohnwu.magisk.ui.base.MagiskActivity
import com.topjohnwu.magisk.utils.Utils
internal class InstallMethodDialog(activity: MagiskActivity<*, *>, options: List<String>) :
internal class InstallMethodDialog(activity: BaseActivity<*, *>, options: List<String>) :
AlertDialog.Builder(activity) {
init {
......@@ -28,11 +28,11 @@ internal class InstallMethodDialog(activity: MagiskActivity<*, *>, options: List
}
}
private fun flash(activity: MagiskActivity<*, *>) = DownloadService(activity) {
private fun flash(activity: BaseActivity<*, *>) = DownloadService(activity) {
subject = DownloadSubject.Magisk(Configuration.Flash.Primary)
}
private fun patchBoot(activity: MagiskActivity<*, *>) = activity.withExternalRW {
private fun patchBoot(activity: BaseActivity<*, *>) = activity.withExternalRW {
onSuccess {
Utils.toast(R.string.patch_file_msg, Toast.LENGTH_LONG)
val intent = Intent(Intent.ACTION_GET_CONTENT)
......@@ -49,7 +49,7 @@ internal class InstallMethodDialog(activity: MagiskActivity<*, *>, options: List
}
}
private fun downloadOnly(activity: MagiskActivity<*, *>) = activity.withExternalRW {
private fun downloadOnly(activity: BaseActivity<*, *>) = activity.withExternalRW {
onSuccess {
DownloadService(activity) {
subject = DownloadSubject.Magisk(Configuration.Download)
......@@ -57,7 +57,7 @@ internal class InstallMethodDialog(activity: MagiskActivity<*, *>, options: List
}
}
private fun installInactiveSlot(activity: MagiskActivity<*, *>) {
private fun installInactiveSlot(activity: BaseActivity<*, *>) {
CustomAlertDialog(activity)
.setTitle(R.string.warning)
.setMessage(R.string.install_inactive_slot_msg)
......
......@@ -3,14 +3,14 @@ package com.topjohnwu.magisk.view.dialogs
import android.net.Uri
import com.topjohnwu.magisk.Info
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.ui.base.MagiskActivity
import com.topjohnwu.magisk.base.BaseActivity
import com.topjohnwu.magisk.utils.Utils
import com.topjohnwu.magisk.view.MarkDownWindow
import com.topjohnwu.superuser.Shell
import com.topjohnwu.superuser.ShellUtils
import java.util.*
class MagiskInstallDialog(a: MagiskActivity<*, *>) : CustomAlertDialog(a) {
class MagiskInstallDialog(a: BaseActivity<*, *>) : CustomAlertDialog(a) {
init {
val filename = "Magisk v${Info.remote.magisk.version}" +
"(${Info.remote.magisk.versionCode})"
......
......@@ -5,7 +5,7 @@
<data>
<import type="com.skoumal.teanity.viewmodel.LoadingViewModel.State" />
<import type="com.topjohnwu.magisk.base.viewmodel.LoadingViewModel.State" />
<import type="com.topjohnwu.magisk.ui.home.SafetyNetState" />
......
# v7.3.4
- App is now fully written in Kotlin!
- New downloading system
- Add new "Recovery Mode" to Advanced Settings
# v7.3.5
- Sort installed modules by name
- Better pre-5.0 support
- Fix potential issues when patching tar files
<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
<resources>
<string name="advanced_settings_title">إعدادات متقدمة</string>
<string name="app_changelog">تغييرات التطبيق</string>
<string name="author">انشئ بواسطة %1$s</string>
<string name="auto_response">استجابة تلقائية</string>
<string name="checking_for_updates">البحث عن تحديثات…</string>
<string name="checking_safetyNet_status">التحقق من حالة SafetyNet…</string>
<string name="close">إغلاق</string>
<string name="command">الأمر: %1$s</string>
<string name="deny">رفض</string>
<string name="disable_file_created">سيتم تعطيل الإضافة في إعادة التشغيل التالي</string>
<string name="disable_file_removed">سيتم تمكين الإضافة في إعادة التشغيل التالي</string>
<string name="download">التنزيل</string>
<!--Welcome Activity-->
<string name="modules">الإضافات</string>
<string name="downloads">التنزيلات</string>
<string name="forever">للابد</string>
<string name="grant">سماح</string>
<string name="install">التثبيت</string>
<string name="installed">مثبت</string>
<string name="keep_dm_verity">إبقاء AVB 2.0/dm-verity</string>
<string name="keep_force_encryption">الحفاظ علي قوه التشفير</string>
<string name="superuser">المستخدم المتميز</string>
<string name="log">السجل</string>
<string name="logs_cleared">تم حذف السجل بنجاح</string>
<string name="magisk_update_title">تحديث Magisk جديد متوفر!</string>
<string name="settings">الإعدادات</string>
<string name="install">التثبيت</string>
<string name="unsupport_magisk_title">إصدار Magisk غير مدعوم</string>
<string name="unsupport_magisk_message">لا يدعم هذا الإصدار من Magisk Manager إصدارا لـ Magisk vأقل من 18\n\n بإمكانك تحديث Magisk يدويا أو تثبيت إصدار أدنى.</string>
<!--Status Fragment-->
<string name="magisk_version_error">Magisk غير مثبت</string>
<string name="menuClearLog">حذف السجل الآن</string>
<string name="menuReload">إعادة تحميل</string>
<string name="modules">الإضافات</string>
<string name="multiuser_mode">وضع تعدد المستخدمين</string>
<string name="no_apps_found">لا توجد تطبيقات</string>
<string name="checking_for_updates">البحث عن تحديثات…</string>
<string name="invalid_update_channel">قناة تحديث غير صالحة</string>
<string name="safetyNet_check_text">انقر لبدء فحص SafetyNet</string>
<string name="checking_safetyNet_status">التحقق من حالة SafetyNet…</string>
<string name="safetyNet_check_success">نجح فحص SafetyNet</string>
<string name="safetyNet_api_error">خطأ SafetyNet API</string>
<string name="safetyNet_res_invalid">رد غير صالح</string>
<string name="magisk_up_to_date">Magisk محدث</string>
<string name="manager_up_to_date">Magisk Manager محدث</string>
<string name="advanced_settings_title">إعدادات متقدمة</string>
<string name="keep_force_encryption">إبقاء التشفير بقوة</string>
<string name="keep_dm_verity">إبقاء AVB 2.0/dm-verity</string>
<string name="recovery_mode">نمط الاستعادة</string>
<string name="current_installed">التحديث المثبت : %1$s</string>
<string name="latest_version">آخر تحديث : %1$s</string>
<string name="uninstall">إلغاء التثبيت</string>
<string name="uninstall_magisk_title">إلغاء تثبيت Magisk</string>
<string name="uninstall_magisk_msg">ستُعطل/ستُحذف جميع الإضافات. سيُحذف الروت ، وربما ستشفر بياناتك إذا لم تكن غير مشفرة حالياً.</string>
<string name="update">تحديث</string>
<string name="core_only_enabled">(النمط الأساسي فقط ممكن)</string>
<!--Module Fragment-->
<string name="no_info_provided">(لم يتم توفير أي معلومات)</string>
<string name="no_modules_found">لم يعثر على الإضافات</string>
<string name="none">بدون</string>
<string name="no_modules_found">لم يعثر على أي إضافات</string>
<string name="update_file_created">ستُحدث الإضافة في إعادة التشغيل التالي</string>
<string name="remove_file_created">ستُحذف الإضافة في إعادة التشغيل التالي</string>
<string name="remove_file_deleted">لن تُحذف الإضافة في إعادة التشغيل التالي</string>
<string name="disable_file_created">ستُعطل الإضافة في إعادة التشغيل التالي</string>
<string name="disable_file_removed">ستُمكن الإضافة في إعادة التشغيل التالي</string>
<string name="author">انشئ من طرف %1$s</string>
<string name="reboot_recovery">إعادة التشغيل إلى نمط الاستعادة</string>
<string name="reboot_bootloader">إعادة التشغيل إلى نمط Bootloader</string>
<string name="reboot_download">إعادة التشغيل إلى نمط التحميل</string>
<string name="reboot_edl">إعادة التشغيل إلى نمط EDL</string>
<!--Repo Fragment-->
<string name="update_available">يتوفر على تحديث</string>
<string name="installed">مثبت</string>
<string name="not_installed">غير مثبت</string>
<string name="once">مرة</string>
<string name="owner_manage_summary">يمكن للمالك فقط إدارة صلاحيات الروت وتلقي مطالبات الطلب</string>
<string name="owner_only_summary">المالك فقط لديه صلاحيات الروت</string>
<string name="prompt">طلب</string>
<string name="updated_on">حُدث في: %1$s</string>
<string name="sorting_order">ترتيب الفرز</string>
<string name="sort_by_name">افرز حسب الاسم</string>
<string name="sort_by_update">افرز حسب آخر تحديث</string>
<!--Log Fragment-->
<string name="menuSaveLog">حفظ السجل</string>
<string name="menuReload">إعادة التحميل</string>
<string name="menuClearLog">حذف السجل الآن</string>
<string name="logs_cleared">حُذف السجل بنجاح</string>
<!--About Activity-->
<string name="app_changelog">تغييرات التطبيق</string>
<!-- System Components, Notifications -->
<string name="update_channel">تحديثات Magisk</string>
<string name="progress_channel">إشعارات التقدم</string>
<string name="download_complete">التنزيل انتهى</string>
<string name="download_file_error">خطأ تنزيل الملف</string>
<string name="download_open_parent">أظهر والد المجلد</string>
<string name="download_open_self">أظهر الملف</string>
<string name="magisk_update_title">تحديث Magisk جديد متوفر!</string>
<string name="manager_update_title">تحديث Magisk Manager جديد متوفر!</string>
<!-- Installation -->
<string name="manager_download_install">اضغط للتنزيل و التثبيت</string>
<string name="download_zip_only">تحميل ملف Zip فقط</string>
<string name="direct_install">تثبيت مباشر (موصى بها)</string>
<string name="install_inactive_slot">التثبيت على فتحة غير نشطة (بعد OTA)</string>
<string name="install_inactive_slot_msg">"سيُجبر جهازك للتشغيل على الفتحة غير النشطة الحالية بعد إعادة التشغيل! استخدم فقط هذا الخيار بعد الانتهاء من OTA. استمرار؟"</string>
<string name="select_method">اختر الطريقة</string>
<string name="setup_title">إعداد إضافي</string>
<string name="select_patch_file">اختر و رقع ملفا</string>
<string name="patch_file_msg">اختر ملفا خاما (*.img) أو ملف ODIN tarfile (*.tar)</string>
<string name="reboot_delay_toast">إعادة التشغيل خلال خمس ثواني…</string>
<!--Toasts, Dialogs-->
<string name="close">إغلاق</string>
<string name="repo_install_title">تثبيت %1$s</string>
<string name="repo_install_msg">هل تريد تثبيت %1$s ?</string>
<string name="download">التنزيل</string>
<string name="reboot">إعادة التشغيل</string>
<string name="settings_reboot_toast">إعادة التشغيل لتطبيق الإعدادات</string>
<string name="release_notes">ملاحظات الإصدار</string>
<string name="remove_file_created">سيتم حذف الإضافة في إعادة التشغيل التالي</string>
<string name="remove_file_deleted">لن يتم حذف الإضافة في إعادة التشغيل التالي</string>
<string name="repo_cache_cleared">تم مسح الذاكرة المؤقته للمستودع</string>
<string name="repo_install_msg">هل تريد تثبيت %1$s ?</string>
<string name="repo_install_title">تثبيت %1$s</string>
<string name="request_timeout">مهلة الطلب</string>
<string name="request_timeout_summary">%1$d ثانية</string>
<string name="safetyNet_check_success">نجح فحص SafetyNet</string>
<string name="safetyNet_check_text">انقر لبدء فحص SafetyNet</string>
<string name="safetyNet_res_invalid">الاستجابة غير صالحه</string>
<string name="settings">الإعدادات</string>
<string name="settings_clear_cache_summary">حذف المعلومات المخزنة مؤقتا للمستودع على الانترنت، يجبر التطبيق لتحديث عبر الانترنت</string>
<string name="settings_clear_cache_title">حذف الذاكرة المؤقتة للمستودع</string>
<string name="settings_core_only_summary">تمكين الميزات الأساسية فقط، لن يتم تحميل جميع الإضافات. MagiskSU، MagiskHide، systemless hosts، و لا يزال ممكنا</string>
<string name="settings_core_only_title">Magisk الوضع الأساسي فقط</string>
<string name="settings_dark_theme_summary">تفعيل السمة الغامقة</string>
<string name="settings_dark_theme_title">السمة الغامقة</string>
<string name="repo_cache_cleared">مُسحت الذاكرة المؤقتة للإضافة</string>
<string name="dtbo_patched_title">رُقع DTBO!</string>
<string name="dtbo_patched_reboot">رقع Magisk Manager الملف dtbo.img. يرجى إعادة التشغيل</string>
<string name="flashing">…تثبيت</string>
<string name="done">تم!</string>
<string name="failure">فشل</string>
<string name="hide_manager_title">إخفاء Magisk Manager…</string>
<string name="hide_manager_fail_toast">فشل إخفاء Magisk Manager…</string>
<string name="open_link_failed_toast">لم يُعثر على تطبيق لفتح الرابط …</string>
<string name="warning">تحذير</string>
<string name="complete_uninstall">إلغاء التثبيت بالكامل</string>
<string name="restore_img">استعادة الصور</string>
<string name="restore_img_msg">استعادة…</string>
<string name="restore_done">استُعيدت!</string>
<string name="restore_fail">النسخة الاحتياطية الأصلية غير موجودة!</string>
<string name="proprietary_title">تحميل شفرة المسجلة الملكية</string>
<string name="proprietary_notice">لا Magisk Manager برنامج FOSS ،و لا يحتوي على شفرة SafetyNet API الخاصة بشركة Google.\n\nهل تسمح لـ Magisk Manager بتحميل ملحق (يحتوي على GoogleApiClient) للفحص SafetyNet؟</string>
<string name="setup_fail">فشل الإعداد</string>
<string name="env_fix_title">يتطلب إعدادا إضافيا</string>
<string name="env_fix_msg">يحتاج جهازك إلى إعداد إضافي ليتسنى لـ Magisk بشكل صحيح. سينزل ملف Zip لتثبيت Magisk ، هل تريد المتابعة الآن؟</string>
<string name="setup_msg">تشغيل إعداد البيئة…</string>
<!--Settings Activity -->
<string name="settings_general_category">عام</string>
<string name="settings_hosts_summary">Systemless يدعم تطبيقات حجب الإعلانات</string>
<string name="settings_hosts_title">تمكين المضيفين(الهوست) لـ systemless</string>
<string name="settings_magiskhide_summary">إخفاء Magisk من مختلف الاكتشافات</string>
<string name="settings_owner_manage">إدارة مالك الجهاز</string>
<string name="settings_owner_only">مالك الجهاز فقط</string>
<string name="settings_reboot_toast">إعادة التشغيل لتطبيق الإعدادات</string>
<string name="settings_su_adb">ADB فقط</string>
<string name="settings_su_app">التطبيقات فقط</string>
<string name="settings_dark_theme_title">السمة الغامقة</string>
<string name="settings_dark_theme_summary">تمكين السمة الغامقة</string>
<string name="settings_download_path_title">مسار التحميل</string>
<string name="settings_download_path_message">ستحمل الملفات إلى %1$s</string>
<string name="settings_clear_cache_title">حذف الذاكرة المؤقتة للإضافات</string>
<string name="settings_clear_cache_summary">حذف المعلومات المخزنة مؤقتا للإضافات المباشرة، هذا سيجبر التطبيق على التحديث عبر الانترنت</string>
<string name="settings_hide_manager_title">إخفاء Magisk Manager</string>
<string name="settings_hide_manager_summary">أعد حزم Magisk Manager مع اسم حزمة عشوائي</string>
<string name="settings_restore_manager_title">استعادة Magisk Manager</string>
<string name="settings_restore_manager_summary">استعادة Magisk Manager مع اسم الحزمة الأصلي</string>
<string name="language">اللغة</string>
<string name="system_default">(النظام المبدئي)</string>
<string name="settings_update">تحديث الإعدادات</string>
<string name="settings_check_update_title">تفقد التحديثات</string>
<string name="settings_check_update_summary">التحقق من التحديثات في الخلفية بشكل دوري</string>
<string name="settings_update_channel_title">قناة التحديث</string>
<string name="settings_update_stable">مستقر</string>
<string name="settings_update_beta">بيتا</string>
<string name="settings_update_custom">مخصص</string>
<string name="settings_update_custom_msg">أدخل عنوانا URL مخصصا</string>
<string name="settings_core_only_title"> النمط Magisk الأساسي فقط</string>
<string name="settings_core_only_summary">تمكين الميزات الأساسية فقط، سيُشغل الإضافات MagiskSU و MagiskHide فقط و لا غيرها.</string>
<string name="settings_magiskhide_summary">إخفاء Magisk من شتى طرق الاكتشاف</string>
<string name="settings_hosts_title">Systemless hosts</string>
<string name="settings_hosts_summary">Systemless hosts يدعم تطبيقات حجب الإعلانات</string>
<string name="settings_hosts_toast">مُكنت إضافة Systemless hosts</string>
<string name="settings_su_app_adb">التطبيقات و ADB</string>
<string name="settings_su_app">التطبيقات فقط</string>
<string name="settings_su_adb">ADB فقط</string>
<string name="settings_su_disable">معطل</string>
<string name="settings_su_request_10">10 ثواني</string>
<string name="settings_su_request_15">15 ثانية</string>
<string name="settings_su_request_20">20 ثانية</string>
<string name="settings_su_request_30">30 ثانية</string>
<string name="settings_su_request_45">45 ثانية</string>
<string name="settings_su_request_60">60 ثانية</string>
<string name="settings_user_independent">مستخدم مستقل</string>
<string name="sixtymin">60 دقائق</string>
<string name="su_allow_toast">%1$s يتم منح صلاحيات Superuser</string>
<string name="su_deny_toast">%1$s يتم رفض صلاحيات Superuser</string>
<string name="su_request_title">Superuser طلبات</string>
<string name="su_revoke_msg">تأكيد لسحب صلاحيات %1$s ?</string>
<string name="su_revoke_title">سحب؟</string>
<string name="su_snack_deny">Superuser الصلاحيات لـ %1$s تم رفضها</string>
<string name="su_snack_grant">Superuser الصلاحيات لـ %1$s تم منحها</string>
<string name="su_snack_log_off">السجلات لـ %1$s تم تعطيلها</string>
<string name="su_snack_log_on">السجلات لـ %1$s تم تفعيلها</string>
<string name="su_snack_notif_off">الإشعارات لـ %1$s تم تعطيلها</string>
<string name="su_snack_notif_on">الإشعارات لـ %1$s تم تفعيلها</string>
<string name="su_warning">"منح حق الوصول الكامل إلى جهازك.
رفض إذا كنت غير متأكد!"</string>
<string name="superuser">Superuser</string>
<string name="superuser_access">Superuser صلاحيات</string>
<string name="superuser_notification">Superuser إشعارات</string>
<string name="target_uid">الهدف UID: %1$d</string>
<string name="superuser_access">صلاحيات المستخدم المتميز</string>
<string name="auto_response">رد تلقائي</string>
<string name="request_timeout">مهلة الطلب</string>
<string name="superuser_notification">إشعارات المستخدم المتميز</string>
<string name="request_timeout_summary">%1$d ثانية</string>
<string name="settings_su_reauth_title">إعادة المصادقة بعد الترقية</string>
<string name="settings_su_reauth_summary">أعد المصادقة على صلاحيات المستخدم المتميز بعد إجراء ترقيات للتطبيق</string>
<string name="settings_su_fingerprint_summary">استخدام قارئ بصمات الأصابع للسماح بطلبات المستخدم المتميز</string>
<string name="settings_su_fingerprint_title">تمكين مصادقة البصمة</string>
<string name="auth_fingerprint">مصادقة البصمة</string>
<string name="multiuser_mode">نمط تعدد المستخدمين</string>
<string name="settings_owner_only">مالك الجهاز فقط</string>
<string name="settings_owner_manage">إدارة مالك الجهاز</string>
<string name="settings_user_independent">مستقل عن المستخدم</string>
<string name="owner_only_summary">للمالك فقط صلاحيات الروت</string>
<string name="owner_manage_summary">يمكن للمالك فقط إدارة صلاحيات الروت وتلقي مطالبات الطلب</string>
<string name="user_indepenent_summary">لكل مستخدم قواعد روت منفصلة خاصة به</string>
<string name="mount_namespace_mode">نمط تحميل مساحة الاسم</string>
<string name="settings_ns_global">مساحة الاسم العامة</string>
<string name="settings_ns_requester">وراثة مساحة الاسم</string>
<string name="settings_ns_isolate">مساحة الاسم المعزولة</string>
<string name="global_summary">تستخدم كافة جلسات العمل للروت مساحة الاسم ذات التركيب العامة</string>
<string name="requester_summary">سترث جلسات العمل للروت مساحة الأسماء لطالبيها</string>
<string name="isolate_summary">سيكون لكل جلسة عمل للروت مساحة اسم معزولة خاصة بها </string>
<string name="android_o_not_support">لا يدعم إصدار الأندرويد +8.0</string>
<string name="disable_fingerprint">لم تُعين بصمات الأصابع أو لا يوجد قارئ بصمات</string>
<string name="settings_download_path_error">خطأ عند إنشاء مجلد. عليه أن يكون سهلا الوصول إليه من خلال مجلد التخزين للروت و ألا يكون ملفا.</string>
<!--Superuser-->
<string name="su_request_title">طلبات المستخدم المتميز</string>
<string name="deny">رفض</string>
<string name="prompt">طلب</string>
<string name="grant">سماح</string>
<string name="su_warning">"منح حق الوصول الكامل إلى جهازك. ارفض إذا كنت غير متأكد!"</string>
<string name="forever">للأبد</string>
<string name="once">مرة</string>
<string name="tenmin">10 دقائق</string>
<string name="twentymin">20 دقائق</string>
<string name="thirtymin">30 دقائق</string>
<string name="sixtymin">60 دقائق</string>
<string name="su_allow_toast">%1$s يتم منح صلاحيات </string>
<string name="su_deny_toast">%1$s يتم رفض صلاحيات المستخدم المتميز</string>
<string name="no_apps_found">لا توجد تطبيقات</string>
<string name="su_snack_grant">المستخدم المتميز الصلاحيات لـ %1$s تم منحها</string>
<string name="su_snack_deny">المستخدم المتميز الصلاحيات لـ %1$s تم رفضها</string>
<string name="su_snack_notif_on">الإشعارات لـ %1$s تم تفعيلها</string>
<string name="su_snack_notif_off">الإشعارات لـ %1$s تم تعطيلها</string>
<string name="su_snack_log_on">السجلات لـ %1$s تم تفعيلها</string>
<string name="su_snack_log_off">السجلات لـ %1$s تم تعطيلها</string>
<string name="su_revoke_title">سحب؟</string>
<string name="su_revoke_msg">تأكيد لسحب صلاحيات %1$s ?</string>
<string name="toast">ملاحظة منبثقة</string>
<string name="twentymin">20 دقائق</string>
<string name="uninstall">إلغاء التثبيت</string>
<string name="uninstall_magisk_title">إلغاء تثبيت Magisk</string>
<string name="update_available">يتوفر تحديث</string>
<string name="update_file_created">سيتم تحديث الإضافة في إعادة التشغيل التالي</string>
<string name="user_indepenent_summary">كل مستخدم لديه قواعد روت منفصلة خاصة به</string>
<string name="android_o_not_support">لا يدعم إصدار الأندرويد +8.0</string>
<string name="none">بدون</string>
<string name="auth_fail">فشل المصادقة</string>
<string name="auth_fingerprint">مصادقة البصمة</string>
<string name="complete_uninstall">إلغاء التثبيت بالكامل</string>
<string name="direct_install">تثبيت مباشر (موصى به)</string>
<string name="disable_fingerprint">لم يتم تعيين بصمات الأصابع أو لا يوجد جهاز مدعوم</string>
<string name="download_zip_only">تحميل ملف zip فقط</string>
<string name="dtbo_patched_reboot">قام مدير Magisk بتصحيح dtbo.img ، يرجى إعادة التشغيل</string>
<string name="dtbo_patched_title">تم تصحيح DTBO!</string>
<string name="env_fix_msg">يحتاج جهازك إلى إعداد إضافي لـ Magisk للعمل بشكل صحيح. سيتم تنزيل ملف zip لتثبيت Magisk ، هل تريد المتابعة الآن؟</string>
<string name="env_fix_title">يتطلب إعداد إضافي</string>
<string name="flashing">التثبيت</string>
<string name="global_summary">تستخدم كافة جلسات الجذر مساحة الأسم ذات التركيب العالمي</string>
<string name="hide_manager_fail_toast">فشل إخفاء مدير Magisk …</string>
<string name="hide_manager_title">إخفاء مدير Magisk…</string>
<string name="install_inactive_slot">التثبيت على فتحة غير نشطة (بعد OTA)</string>
<string name="install_inactive_slot_msg">"سيتم إجبار جهازك للتمهيد على الفتحة غير النشطة الحالية بعد إعادة التشغيل!
فقط استخدام هذا الخيار بعد الانتهاء من OTA.
استمرار؟"</string>
<string name="invalid_update_channel">قناة تحديث غير صالحة</string>
<string name="isolate_summary">سيكون لكل جلسة جذر مساحة الاسم الخاصة بها معزولة</string>
<string name="language">اللغة</string>
<string name="update_channel">تحديثات Magisk</string>
<string name="manager_download_install">اضغط للتنزيل والتثبيت</string>
<string name="manager_update_title">تحديث مدير Magisk الجديد متوفر!</string>
<string name="menuSaveLog">حفظ السجل</string>
<string name="mount_namespace_mode">وضع تركيب مساحة الأسم</string>
<string name="open_link_failed_toast">لم يتم العثور على تطبيق لفتح الرابط …</string>
<string name="proprietary_notice">مدير Magisk هو FOSS ، والذي لا يحتوي على شفرة API الخاصة بشركة SafetyNet الخاصة بشركة Google.
هل تسمح لـ Magisk Manager بتنزيل ملحق (يحتوي على GoogleApiClient) لعمليات التحقق من SafetyNet؟ "</string>
<string name="proprietary_title">تحميل رمز الملكية</string>
<string name="reboot_bootloader">إعادة تمهيد إلى وضع Bootloader</string>
<string name="reboot_download">إعادة تمهيد إلى وضع التحميل</string>
<string name="reboot_recovery">إعادة تمهيد إلى وضع الإسترداد</string>
<string name="requester_summary">"سترث جلسات الجذر مساحة الأسماء المطلوبة الخاصة بها"</string>
<string name="restore_done">تمت الأستعادة!</string>
<string name="restore_fail">النسخ الاحتياطي الأصلي غير موجود!</string>
<string name="restore_img">استعادة الصور</string>
<string name="restore_img_msg">الأستعادة …</string>
<string name="safetyNet_api_error">خطأ SafetyNet API</string>
<string name="select_method">حدد الطريقة</string>
<string name="settings_check_update_summary">التحقق من التحديثات في الخلفية بشكل دوري</string>
<string name="settings_check_update_title">تفقد التحديث</string>
<string name="settings_hide_manager_summary">أعد حزم مدير Magisk مع اسم حزمة عشوائية</string>
<string name="settings_hide_manager_title">إخفاء مدير Magisk</string>
<string name="settings_ns_global">مساحة الاسم العالمية</string>
<string name="settings_ns_isolate">مساحة الاسم المعزولة</string>
<string name="settings_ns_requester">وراثة مساحة الاسم</string>
<string name="settings_restore_manager_summary">استعادة مدير Magisk مع الحزمة الأصلية</string>
<string name="settings_restore_manager_title">استعادة مدير Magisk</string>
<string name="settings_su_fingerprint_summary">أستخدام ماسح بصمات الأصابع للسماح بطلبات المستخدم المتميز</string>
<string name="settings_su_fingerprint_title">تمكين مصادقة البصمة</string>
<string name="settings_su_reauth_summary">أعد المصادقة على صلاحيات المستخدم المتميز بعد إجراء ترقيات للتطبيق</string>
<string name="settings_su_reauth_title">إعادة المصادقة بعد الترقية</string>
<string name="settings_update">تحديث الاعدادات</string>
<string name="settings_update_beta">بيتا</string>
<string name="settings_update_channel_title">قناة التحديث</string>
<string name="settings_update_custom">مخصص</string>
<string name="settings_update_custom_msg">أدخل عنوان URL مخصص</string>
<string name="settings_update_stable">مستقر</string>
<string name="setup_fail">فشل الإعداد</string>
<string name="setup_msg">تشغيل إعداد البيئة…</string>
<string name="setup_title">إعداد إضافي</string>
<string name="sort_by_name">الترتيب حسب الاسم</string>
<string name="sort_by_update">فرز حسب آخر تحديث</string>
<string name="sorting_order">ترتيب الفرز</string>
<string name="system_default">(أفتراضي النظام)</string>
<string name="uninstall_magisk_msg">سيتم تعطيل/إزالة جميع الوحدات. ستتم إزالة الجذر ، وربما تشفير بياناتك إذا كانت بياناتك غير مشفرة حالياً</string>
<string name="update">تحديث</string>
<string name="updated_on">تم التحديث في: %1$s</string>
<string name="warning">تحذير</string>
<string name="download_file_error">خطأ تنزيل الملف</string>
<!--Superuser logs-->
<string name="pid">PID: %1$d</string>
<string name="target_uid">Target UID: %1$d</string>
<string name="command">Command: %1$s</string>
<!-- MagiskHide -->
<string name="show_system_app">اظهار برامج النظام</string>
</resources>
......@@ -24,6 +24,7 @@
<string name="advanced_settings_title">Ajustes avanzados</string>
<string name="keep_force_encryption">Mantener cifrado forzado</string>
<string name="keep_dm_verity">Mantener AVB 2.0/dm-verity</string>
<string name="recovery_mode">Modo Recovery</string>
<string name="current_installed">Instalada: %1$s</string>
<string name="latest_version">Última: %1$s</string>
<string name="uninstall">Desinstalar</string>
......@@ -44,6 +45,7 @@
<string name="reboot_recovery">Reiniciar en Modo Recovery</string>
<string name="reboot_bootloader">Reiniciar en Modo Bootloader</string>
<string name="reboot_download">Reiniciar en Modo Download</string>
<string name="reboot_edl">Reiniciar en Modo EDL</string>
<!--Repo Fragment-->
<string name="update_available">Actualización Disponible</string>
......@@ -118,6 +120,8 @@
<string name="settings_general_category">General</string>
<string name="settings_dark_theme_title">Tema oscuro</string>
<string name="settings_dark_theme_summary">Habilitar el tema oscuro</string>
<string name="settings_download_path_title">Ruta de Descarga</string>
<string name="settings_download_path_message">Los archivos se guardarán en %1$s</string>
<string name="settings_clear_cache_title">Limpiar caché del repositorio</string>
<string name="settings_clear_cache_summary">Limpiar la información en caché para los repositorios en línea, fuerza a la aplicación a actualizar en línea</string>
<string name="settings_hide_manager_title">Ocultar Magisk Manager</string>
......@@ -178,6 +182,7 @@
<string name="isolate_summary">Cada sesión root tendrá su propia Namespace</string>
<string name="android_o_not_support">No es compatible con Android 8.0+</string>
<string name="disable_fingerprint">No se establecieron huellas dactilares o no existe soporte del dispositivo</string>
<string name="settings_download_path_error">Error al crear la carpeta. Debe ser accesible desde el directorio raíz de almacenamiento y no debe ser un archivo.</string>
<!--Superuser-->
<string name="su_request_title">Petición de superusuario</string>
......
......@@ -8,10 +8,10 @@
<string name="settings">Seaded</string>
<string name="install">Installi</string>
<string name="unsupport_magisk_title">Mittetoetatud Magisk\'i versioon</string>
<string name="unsupport_magisk_message">See Magisk Manager\'i versioon ei toeta Magisk\'i v18.0-st vanemat versiooni.\n\nSa võid kas Magisk\'i käsitsi täiendada või alandad rakenduse vanemale versioonile.</string>
<string name="unsupport_magisk_message">See Magisk Manager\'i versioon ei toeta Magisk\'ist vanemat versiooni kui v18.0.\n\nSa võid kas Magisk\'i käsitsi täiendada või alandad rakenduse vanemale versioonile.</string>
<!--Status Fragment-->
<string name="magisk_version_error">Magisk pole installitud.</string>
<string name="magisk_version_error">Magisk pole installitud</string>
<string name="checking_for_updates">Kontrollin uuendusi…</string>
<string name="invalid_update_channel">Sobimatu uuenduste kanal</string>
<string name="safetyNet_check_text">Koputa SafetyNet\'i kontrolliks</string>
......@@ -24,6 +24,7 @@
<string name="advanced_settings_title">Täpsemad seaded</string>
<string name="keep_force_encryption">Säilita sunnitud krüpteering</string>
<string name="keep_dm_verity">Säilita AVB 2.0/dm-verity</string>
<string name="recovery_mode">Taastusrežiim</string>
<string name="current_installed">Installitud: %1$s</string>
<string name="latest_version">Viimatine: %1$s</string>
<string name="uninstall">Eemalda</string>
......@@ -44,6 +45,7 @@
<string name="reboot_recovery">Taaskäivita taastusesse</string>
<string name="reboot_bootloader">Taaskäivita käivitushaldurisse</string>
<string name="reboot_download">Taaskäivita allalaadimisrežiimi</string>
<string name="reboot_edl">Taaskäivita EDL\'i</string>
<!--Repo Fragment-->
<string name="update_available">Uuendus saadaval</string>
......@@ -68,9 +70,23 @@
<string name="progress_channel">Edenemise teated</string>
<string name="download_complete">Allalaadimine valmis</string>
<string name="download_file_error">Faili allalaadimisel esines viga</string>
<string name="download_open_parent">Kuva ülemkaustas</string>
<string name="download_open_self">Kuva fail</string>
<string name="magisk_update_title">Magisk\'ile on uuendus saadaval!</string>
<string name="manager_update_title">Magisk Manager\'ile on uuendus saadaval!</string>
<!-- Installation -->
<string name="manager_download_install">Vajuta allalaadimiseks ja installimiseks.</string>
<string name="download_zip_only">Laadi ainult ZIP alla</string>
<string name="direct_install">Otsene install (soovitatud)</string>
<string name="install_inactive_slot">Installi ebaaktiivsesse lahtrisse (pärast üle-õhu uuendust)</string>
<string name="install_inactive_slot_msg">Sinu seade SUNNITAKSE peale taaskäivitust käivituma praegusesse ebaaktiivsesse lahtrisse!\nKasuta seda valikut vaid peale üle-õhu uuenduse teostamist.\nJätkad?</string>
<string name="select_method">Vali meetod</string>
<string name="setup_title">Lisaseadistus</string>
<string name="select_patch_file">Vali ja paika fail</string>
<string name="patch_file_msg">Vali toortõmmis (*.img) või ODIN tar-fail (*.tar)</string>
<string name="reboot_delay_toast">Taaskäivitamine 5 sekundi pärast…</string>
<!--Toasts, Dialogs-->
<string name="close">Sulge</string>
<string name="repo_install_title">Installi %1$s</string>
......@@ -80,19 +96,16 @@
<string name="settings_reboot_toast">Taaskäivita seadete rakendamiseks.</string>
<string name="release_notes">Väljalaske märkmed</string>
<string name="repo_cache_cleared">Hoidla vahemälu tühjendatud</string>
<string name="manager_download_install">Vajuta allalaadimiseks ja installimiseks.</string>
<string name="dtbo_patched_title">DTBO sai paigatud!</string>
<string name="dtbo_patched_reboot">Magisk Manager on paiganud dtbo.img. Palun taaskäivita.</string>
<string name="flashing">Välgutamine</string>
<string name="done">Valmis!</string>
<string name="failure">Ebaõnnestus</string>
<string name="hide_manager_title">Peidan Magisk Manager\'i…</string>
<string name="hide_manager_fail_toast">Magisk Manager\'i peitmine ebaõnnestus.</string>
<string name="open_link_failed_toast">Lingi avamiseks sobivat rakendust ei leitud.</string>
<string name="download_zip_only">Laadi ainult ZIP alla</string>
<string name="direct_install">Otsene install (soovitatud)</string>
<string name="install_inactive_slot">Installi ebaaktiivsesse lahtrisse (pärast üle-õhu uuendust)</string>
<string name="warning">Hoiatus</string>
<string name="install_inactive_slot_msg">Sinu seade SUNNITAKSE peale taaskäivitust käivituma praegusesse ebaaktiivsesse lahtrisse!\nKasuta seda valikut vaid peale üle-õhu uuenduse teostamist.\nJätkad?</string>
<string name="select_method">Vali meetod</string>
<string name="complete_uninstall">Täielik eemaldus</string>
<string name="restore_img">Taasta tõmmised</string>
<string name="restore_img_msg">Taastamine…</string>
......@@ -103,13 +116,14 @@
<string name="setup_fail">Seadistus ebaõnnnestus.</string>
<string name="env_fix_title">Vajab lisaseadistust</string>
<string name="env_fix_msg">Sinu seade vajab lisaseadistust, et Magisk töötaks korralikult. Laadime alla Magisk\'i seadistus-zip\'i, kas soovid kohe jätkata?</string>
<string name="setup_title">Lisaseadistus</string>
<string name="setup_msg">Käivitan keskkonnaseadistust…</string>
<!--Settings Activity -->
<string name="settings_general_category">Üldine</string>
<string name="settings_dark_theme_title">Tume teema</string>
<string name="settings_dark_theme_summary">Luba tume teema.</string>
<string name="settings_download_path_title">Allalaadimise failitee</string>
<string name="settings_download_path_message">Failid salvestatakse kausta %1$s</string>
<string name="settings_clear_cache_title">Tühjenda hoidla vahemälu</string>
<string name="settings_clear_cache_summary">Tühjenda vahemälus olev teave võrgus olevate hoidlate kohta. See sunnib rakendust võrgust värskendama.</string>
<string name="settings_hide_manager_title">Peida Magisk Manager</string>
......@@ -171,6 +185,7 @@
<string name="isolate_summary">Iga juurkasutaja sessioon saab oma isoleeritud nimeruumi.</string>
<string name="android_o_not_support">Ei toeta Androidi versiooni 8.0+.</string>
<string name="disable_fingerprint">Sõrmejälgi pole määratud või seade pole toetatud.</string>
<string name="settings_download_path_error">Faili loomisel esines viga. See peab olema ligipääsetav mäluruumi juurkaustast ning ei tohi olla fail.</string>
<!--Superuser-->
<string name="su_request_title">Superkasutaja taotlus</string>
......
......@@ -24,6 +24,7 @@
<string name="advanced_settings_title">Gelişmiş Ayarlar</string>
<string name="keep_force_encryption">Şifrelemeyi zorlamayı sürdür</string>
<string name="keep_dm_verity">AVB 2.0/dm-verity\'i koru</string>
<string name="recovery_mode">Kurtarma Modu</string>
<string name="current_installed">Yüklü: %1$s</string>
<string name="latest_version">Güncel: %1$s</string>
<string name="uninstall">Kaldır</string>
......
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="SplashTheme" parent="Theme.AppCompat.NoActionBar">
<item name="android:windowBackground">@drawable/ic_splash_activity</item>
<item name="android:windowTranslucentStatus">true</item>
<item name="android:windowTranslucentNavigation">true</item>
</style>
<style name="SplashTheme" parent="SplashThemeBase.V19"/>
</resources>
\ No newline at end of file
......@@ -32,10 +32,6 @@
<color name="dark_secondary_text">#dedede</color>
<color name="su_request_background">#e0e0e0</color>
<!-- Flashing colors -->
<color name="ic_launcher_background">#00AF9C</color>
<!-- Card colors -->
<color name="card_background_color_dark">#ff424242</color>
<color name="card_background_color_light">#ffffffff</color>
......
......@@ -60,8 +60,6 @@
<item name="cardBackgroundColor">@color/card_background_color_light</item>
</style>
<style name="SplashTheme" parent="Theme.AppCompat.NoActionBar">
<item name="android:windowBackground">@drawable/ic_splash_activity</item>
</style>
<style name="SplashTheme" parent="SplashThemeBase"/>
</resources>
......@@ -7,7 +7,7 @@ if (configPath.exists())
configPath.withInputStream { is -> props.load(is) }
buildscript {
ext.kotlinVer = '1.3.50'
ext.vKotlin = '1.3.50'
repositories {
google()
......@@ -16,8 +16,8 @@ buildscript {
maven { url 'https://kotlin.bintray.com/kotlinx' }
}
dependencies {
classpath 'com.android.tools.build:gradle:3.5.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlinVer}"
classpath 'com.android.tools.build:gradle:3.5.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${vKotlin}"
// NOTE: Do not place your application dependencies here; they belong
......@@ -38,7 +38,7 @@ subprojects {
maven { url "https://jitpack.io" }
maven { url "http://oss.sonatype.org/content/repositories/snapshots" }
}
afterEvaluate {
afterEvaluate { module ->
if (getPlugins().hasPlugin('com.android.library') ||
getPlugins().hasPlugin('com.android.application')) {
android {
......@@ -58,7 +58,7 @@ subprojects {
}
}
if (it.name == 'app' || it.name == 'stub') {
if (module.name == 'app' || module.name == 'stub') {
android {
signingConfigs {
config {
......@@ -76,8 +76,6 @@ subprojects {
signingConfig signingConfigs.config
}
release {
minifyEnabled true
shrinkResources true
signingConfig signingConfigs.config
}
}
......@@ -85,6 +83,16 @@ subprojects {
lintOptions {
disable 'MissingTranslation'
}
aaptOptions {
// Preserve stub resource IDs
File publicTxt = rootProject.file('stub-public.txt')
if (publicTxt.exists()) {
additionalParameters "--stable-ids", "${publicTxt.absolutePath}"
} else if (module.name == 'stub') {
additionalParameters "--emit-ids", "${publicTxt.absolutePath}"
}
}
}
}
}
......
......@@ -2,13 +2,13 @@
If you already have Magisk installed, it is **strongly recommended to upgrade directly via Magisk Manager**. The following tutorial is for first time users.
## Getting Started
- If you are using a Huawei device running **EMUI 8 and higher**, please check [its own section](#huawei).
- If you are using a Samsung device that is **launched with Android 9.0** (new devices in 2019), please check [its own section](#samsung-system-as-root).
- If you are using a Huawei device running **EMUI 8 and higher**, please check [its section](#huawei).
- If you are using a Samsung device that is **launched with Android 9.0** (new devices in 2019), please check [its section](#samsung-system-as-root).
Otherwise, follow the instructions in [Knowing Your Device](#knowing-your-device), and choose the right steps
- If your device is **NOT** A/B, but **IS** using system-as-root, then you will have to install Magisk to the recovery partition of your device. Follow the instructions in [Boot Image Patching](#boot-image-patching), but instead of using your boot image, use your recovery image. **Read through the [Magisk in Recovery](#magisk-in-recovery) section!**
- Otherwise, you can either follow the instructions in [Custom Recovery](#custom-recovery) (if your device have custom recovery available) or [Boot Image Patching](#boot-image-patching).
- Otherwise, you can either follow the instructions in [Custom Recovery](#custom-recovery) (if your device has custom recovery available) or [Boot Image Patching](#boot-image-patching).
Other notes:
......@@ -33,7 +33,7 @@ If the result is `true`, then your device is using system-as-root.
(P.S. If you are interested more regarding system-as-root, please check [this Twitter thread](https://twitter.com/topjohnwu/status/1174392824625676288))
## Custom Recovery
If your device have custom recovery support, the easiest way is to install it through custom recoveries, such as TWRP.
If your device has custom recovery support, the easiest way is to install it through custom recoveries, such as TWRP.
- Download the Magisk installer zip
- Reboot to custom recovery
......@@ -41,9 +41,9 @@ If your device have custom recovery support, the easiest way is to install it th
- Check whether Magisk Manager is installed. If for some reason it isn't installed automatically, manually install the APK
## Boot Image Patching
You would want choose this method if either your device does not have custom recoveries, your device is A/B and you don't want to mix recovery and boot images, or your device is using system-as-root without A/B partitions.
You would want to choose this method if either your device does not have custom recoveries, your device is A/B and you don't want to mix recovery and boot images, or your device is using system-as-root without A/B partitions.
In order to use this method, you are required to obtain a copy of the stock boot/recovery image, which can be found by extracting OEM provided factory images or extracting from OTA update zips. If you are unable to obtain one yourself, you might be able to find it somewhere on the internet. The following instructions will guide you through the process after you have the copy of boot/recovery image.
To use this method, you are required to obtain a copy of the stock boot/recovery image, which can be found by extracting OEM provided factory images or extracting from OTA update zips. If you are unable to obtain one yourself, you might be able to find it somewhere on the internet. The following instructions will guide you through the process after you have the copy of boot/recovery image.
- Copy the boot/recovery image to your device
- Download and install the latest Magisk Manager
......@@ -57,7 +57,7 @@ In order to use this method, you are required to obtain a copy of the stock boot
`fastboot flash recovery /path/to/magisk_patched.img` if you are patching a recovery image
## Magisk in Recovery
Due to the fact that some devices no longer uses ramdisk in boot images, Magisk has no choice but to be installed in the recovery partition. For these devices, you will have to **boot to recovery every time** if you want Magisk. Since both Magisk and recovery lives in the same partition, what you actually end up getting when you choose to boot to recovery will be determined by **how long you press volume up**.
Since some devices no longer use ramdisk in boot images, Magisk has no choice but to be installed in the recovery partition. For these devices, you will have to **boot to recovery every time** if you want Magisk. Since both Magisk and recovery lives in the same partition, what you actually end up getting when you choose to boot to recovery will be determined by **how long you press volume up**.
Each OEM and device has its own key combo to boot into recovery. For example on Samsung S10 it is **(Power + Bixby + Volume Up)**, and for Huawei it is **(Power + Volume Up)**. As soon as you press the combo and the device vibrates with a splash screen, the bootloader has already chosen which mode it is booting, either it be `boot`, `recovery`, or some OEM specific modes like `download`, `fastboot`, or `erecovery`. After the splash screen, release all buttons to boot into Magisk, since by default `recovery` mode will boot to the system with Magisk enabled. If you decide to boot to actual recovery, continue to press volume up until you see the recovery screen.
......@@ -105,7 +105,7 @@ Press *Power + Volume Down* to exit download mode. As soon as the screen turns o
10. Use volume buttons to navigate through the stock recovery menu, and the power button to select an option. Choose *Wipe data/factory reset* to wipe the data of the device.
11. This time, we can finally boot to the system with Magisk. Select *Reboot system now*, and immediately press the combo key to recovery. After seeing the bootloader warning screen, release all buttons so it can boot to the system.
12. The device will automatically reboot for the first time it boots. This is completely normal and done by design.
13. After the device is booted up, do the usual initial setup. **The following steps will need internet connection.**
13. After the device is booted up, do the usual initial setup. **The following steps will need an internet connection.**
14. You shall see Magisk Manager in your app drawer; if not, manually install the APK you downloaded in step 3 and continue to the next step. The app would be a stub and it shall automatically upgrade to the full Magisk Manager when you open it.
15. Magisk Manager will ask to do additional setups. Let it do its job and the app will automatically reboot your device.
16. Voila! Enjoy Magisk :)
......@@ -116,17 +116,17 @@ Press *Power + Volume Down* to exit download mode. As soon as the screen turns o
- `boot`: remove the signature of the image to prevent soft bricks
- `recovery`: this is where Magisk is actually installed
- **Never, ever** try to restore either of the 3 images mentioned back to stock! You can easily brick your device by doing so, and the only way out is to do full Odin restore following with factory reset. Just don't do it.
- If you want to upgrade your device, **never** flash the stock **AP** tar file with reasons mentioned above. **Always** pre-patch the firmware before flashing in Odin.
- If you want to upgrade your device, **never** flash the stock **AP** tar file with the reasons mentioned above. **Always** pre-patch the firmware before flashing in Odin.
- If you don't need to patch the full firmware, you can manually create a tar file with **at least** `vbmeta.img`, `boot.img`, and `recovery.img` to let Magisk Manager patch your images in the proper way.
## Huawei
Huawei devices using Kirin processors have a different partitioning method from most common devices. Magisk is usually installed to the `boot` partition of the device, however Huawei devices does not have this partition. Depending on what EMUI version your device is running, the instructions will be slightly different.
Huawei devices using Kirin processors have a different partitioning method from most common devices. Magisk is usually installed to the `boot` partition of the device, however Huawei devices do not have this partition. Depending on what EMUI version your device is running, the instructions will be slightly different.
### Obtain Stock Images
Huawei does not release official factory images, however most firmware zips can be downloaded from the [Huawei Firmware Database](http://pro-teammt.ru/firmware-database/). To extract images from `UPDATE.APP` in the zip, you have to use [Huawei Update Extractor](https://forum.xda-developers.com/showthread.php?t=2433454) (Windows only!)
### EMUI 8
For EMUI 8 devices, your device have a partition named `ramdisk`, which is where Magisk is going to be installed.
For EMUI 8 devices, your device has a partition named `ramdisk`, which is where Magisk is going to be installed.
- If you plan to use custom recoveries, simply follow the instructions for custom recovery and you're all set.
- If you plan not to use custom recoveries, you will have to extract `RAMDISK.img` from your firmware. Follow the instructions for boot image patching above, but use the `RAMDISK.img` file instead of a boot image.
......
......@@ -347,7 +347,7 @@ static bool magisk_env() {
unlink("/data/magisk_debug.log");
// Backwards compatibility
symlink("./magisk", "/sbin/.core");
symlink("./.magisk", "/sbin/.core");
symlink("./modules", MAGISKTMP "/img");
// Directories in tmpfs overlay
......
......@@ -74,22 +74,24 @@ void exec_module_script(const char *stage, const vector<string> &module_list) {
}
}
static const char migrate_script[] =
"IMG=%s;"
"MNT=/dev/img_mnt;"
"e2fsck -yf $IMG;"
"mkdir -p $MNT;"
"for num in 0 1 2 3 4 5 6 7; do"
" losetup /dev/block/loop${num} $IMG || continue;"
" mount -t ext4 /dev/block/loop${num} $MNT;"
" rm -rf $MNT/lost+found $MNT/.core;"
" magisk --clone $MNT " MODULEROOT ";"
" umount $MNT;"
" rm -rf $MNT;"
" losetup -d /dev/block/loop${num};"
" break;"
"done;"
"rm -rf $IMG;";
constexpr char migrate_script[] =
"MODULEROOT=" MODULEROOT R"EOF(
IMG=%s
MNT=/dev/img_mnt
e2fsck -yf $IMG
mkdir -p $MNT
for num in 0 1 2 3 4 5 6 7; do
losetup /dev/block/loop${num} $IMG || continue
mount -t ext4 /dev/block/loop${num} $MNT
rm -rf $MNT/lost+found $MNT/.core
magisk --clone $MNT $MODULEROOT
umount $MNT
rm -rf $MNT
losetup -d /dev/block/loop${num}
break
done
rm -rf $IMG
)EOF";
void migrate_img(const char *img) {
LOGI("* Migrating %s\n", img);
......@@ -99,11 +101,12 @@ void migrate_img(const char *img) {
exec_command_sync(exec, "/system/bin/sh", "-c", cmds);
}
static const char install_script[] =
"APK=%s;"
"log -t Magisk \"apk_install: $APK\";"
"log -t Magisk \"apk_install: `pm install -r $APK 2>&1`\";"
"rm -f $APK;";
constexpr char install_script[] = R"EOF(
APK=%s
log -t Magisk "apk_install: $APK"
log -t Magisk "apk_install: `pm install -r $APK 2>&1`"
rm -f $APK
)EOF";
void install_apk(const char *apk) {
setfilecon(apk, "u:object_r:" SEPOL_FILE_DOMAIN ":s0");
......
......@@ -67,7 +67,7 @@ static bool check_key_combo() {
if (events.empty())
return false;
RunFinally fin([&]() -> void {
run_finally fin([&]() -> void {
for (const int &fd : events)
close(fd);
});
......
......@@ -22,7 +22,7 @@ uint32_t dyn_img_hdr::j32 = 0;
uint64_t dyn_img_hdr::j64 = 0;
static int64_t one_step(unique_ptr<Compression> &&ptr, int fd, const void *in, size_t size) {
ptr->set_out(make_unique<FDOutStream>(fd));
ptr->setOut(make_unique<FDOutStream>(fd));
if (!ptr->write(in, size))
return -1;
return ptr->finalize();
......@@ -44,7 +44,7 @@ static void dump(void *buf, size_t size, const char *filename) {
close(fd);
}
static size_t restore(const char *filename, int fd) {
static size_t restore(int fd, const char *filename) {
int ifd = xopen(filename, O_RDONLY);
size_t size = lseek(ifd, 0, SEEK_END);
lseek(ifd, 0, SEEK_SET);
......@@ -57,53 +57,147 @@ static void restore_buf(int fd, const void *buf, size_t size) {
xwrite(fd, buf, size);
}
boot_img::~boot_img() {
munmap(map_addr, map_size);
delete hdr;
delete k_hdr;
delete r_hdr;
delete b_hdr;
void dyn_img_hdr::print() {
uint32_t ver = header_version();
fprintf(stderr, "HEADER_VER [%u]\n", ver);
fprintf(stderr, "KERNEL_SZ [%u]\n", kernel_size());
fprintf(stderr, "RAMDISK_SZ [%u]\n", ramdisk_size());
fprintf(stderr, "SECOND_SZ [%u]\n", second_size());
if (ver == 0)
fprintf(stderr, "EXTRA_SZ [%u]\n", extra_size());
if (ver >= 1)
fprintf(stderr, "RECOV_DTBO_SZ [%u]\n", recovery_dtbo_size());
if (ver >= 2)
fprintf(stderr, "DTB_SZ [%u]\n", dtb_size());
ver = os_version();
if (ver) {
int a,b,c,y,m = 0;
int version, patch_level;
version = ver >> 11;
patch_level = ver & 0x7ff;
a = (version >> 14) & 0x7f;
b = (version >> 7) & 0x7f;
c = version & 0x7f;
fprintf(stderr, "OS_VERSION [%d.%d.%d]\n", a, b, c);
y = (patch_level >> 4) + 2000;
m = patch_level & 0xf;
fprintf(stderr, "OS_PATCH_LEVEL [%d-%02d]\n", y, m);
}
fprintf(stderr, "PAGESIZE [%u]\n", page_size());
fprintf(stderr, "NAME [%s]\n", name());
fprintf(stderr, "CMDLINE [%.512s%.1024s]\n", cmdline(), extra_cmdline());
fprintf(stderr, "CHECKSUM [");
for (int i = 0; i < SHA256_DIGEST_SIZE; ++i)
fprintf(stderr, "%02hhx", static_cast<uint8_t>(id()[i]));
fprintf(stderr, "]\n");
}
void dyn_img_hdr::dump_hdr_file() {
FILE *fp = xfopen(HEADER_FILE, "w");
fprintf(fp, "pagesize=%u\n", page_size());
fprintf(fp, "name=%s\n", name());
fprintf(fp, "cmdline=%.512s%.1024s\n", cmdline(), extra_cmdline());
uint32_t ver = os_version();
if (ver) {
int a, b, c, y, m = 0;
int version, patch_level;
version = ver >> 11;
patch_level = ver & 0x7ff;
a = (version >> 14) & 0x7f;
b = (version >> 7) & 0x7f;
c = version & 0x7f;
fprintf(fp, "os_version=%d.%d.%d\n", a, b, c);
y = (patch_level >> 4) + 2000;
m = patch_level & 0xf;
fprintf(fp, "os_patch_level=%d-%02d\n", y, m);
}
fclose(fp);
}
void dyn_img_hdr::load_hdr_file() {
parse_prop_file(HEADER_FILE, [=](string_view key, string_view value) -> bool {
if (key == "page_size") {
page_size() = parse_int(value);
} else if (key == "name") {
memset(name(), 0, 16);
memcpy(name(), value.data(), value.length() > 15 ? 15 : value.length());
} else if (key == "cmdline") {
memset(cmdline(), 0, 512);
memset(extra_cmdline(), 0, 1024);
if (value.length() > 512) {
memcpy(cmdline(), value.data(), 512);
memcpy(extra_cmdline(), &value[512], value.length() - 511);
} else {
memcpy(cmdline(), value.data(), value.length());
}
} else if (key == "os_version") {
int patch_level = os_version() & 0x7ff;
int a, b, c;
sscanf(value.data(), "%d.%d.%d", &a, &b, &c);
os_version() = (((a << 14) | (b << 7) | c) << 11) | patch_level;
} else if (key == "os_patch_level") {
int os_ver = os_version() >> 11;
int y, m;
sscanf(value.data(), "%d-%d", &y, &m);
y -= 2000;
os_version() = (os_ver << 11) | (y << 4) | m;
}
return true;
});
}
#define UNSUPP_RET 1
#define CHROME_RET 2
int boot_img::parse_file(const char *image) {
boot_img::boot_img(const char *image) {
mmap_ro(image, map_addr, map_size);
fprintf(stderr, "Parsing boot image: [%s]\n", image);
for (uint8_t *head = map_addr; head < map_addr + map_size; ++head) {
switch (check_fmt(head, map_size)) {
for (uint8_t *addr = map_addr; addr < map_addr + map_size; ++addr) {
switch (check_fmt(addr, map_size)) {
case CHROMEOS:
// The caller should know it's chromeos, as it needs additional signing
// chromeos require external signing
flags |= CHROMEOS_FLAG;
addr += 65535;
break;
case DHTB:
flags |= DHTB_FLAG;
flags |= SEANDROID_FLAG;
flags |= (DHTB_FLAG | SEANDROID_FLAG);
fprintf(stderr, "DHTB_HDR\n");
addr += sizeof(dhtb_hdr) - 1;
break;
case BLOB:
flags |= BLOB_FLAG;
fprintf(stderr, "TEGRA_BLOB\n");
b_hdr = new blob_hdr();
memcpy(b_hdr, head, sizeof(blob_hdr));
head += sizeof(blob_hdr) - 1;
addr += sizeof(blob_hdr) - 1;
break;
case AOSP:
return parse_image(head);
parse_image(addr);
return;
default:
break;
}
}
exit(UNSUPP_RET);
exit(1);
}
boot_img::~boot_img() {
munmap(map_addr, map_size);
delete hdr;
}
#define pos_align() pos = do_align(pos, hdr->page_size())
int boot_img::parse_image(uint8_t *head) {
auto hp = reinterpret_cast<boot_img_hdr*>(head);
#define get_block(name) {\
name = addr + off; \
off += hdr->name##_size(); \
off = do_align(off, hdr->page_size()); \
}
void boot_img::parse_image(uint8_t *addr) {
auto hp = reinterpret_cast<boot_img_hdr*>(addr);
if (hp->page_size >= 0x02000000) {
fprintf(stderr, "PXA_BOOT_HDR\n");
hdr = new dyn_img_pxa(head);
hdr = new dyn_img_pxa(addr);
} else {
if (memcmp(hp->cmdline, NOOKHD_RL_MAGIC, 10) == 0 ||
memcmp(hp->cmdline, NOOKHD_GL_MAGIC, 12) == 0 ||
......@@ -112,53 +206,41 @@ int boot_img::parse_image(uint8_t *head) {
memcmp(hp->cmdline, NOOKHD_ER_MAGIC, 30) == 0) {
flags |= NOOKHD_FLAG;
fprintf(stderr, "NOOKHD_LOADER\n");
head += NOOKHD_PRE_HEADER_SZ;
addr += NOOKHD_PRE_HEADER_SZ;
} else if (memcmp(hp->name, ACCLAIM_MAGIC, 10) == 0) {
flags |= ACCLAIM_FLAG;
fprintf(stderr, "ACCLAIM_LOADER\n");
head += ACCLAIM_PRE_HEADER_SZ;
addr += ACCLAIM_PRE_HEADER_SZ;
}
if (hp->header_version == 1)
hdr = new dyn_img_v1(head);
hdr = new dyn_img_v1(addr);
else if (hp->header_version == 2)
hdr = new dyn_img_v2(head);
hdr = new dyn_img_v2(addr);
else
hdr = new dyn_img_v0(head);
hdr = new dyn_img_v0(addr);
}
size_t pos = hdr->page_size();
flags |= hdr->id()[SHA_DIGEST_SIZE] ? SHA256_FLAG : 0;
print_hdr();
kernel = head + pos;
pos += hdr->kernel_size();
pos_align();
ramdisk = head + pos;
pos += hdr->ramdisk_size();
pos_align();
second = head + pos;
pos += hdr->second_size();
pos_align();
extra = head + pos;
pos += hdr->extra_size();
pos_align();
for (int i = SHA_DIGEST_SIZE; i < SHA256_DIGEST_SIZE; ++i) {
if (hdr->id()[i]) {
flags |= SHA256_FLAG;
break;
}
}
recov_dtbo = head + pos;
pos += hdr->recovery_dtbo_size();
pos_align();
hdr->print();
dtb = head + pos;
pos += hdr->dtb_size();
pos_align();
size_t off = hdr->page_size();
hdr_addr = addr;
get_block(kernel);
get_block(ramdisk);
get_block(second);
get_block(extra);
get_block(recovery_dtbo);
get_block(dtb);
if (head + pos < map_addr + map_size) {
tail = head + pos;
if (addr + off < map_addr + map_size) {
tail = addr + off;
tail_size = map_size - (tail - map_addr);
}
......@@ -169,7 +251,7 @@ int boot_img::parse_image(uint8_t *head) {
flags |= LG_BUMP_FLAG;
}
find_dtb();
find_kernel_dtb();
k_fmt = check_fmt(kernel, hdr->kernel_size());
r_fmt = check_fmt(ramdisk, hdr->ramdisk_size());
......@@ -178,159 +260,82 @@ int boot_img::parse_image(uint8_t *head) {
if (k_fmt == MTK) {
fprintf(stderr, "MTK_KERNEL_HDR\n");
flags |= MTK_KERNEL;
k_hdr = new mtk_hdr();
memcpy(k_hdr, kernel, sizeof(mtk_hdr));
k_hdr = reinterpret_cast<mtk_hdr *>(kernel);
fprintf(stderr, "KERNEL [%u]\n", k_hdr->size);
fprintf(stderr, "NAME [%s]\n", k_hdr->name);
kernel += 512;
hdr->kernel_size() -= 512;
kernel += sizeof(mtk_hdr);
hdr->kernel_size() -= sizeof(mtk_hdr);
k_fmt = check_fmt(kernel, hdr->kernel_size());
}
if (r_fmt == MTK) {
fprintf(stderr, "MTK_RAMDISK_HDR\n");
flags |= MTK_RAMDISK;
r_hdr = new mtk_hdr();
memcpy(r_hdr, ramdisk, sizeof(mtk_hdr));
r_hdr = reinterpret_cast<mtk_hdr *>(ramdisk);
fprintf(stderr, "RAMDISK [%u]\n", r_hdr->size);
fprintf(stderr, "NAME [%s]\n", r_hdr->name);
ramdisk += 512;
hdr->ramdisk_size() -= 512;
ramdisk += sizeof(mtk_hdr);
hdr->ramdisk_size() -= sizeof(mtk_hdr);
r_fmt = check_fmt(ramdisk, hdr->ramdisk_size());
}
fprintf(stderr, "KERNEL_FMT [%s]\n", fmt2name[k_fmt]);
fprintf(stderr, "RAMDISK_FMT [%s]\n", fmt2name[r_fmt]);
return (flags & CHROMEOS_FLAG) ? CHROME_RET : 0;
}
void boot_img::find_dtb() {
for (uint32_t i = 0; i < hdr->kernel_size(); ++i) {
void boot_img::find_kernel_dtb() {
const int eof = static_cast<int>(hdr->kernel_size());
for (int i = 0; i < eof - (int) sizeof(fdt_header); ++i) {
auto fdt_hdr = reinterpret_cast<fdt_header *>(kernel + i);
if (fdt32_to_cpu(fdt_hdr->magic) != FDT_MAGIC)
continue;
// Check that fdt_header.totalsize does not overflow kernel image size
uint32_t totalsize = fdt32_to_cpu(fdt_hdr->totalsize);
if (totalsize > hdr->kernel_size() - i) {
fprintf(stderr, "Invalid DTB detection at 0x%x: size (%u) > remaining (%u)\n",
i, totalsize, hdr->kernel_size() - i);
if (totalsize + i > eof)
continue;
}
// Check that fdt_header.off_dt_struct does not overflow kernel image size
uint32_t off_dt_struct = fdt32_to_cpu(fdt_hdr->off_dt_struct);
if (off_dt_struct > hdr->kernel_size() - i) {
fprintf(stderr, "Invalid DTB detection at 0x%x: "
"struct offset (%u) > remaining (%u)\n",
i, off_dt_struct, hdr->kernel_size() - i);
if (off_dt_struct + i > eof)
continue;
}
// Check that fdt_node_header.tag of first node is FDT_BEGIN_NODE
auto fdt_node_hdr = reinterpret_cast<fdt_node_header *>(kernel + i + off_dt_struct);
if (fdt32_to_cpu(fdt_node_hdr->tag) != FDT_BEGIN_NODE) {
fprintf(stderr, "Invalid DTB detection at 0x%x: "
"header tag of first node != FDT_BEGIN_NODE\n", i);
if (fdt32_to_cpu(fdt_node_hdr->tag) != FDT_BEGIN_NODE)
continue;
}
kernel_dtb = kernel + i;
kernel_dt_size = hdr->kernel_size() - i;
kernel_dt_size = eof - i;
hdr->kernel_size() = i;
fprintf(stderr, "KERNEL_DTB [%u]\n", kernel_dt_size);
break;
}
}
void boot_img::print_hdr() {
uint32_t ver = hdr->header_version();
fprintf(stderr, "HEADER_VER [%u]\n", ver);
fprintf(stderr, "KERNEL_SZ [%u]\n", hdr->kernel_size());
fprintf(stderr, "RAMDISK_SZ [%u]\n", hdr->ramdisk_size());
fprintf(stderr, "SECOND_SZ [%u]\n", hdr->second_size());
if (ver) {
fprintf(stderr, "RECOV_DTBO_SZ [%u]\n", hdr->recovery_dtbo_size());
fprintf(stderr, "DTB [%u]\n", hdr->dtb_size());
} else {
fprintf(stderr, "EXTRA_SZ [%u]\n", hdr->extra_size());
}
ver = hdr->os_version();
if (ver) {
int a,b,c,y,m = 0;
int version, patch_level;
version = ver >> 11;
patch_level = ver & 0x7ff;
a = (version >> 14) & 0x7f;
b = (version >> 7) & 0x7f;
c = version & 0x7f;
fprintf(stderr, "OS_VERSION [%d.%d.%d]\n", a, b, c);
y = (patch_level >> 4) + 2000;
m = patch_level & 0xf;
fprintf(stderr, "OS_PATCH_LEVEL [%d-%02d]\n", y, m);
}
fprintf(stderr, "PAGESIZE [%u]\n", hdr->page_size());
fprintf(stderr, "NAME [%s]\n", hdr->name());
fprintf(stderr, "CMDLINE [%.512s%.1024s]\n", hdr->cmdline(), hdr->extra_cmdline());
fprintf(stderr, "CHECKSUM [");
for (int i = 0; hdr->id()[i]; ++i)
fprintf(stderr, "%02x", hdr->id()[i]);
fprintf(stderr, "]\n");
}
int unpack(const char *image, bool hdr) {
boot_img boot {};
int ret = boot.parse_file(image);
int fd;
boot_img boot(image);
if (hdr) {
FILE *fp = xfopen(HEADER_FILE, "w");
fprintf(fp, "pagesize=%u\n", boot.hdr->page_size());
fprintf(fp, "name=%s\n", boot.hdr->name());
fprintf(fp, "cmdline=%.512s%.1024s\n", boot.hdr->cmdline(), boot.hdr->extra_cmdline());
uint32_t ver = boot.hdr->os_version();
if (ver) {
int a, b, c, y, m = 0;
int version, patch_level;
version = ver >> 11;
patch_level = ver & 0x7ff;
a = (version >> 14) & 0x7f;
b = (version >> 7) & 0x7f;
c = version & 0x7f;
fprintf(fp, "os_version=%d.%d.%d\n", a, b, c);
y = (patch_level >> 4) + 2000;
m = patch_level & 0xf;
fprintf(fp, "os_patch_level=%d-%02d\n", y, m);
}
fclose(fp);
}
if (hdr)
boot.hdr->dump_hdr_file();
// Dump kernel
if (COMPRESSED(boot.k_fmt)) {
fd = creat(KERNEL_FILE, 0644);
int fd = creat(KERNEL_FILE, 0644);
decompress(boot.k_fmt, fd, boot.kernel, boot.hdr->kernel_size());
close(fd);
} else {
fprintf(stderr, "Kernel is uncompressed or not a supported compressed type!\n");
dump(boot.kernel, boot.hdr->kernel_size(), KERNEL_FILE);
}
// Dump dtb
// Dump kernel_dtb
dump(boot.kernel_dtb, boot.kernel_dt_size, KER_DTB_FILE);
// Dump ramdisk
if (COMPRESSED(boot.r_fmt)) {
fd = creat(RAMDISK_FILE, 0644);
int fd = creat(RAMDISK_FILE, 0644);
decompress(boot.r_fmt, fd, boot.ramdisk, boot.hdr->ramdisk_size());
close(fd);
} else {
fprintf(stderr, "Ramdisk is uncompressed or not a supported compressed type!\n");
dump(boot.ramdisk, boot.hdr->ramdisk_size(), RAMDISK_FILE);
}
......@@ -341,23 +346,30 @@ int unpack(const char *image, bool hdr) {
dump(boot.extra, boot.hdr->extra_size(), EXTRA_FILE);
// Dump recovery_dtbo
dump(boot.recov_dtbo, boot.hdr->recovery_dtbo_size(), RECV_DTBO_FILE);
dump(boot.recovery_dtbo, boot.hdr->recovery_dtbo_size(), RECV_DTBO_FILE);
// Dump dtb
dump(boot.dtb, boot.hdr->dtb_size(), DTB_FILE);
return ret;
return (boot.flags & CHROMEOS_FLAG) ? 2 : 0;
}
#define file_align() \
write_zero(fd, align_off(lseek(fd, 0, SEEK_CUR) - header_off, boot.hdr->page_size()))
write_zero(fd, align_off(lseek(fd, 0, SEEK_CUR) - off.header, boot.hdr->page_size()))
void repack(const char* orig_image, const char* out_image, bool force_nocomp) {
boot_img boot {};
void repack(const char* src_img, const char* out_img, bool nocomp) {
boot_img boot(src_img);
off_t header_off, kernel_off, ramdisk_off, second_off, extra_off, dtb_off;
struct {
uint32_t header;
uint32_t kernel;
uint32_t ramdisk;
uint32_t second;
uint32_t extra;
uint32_t dtb;
} off;
// Parse original image
boot.parse_file(orig_image);
fprintf(stderr, "Repack to boot image: [%s]\n", out_img);
// Reset sizes
boot.hdr->kernel_size() = 0;
......@@ -366,65 +378,36 @@ void repack(const char* orig_image, const char* out_image, bool force_nocomp) {
boot.hdr->dtb_size() = 0;
boot.kernel_dt_size = 0;
fprintf(stderr, "Repack to boot image: [%s]\n", out_image);
if (access(HEADER_FILE, R_OK) == 0)
boot.hdr->load_hdr_file();
/*****************
* Writing blocks
*****************/
// Create new image
int fd = creat(out_image, 0644);
int fd = creat(out_img, 0644);
if (boot.flags & DHTB_FLAG) {
// Skip DHTB header
write_zero(fd, 512);
write_zero(fd, sizeof(dhtb_hdr));
} else if (boot.flags & BLOB_FLAG) {
// Skip blob header
write_zero(fd, sizeof(blob_hdr));
restore_buf(fd, boot.map_addr, sizeof(blob_hdr));
} else if (boot.flags & NOOKHD_FLAG) {
restore_buf(fd, boot.map_addr, NOOKHD_PRE_HEADER_SZ);
} else if (boot.flags & ACCLAIM_FLAG) {
restore_buf(fd, boot.map_addr, ACCLAIM_PRE_HEADER_SZ);
}
// header
if (access(HEADER_FILE, R_OK) == 0) {
parse_prop_file(HEADER_FILE, [&](string_view key, string_view value) -> bool {
if (key == "page_size") {
boot.hdr->page_size() = parse_int(value);
} else if (key == "name") {
memset(boot.hdr->name(), 0, 16);
memcpy(boot.hdr->name(), value.data(), value.length() > 15 ? 15 : value.length());
} else if (key == "cmdline") {
memset(boot.hdr->cmdline(), 0, 512);
memset(boot.hdr->extra_cmdline(), 0, 1024);
if (value.length() > 512) {
memcpy(boot.hdr->cmdline(), value.data(), 512);
memcpy(boot.hdr->extra_cmdline(), &value[512], value.length() - 511);
} else {
memcpy(boot.hdr->cmdline(), value.data(), value.length());
}
} else if (key == "os_version") {
int patch_level = boot.hdr->os_version() & 0x7ff;
int a, b, c;
sscanf(value.data(), "%d.%d.%d", &a, &b, &c);
boot.hdr->os_version() = (((a << 14) | (b << 7) | c) << 11) | patch_level;
} else if (key == "os_patch_level") {
int os_version = boot.hdr->os_version() >> 11;
int y, m;
sscanf(value.data(), "%d-%d", &y, &m);
y -= 2000;
boot.hdr->os_version() = (os_version << 11) | (y << 4) | m;
}
return true;
});
}
// Skip a page for header
header_off = lseek(fd, 0, SEEK_CUR);
write_zero(fd, boot.hdr->page_size());
// Copy a page for header
off.header = lseek(fd, 0, SEEK_CUR);
restore_buf(fd, boot.hdr_addr, boot.hdr->page_size());
// kernel
kernel_off = lseek(fd, 0, SEEK_CUR);
off.kernel = lseek(fd, 0, SEEK_CUR);
if (boot.flags & MTK_KERNEL) {
// Skip MTK header
write_zero(fd, 512);
// Copy MTK headers
restore_buf(fd, boot.k_hdr, sizeof(mtk_hdr));
}
if (access(KERNEL_FILE, R_OK) == 0) {
size_t raw_size;
......@@ -433,60 +416,60 @@ void repack(const char* orig_image, const char* out_image, bool force_nocomp) {
if (!COMPRESSED_ANY(check_fmt(raw_buf, raw_size)) && COMPRESSED(boot.k_fmt)) {
boot.hdr->kernel_size() = compress(boot.k_fmt, fd, raw_buf, raw_size);
} else {
boot.hdr->kernel_size() = write(fd, raw_buf, raw_size);
boot.hdr->kernel_size() = xwrite(fd, raw_buf, raw_size);
}
munmap(raw_buf, raw_size);
}
// kernel dtb
if (access(KER_DTB_FILE, R_OK) == 0)
boot.hdr->kernel_size() += restore(KER_DTB_FILE, fd);
boot.hdr->kernel_size() += restore(fd, KER_DTB_FILE);
file_align();
// ramdisk
ramdisk_off = lseek(fd, 0, SEEK_CUR);
off.ramdisk = lseek(fd, 0, SEEK_CUR);
if (boot.flags & MTK_RAMDISK) {
// Skip MTK header
write_zero(fd, 512);
// Copy MTK headers
restore_buf(fd, boot.r_hdr, sizeof(mtk_hdr));
}
if (access(RAMDISK_FILE, R_OK) == 0) {
size_t raw_size;
void *raw_buf;
mmap_ro(RAMDISK_FILE, raw_buf, raw_size);
if (!COMPRESSED_ANY(check_fmt(raw_buf, raw_size)) && COMPRESSED(boot.r_fmt) && !force_nocomp) {
if (!COMPRESSED_ANY(check_fmt(raw_buf, raw_size)) && COMPRESSED(boot.r_fmt) && !nocomp) {
boot.hdr->ramdisk_size() = compress(boot.r_fmt, fd, raw_buf, raw_size);
} else {
boot.hdr->ramdisk_size() = write(fd, raw_buf, raw_size);
boot.hdr->ramdisk_size() = xwrite(fd, raw_buf, raw_size);
}
munmap(raw_buf, raw_size);
file_align();
}
// second
second_off = lseek(fd, 0, SEEK_CUR);
off.second = lseek(fd, 0, SEEK_CUR);
if (access(SECOND_FILE, R_OK) == 0) {
boot.hdr->second_size() = restore(SECOND_FILE, fd);
boot.hdr->second_size() = restore(fd, SECOND_FILE);
file_align();
}
// extra
extra_off = lseek(fd, 0, SEEK_CUR);
off.extra = lseek(fd, 0, SEEK_CUR);
if (access(EXTRA_FILE, R_OK) == 0) {
boot.hdr->extra_size() = restore(EXTRA_FILE, fd);
boot.hdr->extra_size() = restore(fd, EXTRA_FILE);
file_align();
}
// recovery_dtbo
if (access(RECV_DTBO_FILE, R_OK) == 0) {
boot.hdr->recovery_dtbo_offset() = lseek(fd, 0, SEEK_CUR);
boot.hdr->recovery_dtbo_size() = restore(RECV_DTBO_FILE, fd);
boot.hdr->recovery_dtbo_size() = restore(fd, RECV_DTBO_FILE);
file_align();
}
// dtb
dtb_off = lseek(fd, 0, SEEK_CUR);
off.dtb = lseek(fd, 0, SEEK_CUR);
if (access(DTB_FILE, R_OK) == 0) {
boot.hdr->dtb_size() = restore(DTB_FILE, fd);
boot.hdr->dtb_size() = restore(fd, DTB_FILE);
file_align();
}
......@@ -500,72 +483,73 @@ void repack(const char* orig_image, const char* out_image, bool force_nocomp) {
close(fd);
/*********************
* Patching the image
*********************/
// Map output image as rw
munmap(boot.map_addr, boot.map_size);
mmap_rw(out_image, boot.map_addr, boot.map_size);
mmap_rw(out_img, boot.map_addr, boot.map_size);
// MTK headers
if (boot.flags & MTK_KERNEL) {
boot.k_hdr->size = boot.hdr->kernel_size();
boot.hdr->kernel_size() += 512;
memcpy(boot.map_addr + kernel_off, boot.k_hdr, sizeof(mtk_hdr));
auto hdr = reinterpret_cast<mtk_hdr *>(boot.map_addr + off.kernel);
hdr->size = boot.hdr->kernel_size();
boot.hdr->kernel_size() += sizeof(*hdr);
}
if (boot.flags & MTK_RAMDISK) {
boot.r_hdr->size = boot.hdr->ramdisk_size();
boot.hdr->ramdisk_size() += 512;
memcpy(boot.map_addr + ramdisk_off, boot.r_hdr, sizeof(mtk_hdr));
auto hdr = reinterpret_cast<mtk_hdr *>(boot.map_addr + off.ramdisk);
hdr->size = boot.hdr->ramdisk_size();
boot.hdr->ramdisk_size() += sizeof(*hdr);
}
// Update checksum
HASH_CTX ctx;
(boot.flags & SHA256_FLAG) ? SHA256_init(&ctx) : SHA_init(&ctx);
uint32_t size = boot.hdr->kernel_size();
HASH_update(&ctx, boot.map_addr + kernel_off, size);
HASH_update(&ctx, boot.map_addr + off.kernel, size);
HASH_update(&ctx, &size, sizeof(size));
size = boot.hdr->ramdisk_size();
HASH_update(&ctx, boot.map_addr + ramdisk_off, size);
HASH_update(&ctx, boot.map_addr + off.ramdisk, size);
HASH_update(&ctx, &size, sizeof(size));
size = boot.hdr->second_size();
HASH_update(&ctx, boot.map_addr + second_off, size);
HASH_update(&ctx, boot.map_addr + off.second, size);
HASH_update(&ctx, &size, sizeof(size));
size = boot.hdr->extra_size();
if (size) {
HASH_update(&ctx, boot.map_addr + extra_off, size);
HASH_update(&ctx, boot.map_addr + off.extra, size);
HASH_update(&ctx, &size, sizeof(size));
}
if (boot.hdr->header_version()) {
if (boot.hdr->header_version() >= 1) {
size = boot.hdr->recovery_dtbo_size();
HASH_update(&ctx, boot.map_addr + boot.hdr->recovery_dtbo_offset(), size);
HASH_update(&ctx, &size, sizeof(size));
}
if (boot.hdr->header_version() >= 2) {
size = boot.hdr->dtb_size();
if (size) {
HASH_update(&ctx, boot.map_addr + dtb_off, size);
HASH_update(&ctx, boot.map_addr + off.dtb, size);
HASH_update(&ctx, &size, sizeof(size));
}
}
memset(boot.hdr->id(), 0, 32);
memcpy(boot.hdr->id(), HASH_final(&ctx),
(boot.flags & SHA256_FLAG) ? SHA256_DIGEST_SIZE : SHA_DIGEST_SIZE);
boot.hdr->header_size() = boot.hdr->hdr_size();
// Print new image info
boot.print_hdr();
// Try to fix the header
if (boot.hdr->header_version() && boot.hdr->header_size() == 0)
boot.hdr->header_size() = sizeof(boot_img_hdr);
boot.hdr->print();
// Main header
memcpy(boot.map_addr + header_off, **boot.hdr, boot.hdr->hdr_size());
memcpy(boot.map_addr + off.header, **boot.hdr, boot.hdr->hdr_size());
if (boot.flags & DHTB_FLAG) {
// DHTB header
dhtb_hdr *hdr = reinterpret_cast<dhtb_hdr *>(boot.map_addr);
auto hdr = reinterpret_cast<dhtb_hdr *>(boot.map_addr);
memcpy(hdr, DHTB_MAGIC, 8);
hdr->size = boot.map_size - 512;
SHA256_hash(boot.map_addr + 512, hdr->size, hdr->checksum);
hdr->size = boot.map_size - sizeof(dhtb_hdr);
SHA256_hash(boot.map_addr + sizeof(dhtb_hdr), hdr->size, hdr->checksum);
} else if (boot.flags & BLOB_FLAG) {
// Blob headers
boot.b_hdr->size = boot.map_size - sizeof(blob_hdr);
memcpy(boot.map_addr, boot.b_hdr, sizeof(blob_hdr));
// Blob header
auto hdr = reinterpret_cast<blob_hdr *>(boot.map_addr);
hdr->size = boot.map_size - sizeof(blob_hdr);
}
}
......@@ -4,6 +4,47 @@
#include <utility>
#include "format.h"
/****************
* Other Headers
****************/
struct mtk_hdr {
uint32_t magic; /* MTK magic */
uint32_t size; /* Size of the content */
char name[32]; /* The type of the header */
char padding[472]; /* Padding to 512 bytes */
} __attribute__((packed));
struct dhtb_hdr {
char magic[8]; /* DHTB magic */
uint8_t checksum[40]; /* Payload SHA256, whole image + SEANDROIDENFORCE + 0xFFFFFFFF */
uint32_t size; /* Payload size, whole image + SEANDROIDENFORCE + 0xFFFFFFFF */
char padding[460]; /* Padding to 512 bytes */
} __attribute__((packed));
struct blob_hdr {
char secure_magic[20]; /* "-SIGNED-BY-SIGNBLOB-" */
uint32_t datalen; /* 0x00000000 */
uint32_t signature; /* 0x00000000 */
char magic[16]; /* "MSM-RADIO-UPDATE" */
uint32_t hdr_version; /* 0x00010000 */
uint32_t hdr_size; /* Size of header */
uint32_t part_offset; /* Same as size */
uint32_t num_parts; /* Number of partitions */
uint32_t unknown[7]; /* All 0x00000000 */
char name[4]; /* Name of partition */
uint32_t offset; /* offset in blob where this partition starts */
uint32_t size; /* Size of data */
uint32_t version; /* 0x00000001 */
} __attribute__((packed));
/*********************
* Boot Image Headers
*********************/
struct boot_img_hdr_base {
char magic[8];
......@@ -57,12 +98,12 @@ struct boot_img_hdr_v2 : public boot_img_hdr_v1 {
} __attribute__((packed));
// Default to hdr v2
typedef boot_img_hdr_v2 boot_img_hdr;
using boot_img_hdr = boot_img_hdr_v2;
// Special Samsung header
struct boot_img_hdr_pxa : public boot_img_hdr_base {
uint32_t extra_size; /* extra blob size in bytes */
uint32_t unknown; /* unknown value */
uint32_t unknown;
uint32_t tags_addr; /* physical addr for kernel tags */
uint32_t page_size; /* flash page size we assume */
......@@ -111,34 +152,6 @@ struct boot_img_hdr_pxa : public boot_img_hdr_base {
** else: jump to kernel_addr
*/
struct mtk_hdr {
uint32_t magic; /* MTK magic */
uint32_t size; /* Size of the content */
char name[32]; /* The type of the header */
} __attribute__((packed));
struct dhtb_hdr {
char magic[8]; /* DHTB magic */
uint8_t checksum[40]; /* Payload SHA256, whole image + SEANDROIDENFORCE + 0xFFFFFFFF */
uint32_t size; /* Payload size, whole image + SEANDROIDENFORCE + 0xFFFFFFFF */
} __attribute__((packed));
struct blob_hdr {
char secure_magic[20]; /* "-SIGNED-BY-SIGNBLOB-" */
uint32_t datalen; /* 0x00000000 */
uint32_t signature; /* 0x00000000 */
char magic[16]; /* "MSM-RADIO-UPDATE" */
uint32_t hdr_version; /* 0x00010000 */
uint32_t hdr_size; /* Size of header */
uint32_t part_offset; /* Same as size */
uint32_t num_parts; /* Number of partitions */
uint32_t unknown[7]; /* All 0x00000000 */
char name[4]; /* Name of partition */
uint32_t offset; /* offset in blob where this partition starts */
uint32_t size; /* Size of data */
uint32_t version; /* 0x00000001 */
} __attribute__((packed));
#define drct_var(name) \
auto &name() { return img_hdr->name; }
#define decl_var(name, len) \
......@@ -179,6 +192,10 @@ struct dyn_img_hdr {
return img_hdr;
}
void print();
void dump_hdr_file();
void load_hdr_file();
protected:
union {
/* Main header could be either AOSP or PXA,
......@@ -199,10 +216,22 @@ private:
#undef decl_var
#undef decl_val
#define __impl_cls(name, hdr) \
protected: name() = default; \
public: \
name(void *ptr) { \
raw = xmalloc(sizeof(hdr)); \
memcpy(raw, ptr, sizeof(hdr)); \
} \
size_t hdr_size() override { return sizeof(hdr); }
#define impl_cls(ver) __impl_cls(dyn_img_##ver, boot_img_hdr_##ver)
#define impl_val(name) \
decltype(std::declval<dyn_img_hdr>().name()) name() override { return hdr_pxa->name; }
struct dyn_img_pxa : public dyn_img_hdr {
impl_cls(pxa)
impl_val(extra_size)
impl_val(page_size)
......@@ -210,15 +239,6 @@ struct dyn_img_pxa : public dyn_img_hdr {
impl_val(cmdline)
impl_val(id)
impl_val(extra_cmdline)
dyn_img_pxa(void *ptr) {
raw = xmalloc(sizeof(boot_img_hdr_pxa));
memcpy(raw, ptr, sizeof(boot_img_hdr_pxa));
}
size_t hdr_size() override {
return sizeof(boot_img_hdr_pxa);
}
};
#undef impl_val
......@@ -226,6 +246,7 @@ struct dyn_img_pxa : public dyn_img_hdr {
decltype(std::declval<dyn_img_hdr>().name()) name() override { return v2_hdr->name; }
struct dyn_img_v0 : public dyn_img_hdr {
impl_cls(v0)
impl_val(page_size)
impl_val(extra_size)
......@@ -234,90 +255,86 @@ struct dyn_img_v0 : public dyn_img_hdr {
impl_val(cmdline)
impl_val(id)
impl_val(extra_cmdline)
dyn_img_v0(void *ptr) {
raw = xmalloc(sizeof(boot_img_hdr_v2));
memcpy(raw, ptr, sizeof(boot_img_hdr_v2));
}
size_t hdr_size() override {
return sizeof(boot_img_hdr_v2);
}
};
struct dyn_img_v1 : public dyn_img_v0 {
impl_cls(v1)
impl_val(header_version)
impl_val(recovery_dtbo_size)
impl_val(recovery_dtbo_offset)
impl_val(header_size)
dyn_img_v1(void *ptr) : dyn_img_v0(ptr) {}
uint32_t &extra_size() override {
return dyn_img_hdr::extra_size();
}
};
struct dyn_img_v2 : public dyn_img_v1 {
impl_cls(v2)
impl_val(dtb_size)
dyn_img_v2(void *ptr) : dyn_img_v1(ptr) {}
};
#undef __impl_cls
#undef impl_cls
#undef impl_val
// Flags
#define MTK_KERNEL 1 << 1
#define MTK_RAMDISK 1 << 2
#define CHROMEOS_FLAG 1 << 3
#define DHTB_FLAG 1 << 4
#define SEANDROID_FLAG 1 << 5
#define LG_BUMP_FLAG 1 << 6
#define SHA256_FLAG 1 << 7
#define BLOB_FLAG 1 << 8
#define NOOKHD_FLAG 1 << 9
#define ACCLAIM_FLAG 1 << 10
#define MTK_KERNEL (1 << 0)
#define MTK_RAMDISK (1 << 1)
#define CHROMEOS_FLAG (1 << 2)
#define DHTB_FLAG (1 << 3)
#define SEANDROID_FLAG (1 << 4)
#define LG_BUMP_FLAG (1 << 5)
#define SHA256_FLAG (1 << 6)
#define BLOB_FLAG (1 << 7)
#define NOOKHD_FLAG (1 << 8)
#define ACCLAIM_FLAG (1 << 9)
struct boot_img {
// Memory map of the whole image
uint8_t *map_addr;
size_t map_size;
// Headers
dyn_img_hdr *hdr; /* Android image header */
mtk_hdr *k_hdr; /* MTK kernel header */
mtk_hdr *r_hdr; /* MTK ramdisk header */
blob_hdr *b_hdr; /* Tegra blob header */
// Android image header
dyn_img_hdr *hdr;
// Flags to indicate the state of current boot image
uint16_t flags;
uint16_t flags = 0;
// The format of kernel and ramdisk
format_t k_fmt;
format_t r_fmt;
/***************************************************
* Following pointers points within the mmap region
***************************************************/
// MTK headers
mtk_hdr *k_hdr;
mtk_hdr *r_hdr;
// Pointer to dtb that is appended after kernel
uint8_t *kernel_dtb;
uint32_t kernel_dt_size;
uint32_t kernel_dt_size = 0;
// Pointer to end of image
uint8_t *tail;
size_t tail_size;
size_t tail_size = 0;
// Pointers to blocks defined in header
uint8_t *hdr_addr;
uint8_t *kernel;
uint8_t *ramdisk;
uint8_t *second;
uint8_t *extra;
uint8_t *recov_dtbo;
uint8_t *recovery_dtbo;
uint8_t *dtb;
boot_img(const char *);
~boot_img();
int parse_file(const char *);
int parse_image(uint8_t *);
void find_dtb();
void print_hdr();
void parse_image(uint8_t *addr);
void find_kernel_dtb();
};
......@@ -62,7 +62,7 @@ void decompress(char *infile, const char *outfile) {
out_fd = strcmp(outfile, "-") == 0 ?
STDOUT_FILENO : xopen(outfile, O_WRONLY | O_CREAT | O_TRUNC, 0644);
cmp->set_out(make_unique<FDOutStream>(out_fd));
cmp->setOut(make_unique<FDOutStream>(out_fd));
if (ext) *ext = '.';
}
if (!cmp->write(buf, len))
......@@ -108,7 +108,7 @@ void compress(const char *method, const char *infile, const char *outfile) {
STDOUT_FILENO : xopen(outfile, O_WRONLY | O_CREAT | O_TRUNC, 0644);
}
cmp->set_out(make_unique<FDOutStream>(out_fd));
cmp->setOut(make_unique<FDOutStream>(out_fd));
read_file(in_file, [&](void *buf, size_t len) -> void {
if (!cmp->write(buf, len))
......
......@@ -6,7 +6,7 @@
#include <lz4.h>
#include <lz4frame.h>
#include <lz4hc.h>
#include <OutStream.h>
#include <stream.h>
#include "format.h"
......
......@@ -7,12 +7,102 @@ extern "C" {
#include <utils.h>
#include <bitset>
#include <vector>
#include <map>
#include "magiskboot.h"
#include "format.h"
using namespace std;
#define DTB_MAGIC "\xd0\x0d\xfe\xed"
#define QCDT_MAGIC "QCDT"
#define DTBH_MAGIC "DTBH"
#define PXADT_MAGIC "PXA-DT"
#define PXA_19xx_MAGIC "PXA-19xx"
struct qcdt_hdr {
char magic[4]; /* "QCDT" */
uint32_t version; /* QCDT version */
uint32_t num_dtbs; /* Number of DTBs */
} __attribute__((packed));
struct qctable_v1 {
uint32_t cpu_info[3]; /* Some CPU info */
uint32_t offset; /* DTB offset in QCDT */
uint32_t len; /* DTB size */
} __attribute__((packed));
struct qctable_v2 {
uint32_t cpu_info[4]; /* Some CPU info */
uint32_t offset; /* DTB offset in QCDT */
uint32_t len; /* DTB size */
} __attribute__((packed));
struct qctable_v3 {
uint32_t cpu_info[8]; /* Some CPU info */
uint32_t offset; /* DTB offset in QCDT */
uint32_t len; /* DTB size */
} __attribute__((packed));
struct dtbh_hdr {
char magic[4]; /* "DTBH" */
uint32_t version; /* DTBH version */
uint32_t num_dtbs; /* Number of DTBs */
} __attribute__((packed));
struct bhtable_v2 {
uint32_t cpu_info[5]; /* Some CPU info */
uint32_t offset; /* DTB offset in DTBH */
uint32_t len; /* DTB size */
uint32_t space; /* 0x00000020 */
} __attribute__((packed));
struct pxadt_hdr {
char magic[6]; /* "PXA-DT" */
uint32_t version; /* PXA-DT version */
uint32_t num_dtbs; /* Number of DTBs */
} __attribute__((packed));
struct pxa_19xx_hdr {
char magic[8]; /* "PXA-19xx" */
uint32_t version; /* PXA-DT version */
uint32_t num_dtbs; /* Number of DTBs */
} __attribute__((packed));
struct pxa_table_v1 {
uint32_t cpu_info[2]; /* Some CPU info */
uint32_t offset; /* DTB offset in PXA-DT */
uint32_t len; /* DTB size */
} __attribute__((packed));
struct dtb_blob {
void *fdt;
uint32_t offset;
uint32_t len;
};
template <class Iter>
class fdt_map_iter {
public:
typedef decltype(std::declval<typename Iter::value_type::second_type>().fdt) value_type;
typedef value_type* pointer;
typedef value_type& reference;
explicit fdt_map_iter(Iter j) : i(j) {}
fdt_map_iter& operator++() { ++i; return *this; }
fdt_map_iter operator++(int) { auto tmp = *this; ++(*this); return tmp; }
fdt_map_iter& operator--() { --i; return *this; }
fdt_map_iter operator--(int) { auto tmp = *this; --(*this); return tmp; }
bool operator==(fdt_map_iter j) const { return i == j.i; }
bool operator!=(fdt_map_iter j) const { return !(*this == j); }
reference operator*() { return i->second.fdt; }
pointer operator->() { return &i->second.fdt; }
private:
Iter i;
};
template<class Iter>
inline fdt_map_iter<Iter> make_iter(Iter j) { return fdt_map_iter<Iter>(j); }
constexpr int MAX_DEPTH = 32;
static bitset<MAX_DEPTH> depth_set;
......@@ -116,35 +206,27 @@ static void dtb_print(const char *file, bool fstab) {
print_node(fdt);
}
++dtb_num;
i += fdt_totalsize(fdt) - 1;
}
}
fprintf(stderr, "\n");
munmap(dtb, size);
exit(0);
}
static void dtb_patch(const char *in, const char *out) {
template <typename Iter>
static bool fdt_patch(Iter first, Iter last) {
bool keepverity = check_env("KEEPVERITY");
bool redirect = check_env("TWOSTAGEINIT");
vector<uint8_t *> fdt_list;
size_t dtb_sz ;
uint8_t *dtb;
fprintf(stderr, "Loading dtbs from [%s]\n", in);
mmap_ro(in, dtb, dtb_sz);
bool modified = false;
for (int i = 0; i < dtb_sz; ++i) {
if (memcmp(dtb + i, DTB_MAGIC, 4) == 0) {
int len = fdt_totalsize(dtb + i);
auto fdt = static_cast<uint8_t *>(xmalloc(redirect ? len + 256 : len));
memcpy(fdt, dtb + i, len);
if (redirect)
fdt_open_into(fdt, fdt, len + 256);
int idx = 0;
for (auto it = first; it != last; ++it) {
++idx;
auto fdt = *it;
int fstab = find_fstab(fdt);
if (fstab < 0)
continue;
fprintf(stderr, "Found fstab in dtb.%04d\n", fdt_list.size());
fprintf(stderr, "Found fstab in dtb.%04d\n", idx - 1);
int block;
fdt_for_each_subnode(block, fdt, fstab) {
const char *name = fdt_get_name(fdt, block, nullptr);
......@@ -165,21 +247,153 @@ static void dtb_patch(const char *in, const char *out) {
fdt_setprop_string(fdt, block, "mnt_point", "/system_root");
}
}
fdt_list.push_back(fdt);
}
return modified;
}
template <class Table, class Header>
static int dtb_patch(const Header *hdr, const char *in, const char *out) {
map<uint32_t, dtb_blob> dtb_map;
auto buf = reinterpret_cast<const uint8_t *>(hdr);
auto tables = reinterpret_cast<const Table *>(hdr + 1);
// Collect all dtbs
for (int i = 0; i < hdr->num_dtbs; ++i) {
if (dtb_map.find(tables[i].offset) == dtb_map.end()) {
auto blob = buf + tables[i].offset;
int size = fdt_totalsize(blob);
auto fdt = xmalloc(size + 256);
memcpy(fdt, blob, size);
fdt_open_into(fdt, fdt, size + 256);
dtb_map[tables[i].offset] = { fdt, tables[i].offset };
}
}
if (dtb_map.empty())
return 1;
// Patch fdt
if (!fdt_patch(make_iter(dtb_map.begin()), make_iter(dtb_map.end())))
return 1;
if (out == in)
unlink(in);
int fd = xopen(out, O_RDWR | O_CREAT | O_TRUNC | O_CLOEXEC, 0644);
// Copy headers and tables
xwrite(fd, buf, dtb_map.begin()->first);
// mmap rw to patch table values retroactively
auto mmap_sz = lseek(fd, 0, SEEK_CUR);
auto addr = (uint8_t *) xmmap(nullptr, mmap_sz, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
// Guess page size using gcd
auto it = dtb_map.begin();
uint32_t page_size = (it++)->first;
for (; it != dtb_map.end(); ++it)
page_size = binary_gcd(page_size, it->first);
// Write dtbs
for (auto &val : dtb_map) {
val.second.offset = lseek(fd, 0, SEEK_CUR);
auto fdt = val.second.fdt;
fdt_pack(fdt);
int size = fdt_totalsize(fdt);
xwrite(fd, fdt, size);
val.second.len = do_align(size, page_size);
write_zero(fd, align_off(lseek(fd, 0, SEEK_CUR), page_size));
free(fdt);
}
// Patch tables
auto tables_rw = reinterpret_cast<Table *>(addr + sizeof(Header));
for (int i = 0; i < hdr->num_dtbs; ++i) {
auto &blob = dtb_map[tables_rw[i].offset];
tables_rw[i].offset = blob.offset;
tables_rw[i].len = blob.len;
}
munmap(dtb, dtb_sz);
if (modified) {
munmap(addr, mmap_sz);
close(fd);
return 0;
}
#define MATCH(s) (memcmp(dtb, s, sizeof(s) - 1) == 0)
static int dtb_patch(const char *in, const char *out) {
if (!out)
out = in;
int fd = xopen(out, O_WRONLY | O_CREAT | O_CLOEXEC);
size_t dtb_sz ;
uint8_t *dtb;
fprintf(stderr, "Loading dtbs from [%s]\n", in);
mmap_ro(in, dtb, dtb_sz);
run_finally f([&]{ munmap(dtb, dtb_sz); });
if (MATCH(QCDT_MAGIC)) {
auto hdr = reinterpret_cast<qcdt_hdr*>(dtb);
switch (hdr->version) {
case 1:
fprintf(stderr, "QCDT v1\n");
return dtb_patch<qctable_v1>(hdr, in, out);
case 2:
fprintf(stderr, "QCDT v2\n");
return dtb_patch<qctable_v2>(hdr, in, out);
case 3:
fprintf(stderr, "QCDT v3\n");
return dtb_patch<qctable_v3>(hdr, in, out);
default:
return 1;
}
} else if (MATCH(DTBH_MAGIC)) {
auto hdr = reinterpret_cast<dtbh_hdr *>(dtb);
switch (hdr->version) {
case 2:
fprintf(stderr, "DTBH v2\n");
return dtb_patch<bhtable_v2>(hdr, in, out);
default:
return 1;
}
} else if (MATCH(PXADT_MAGIC)) {
auto hdr = reinterpret_cast<pxadt_hdr *>(dtb);
switch (hdr->version) {
case 1:
fprintf(stderr, "PXA-DT v1\n");
return dtb_patch<pxa_table_v1>(hdr, in, out);
default:
return 1;
}
} else if (MATCH(PXA_19xx_MAGIC)) {
auto hdr = reinterpret_cast<pxa_19xx_hdr *>(dtb);
switch (hdr->version) {
case 1:
fprintf(stderr, "PXA-19xx v1\n");
return dtb_patch<pxa_table_v1>(hdr, in, out);
default:
return 1;
}
} else {
vector<uint8_t *> fdt_list;
for (int i = 0; i < dtb_sz; ++i) {
if (memcmp(dtb + i, DTB_MAGIC, 4) == 0) {
int len = fdt_totalsize(dtb + i);
auto fdt = static_cast<uint8_t *>(xmalloc(len + 256));
memcpy(fdt, dtb + i, len);
fdt_open_into(fdt, fdt, len + 256);
fdt_list.push_back(fdt);
i += len - 1;
}
}
if (!fdt_patch(fdt_list.begin(), fdt_list.end()))
return 1;
int fd = xopen(out, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0644);
for (auto fdt : fdt_list) {
fdt_pack(fdt);
xwrite(fd, fdt, fdt_totalsize(fdt));
free(fdt);
}
close(fd);
}
exit(!modified);
return 0;
}
int dtb_commands(int argc, char *argv[]) {
......@@ -189,11 +403,12 @@ int dtb_commands(int argc, char *argv[]) {
if (argv[0] == "print"sv) {
dtb_print(dtb, argc > 1 && argv[1] == "-f"sv);
return 0;
} else if (argv[0] == "patch"sv) {
dtb_patch(dtb, argv[1]);
if (dtb_patch(dtb, argv[1]))
exit(1);
return 0;
} else {
return 1;
}
return 0;
}
......@@ -44,8 +44,6 @@ format_t check_fmt(const void *buf, size_t len) {
return LZ4_LEGACY;
} else if (MATCH(MTK_MAGIC)) {
return MTK;
} else if (MATCH(DTB_MAGIC)) {
return DTB;
} else if (MATCH(DHTB_MAGIC)) {
return DHTB;
} else if (MATCH(TEGRABLOB_MAGIC)) {
......@@ -77,8 +75,6 @@ const char *Fmt2Name::operator[](format_t fmt) {
return "lz4_legacy";
case MTK:
return "mtk";
case DTB:
return "dtb";
default:
return "raw";
}
......
......@@ -38,7 +38,6 @@ typedef enum {
#define LZ41_MAGIC "\x03\x21\x4c\x18"
#define LZ42_MAGIC "\x04\x22\x4d\x18"
#define MTK_MAGIC "\x88\x16\x88\x58"
#define DTB_MAGIC "\xd0\x0d\xfe\xed"
#define LG_BUMP_MAGIC "\x41\xa9\xe4\x67\x74\x4d\x1d\x1b\xa4\x29\xf2\xec\xea\x65\x52\x79"
#define DHTB_MAGIC "\x44\x48\x54\x42\x01\x00\x00\x00"
#define SEANDROID_MAGIC "SEANDROIDENFORCE"
......
......@@ -13,11 +13,11 @@
#define NEW_BOOT "new-boot.img"
int unpack(const char *image, bool hdr = false);
void repack(const char* orig_image, const char* out_image, bool force_nocomp = false);
void repack(const char* src_img, const char* out_img, bool nocomp = false);
int hexpatch(const char *image, const char *from, const char *to);
int cpio_commands(int argc, char *argv[]);
int dtb_commands(int argc, char *argv[]);
char *patch_verity(const void *buf, uint32_t &size);
void patch_encryption(void **buf, uint32_t *size);
char *patch_verity(const void *buf, uint32_t &size, bool inplace = false);
void patch_encryption(void *buf, uint32_t &size);
bool check_env(const char *name);
......@@ -22,20 +22,20 @@ static int check_verity_pattern(const char *s) {
}
static int check_encryption_pattern(const char *s) {
const char *encrypt_list[] = { "forceencrypt", "forcefdeorfbe", nullptr };
for (int i = 0 ; encrypt_list[i]; ++i) {
int len = strlen(encrypt_list[i]);
if (strncmp(s, encrypt_list[i], len) == 0)
static const char *encrypt_list[] = { "forceencrypt", "forcefdeorfbe" };
for (auto enc : encrypt_list) {
int len = strlen(enc);
if (strncmp(s, enc, len) == 0)
return len;
}
return -1;
}
char *patch_verity(const void *buf, uint32_t &size) {
char *patch_verity(const void *buf, uint32_t &size, bool inplace) {
auto src = static_cast<const char *>(buf);
int src_size = size;
bool found = false;
char patched[4096];
auto patched = (char *)(inplace ? buf : xmalloc(size));
int write = 0;
for (int read = 0; read < src_size; ++read, ++write) {
if (int skip; (skip = check_verity_pattern(src + read)) > 0) {
......@@ -47,24 +47,25 @@ char *patch_verity(const void *buf, uint32_t &size) {
patched[write] = src[read];
}
patched[write] = '\0';
return found ? strdup(patched) : nullptr;
if (!found) {
if (!inplace)
free(patched);
return nullptr;
}
return patched;
}
void patch_encryption(void **buf, uint32_t *size) {
int skip, src_size = *size;
char *src = (char *) *buf, *patched = (char *) xcalloc(src_size, 1);
for (int read = 0, write = 0; read < src_size; ++read, ++write) {
if ((skip = check_encryption_pattern(src + read)) > 0) {
fprintf(stderr, "Replace pattern [%.*s] with [encryptable]\n", skip, src + read);
memcpy(patched + read, "encryptable", 11);
void patch_encryption(void *buf, uint32_t &size) {
auto src = static_cast<char *>(buf);
int src_size = size;
int write = 0;
for (int read = 0; read < src_size; ++read, ++write) {
if (int skip; (skip = check_encryption_pattern(src + read)) > 0) {
fprintf(stderr, "Found pattern [%.*s]\n", skip, src + read);
size -= skip;
read += skip;
write += 11;
*size -= (skip - 11);
}
patched[write] = src[read];
src[write] = src[read];
}
free(*buf);
*buf = patched;
src[write] = '\0';
}
......@@ -52,11 +52,7 @@ void magisk_cpio::patch() {
if (!keepverity) {
if (fstab) {
fprintf(stderr, "Found fstab file [%s]\n", cur->first.data());
auto buf = patch_verity(cur->second->data, cur->second->filesize);
if (buf) {
free(cur->second->data);
cur->second->data = buf;
}
patch_verity(cur->second->data, cur->second->filesize, true);
} else if (cur->first == "verity_key") {
rm(cur);
continue;
......@@ -64,7 +60,7 @@ void magisk_cpio::patch() {
}
if (!keepforceencrypt) {
if (fstab) {
patch_encryption(&cur->second->data, &cur->second->filesize);
patch_encryption(cur->second->data, cur->second->filesize);
}
}
}
......@@ -246,13 +242,13 @@ void magisk_cpio::compress() {
fprintf(stderr, "Compressing cpio -> [%s]\n", RAMDISK_XZ);
auto init = entries.extract("init");
XZEncoder encoder;
encoder.set_out(make_unique<BufOutStream>());
encoder.setOut(make_unique<BufOutStream>());
output(encoder);
encoder.finalize();
entries.clear();
entries.insert(std::move(init));
auto xz = new cpio_entry(RAMDISK_XZ, S_IFREG);
static_cast<BufOutStream *>(encoder.get_out())->release(xz->data, xz->filesize);
static_cast<BufOutStream *>(encoder.getOut())->release(xz->data, xz->filesize);
insert(xz);
}
......@@ -262,13 +258,13 @@ void magisk_cpio::decompress() {
return;
fprintf(stderr, "Decompressing cpio [%s]\n", RAMDISK_XZ);
LZMADecoder decoder;
decoder.set_out(make_unique<BufOutStream>());
decoder.setOut(make_unique<BufOutStream>());
decoder.write(it->second->data, it->second->filesize);
decoder.finalize();
entries.erase(it);
char *buf;
size_t sz;
static_cast<BufOutStream *>(decoder.get_out())->getbuf(buf, sz);
static_cast<BufOutStream *>(decoder.getOut())->getbuf(buf, sz);
load_cpio(buf, sz);
}
......
......@@ -36,7 +36,7 @@ static inline void lazy_unmount(const char* mountpoint) {
}
void hide_daemon(int pid) {
RunFinally fin([=]() -> void {
run_finally fin([=]() -> void {
// Send resume signal
kill(pid, SIGCONT);
_exit(0);
......
......@@ -98,7 +98,7 @@ static int add_list(const char *pkg, const char *proc = "") {
// Critical region
{
MutexGuard lock(monitor_lock);
mutex_guard lock(monitor_lock);
hide_set.emplace(pkg, proc);
}
......@@ -119,7 +119,7 @@ int add_list(int client) {
static int rm_list(const char *pkg, const char *proc = "") {
{
// Critical region
MutexGuard lock(monitor_lock);
mutex_guard lock(monitor_lock);
bool remove = false;
auto next = hide_set.begin();
decltype(next) cur;
......
......@@ -76,7 +76,7 @@ static inline long xptrace(int request, pid_t pid, void *addr = nullptr, intptr_
}
void update_uid_map() {
MutexGuard lock(monitor_lock);
mutex_guard lock(monitor_lock);
uid_proc_map.clear();
string data_path(APP_DATA_DIR);
data_path += "/0/";
......@@ -336,7 +336,7 @@ void proc_monitor() {
continue;
}
bool detach = false;
RunFinally detach_task([&] {
run_finally f([&] {
if (detach)
// Non of our business now
detach_pid(pid);
......
......@@ -5,6 +5,7 @@
#include <memory>
#include <db.h>
#include <utils.h>
#define DEFAULT_SHELL "/system/bin/sh"
......@@ -29,8 +30,7 @@ public:
su_info(unsigned uid = 0);
~su_info();
void lock();
void unlock();
mutex_guard lock();
bool is_fresh();
void refresh();
......
......@@ -33,12 +33,8 @@ su_info::~su_info() {
pthread_mutex_destroy(&_lock);
}
void su_info::lock() {
pthread_mutex_lock(&_lock);
}
void su_info::unlock() {
pthread_mutex_unlock(&_lock);
mutex_guard su_info::lock() {
return mutex_guard(_lock);
}
bool su_info::is_fresh() {
......@@ -89,17 +85,14 @@ static shared_ptr<su_info> get_su_info(unsigned uid) {
shared_ptr<su_info> info;
{
MutexGuard lock(cache_lock);
mutex_guard lock(cache_lock);
if (!cached || cached->uid != uid || !cached->is_fresh())
cached = make_shared<su_info>(uid);
cached->refresh();
info = cached;
}
info->lock();
RunFinally unlock([&] {
info->unlock();
});
auto g = info->lock();
if (info->access.policy == QUERY) {
// Not cached, get data from database
......
......@@ -2,41 +2,41 @@
#include <pthread.h>
#include <deque>
#include <misc.h>
template<typename T>
class BlockingQueue {
class blocking_queue {
std::deque<T> deque{};
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
bool cancelled = false;
public:
~BlockingQueue();
~blocking_queue();
T take();
T &front();
T &back();
void put(const T&);
void put(T&&);
T &front() const;
T &back() const;
void push(const T&);
void push(T&&);
template< class... Args >
void emplace_back(Args&&... args);
void emplace(Args &&... args);
void clear();
void cancel();
};
#define run_and_notify(block) \
pthread_mutex_lock(&this->lock); \
mutex_guard g(this->lock); \
block \
pthread_cond_signal(&this->cond); \
pthread_mutex_unlock(&this->lock);
pthread_cond_signal(&this->cond);
template<typename T>
BlockingQueue<T>::~BlockingQueue() {
blocking_queue<T>::~blocking_queue() {
pthread_mutex_destroy(&lock);
pthread_cond_destroy(&cond);
}
template<typename T>
T BlockingQueue<T>::take() {
pthread_mutex_lock(&lock);
T blocking_queue<T>::take() {
mutex_guard g(lock);
cancelled = false;
while (deque.empty() && !cancelled)
pthread_cond_wait(&cond, &lock);
......@@ -44,55 +44,51 @@ T BlockingQueue<T>::take() {
pthread_exit(nullptr);
T ret(std::move(deque.front()));
deque.pop_front();
pthread_mutex_unlock(&lock);
return ret;
}
template<typename T>
void BlockingQueue<T>::put(const T &s) {
void blocking_queue<T>::push(const T &s) {
run_and_notify({ deque.push_back(s); })
}
template<typename T>
void BlockingQueue<T>::put(T &&s) {
void blocking_queue<T>::push(T &&s) {
run_and_notify({ deque.push_back(std::move(s)); })
}
template<typename T>
T &BlockingQueue<T>::front() {
pthread_mutex_lock(&lock);
auto &ret = deque.front();
pthread_mutex_unlock(&lock);
return ret;
T &blocking_queue<T>::front() const {
mutex_guard g(lock);
return deque.front();
}
template<typename T>
T &BlockingQueue<T>::back() {
pthread_mutex_lock(&lock);
auto &ret = deque.back();
pthread_mutex_unlock(&lock);
return ret;
T &blocking_queue<T>::back() const {
mutex_guard g(lock);
return deque.back();
}
template<typename T>
template<class... Args>
void BlockingQueue<T>::emplace_back(Args &&... args) {
void blocking_queue<T>::emplace(Args &&... args) {
run_and_notify({ deque.emplace_back(std::forward<Args>(args)...); })
}
template<typename T>
void BlockingQueue<T>::clear() {
pthread_mutex_lock(&lock);
void blocking_queue<T>::clear() {
mutex_guard g(lock);
std::deque<T> t;
deque.swap(t);
pthread_mutex_unlock(&lock);
}
template<typename T>
void BlockingQueue<T>::cancel() {
void blocking_queue<T>::cancel() {
run_and_notify({
cancelled = true;
std::deque<T> t;
deque.swap(t);
})
}
#undef run_and_notify
......@@ -6,7 +6,7 @@
#include <map>
#include <string_view>
#include <OutStream.h>
#include <stream.h>
struct cpio_newc_header;
......
......@@ -19,9 +19,9 @@ public:
FilterOutStream(strm_ptr &&ptr) : out(std::move(ptr)) {}
void set_out(strm_ptr &&ptr) { out = std::move(ptr); }
void setOut(strm_ptr &&ptr) { out = std::move(ptr); }
OutStream *get_out() { return out.get(); }
OutStream *getOut() { return out.get(); }
bool write(const void *buf, size_t len) override {
return out ? out->write(buf, len) : false;
......
......@@ -29,7 +29,7 @@ void no_logging() {
#define LOG_TAG "Magisk"
static int log_d(const char *fmt, va_list ap) {
[[maybe_unused]] static int log_d(const char *fmt, va_list ap) {
return __android_log_vprint(ANDROID_LOG_DEBUG, LOG_TAG, fmt, ap);
}
......
......@@ -181,3 +181,20 @@ int parse_int(const char *s) {
}
return val;
}
uint32_t binary_gcd(uint32_t u, uint32_t v) {
if (u == 0) return v;
if (v == 0) return u;
auto shift = __builtin_ctz(u | v);
u >>= __builtin_ctz(u);
do {
v >>= __builtin_ctz(v);
if (u > v) {
auto t = v;
v = u;
u = t;
}
v -= u;
} while (v != 0);
return u << shift;
}
......@@ -14,6 +14,7 @@ char *rtrim(char *str);
void init_argv0(int argc, char **argv);
void set_nice_name(const char *name);
int parse_int(const char *s);
uint32_t binary_gcd(uint32_t u, uint32_t v);
#ifdef __cplusplus
}
......@@ -27,17 +28,17 @@ void gen_rand_str(char *buf, int len, bool varlen = true);
#define str_contains(s, ss) ((ss) != nullptr && (s).find(ss) != std::string::npos)
#define str_starts(s, ss) ((ss) != nullptr && (s).compare(0, strlen(ss), ss) == 0)
class MutexGuard {
class mutex_guard {
public:
explicit MutexGuard(pthread_mutex_t &m): mutex(&m) {
explicit mutex_guard(pthread_mutex_t &m): mutex(&m) {
pthread_mutex_lock(mutex);
}
explicit MutexGuard(pthread_mutex_t *m): mutex(m) {
explicit mutex_guard(pthread_mutex_t *m): mutex(m) {
pthread_mutex_lock(mutex);
}
~MutexGuard() {
~mutex_guard() {
pthread_mutex_unlock(mutex);
}
......@@ -45,13 +46,13 @@ private:
pthread_mutex_t *mutex;
};
class RunFinally {
class run_finally {
public:
explicit RunFinally(std::function<void()> &&fn): fn(std::move(fn)) {}
explicit run_finally(std::function<void()> &&fn): fn(std::move(fn)) {}
void disable() { fn = nullptr; }
~RunFinally() { if (fn) fn(); }
~run_finally() { if (fn) fn(); }
private:
std::function<void ()> fn;
......
......@@ -31,7 +31,7 @@ if [ ! -f /system/build.prop ]; then
cd "`dirname "$0"`/.."
adb push native/out/x86/busybox scripts/emulator.sh /data/local/tmp
emu_arch=`adb shell uname -m`
if [ $emu_arch = "x86_64" ]; then
if [ "$emu_arch" = "x86_64" ]; then
adb push native/out/x86/magiskinit64 /data/local/tmp/magiskinit
else
adb push native/out/x86/magiskinit /data/local/tmp
......
......@@ -3,6 +3,7 @@ apply plugin: 'com.android.library'
android {
defaultConfig {
vectorDrawables.useSupportLibrary = true
consumerProguardFiles 'proguard-rules.pro'
}
}
......
......@@ -19,3 +19,7 @@
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
-keepclassmembers class * implements javax.net.ssl.SSLSocketFactory {
** delegate;
}
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.topjohnwu.shared">
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
......@@ -9,6 +10,12 @@
android:installLocation="internalOnly"
android:label="@string/app_name"
android:supportsRtl="true">
<activity
android:name="a.r"
android:theme="@android:style/Theme.Translucent.NoTitleBar"
android:process=":remote" />
<provider
android:name="a.p"
android:authorities="${applicationId}.provider"
......
package a;
import com.topjohnwu.magisk.ProcessPhoenix;
public class r extends ProcessPhoenix {
}
/*
* Copyright (C) 2014 Jake Wharton
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.topjohnwu.magisk;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import java.util.ArrayList;
import java.util.Arrays;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
/**
* Modified from JakeWharton/ProcessPhoenix
*
* Process Phoenix facilitates restarting your application process. This should only be used for
* things like fundamental state changes in your debug builds (e.g., changing from staging to
* production).
* <p>
* Trigger process recreation by calling {@link #triggerRebirth} with a {@link Context} instance.
*/
public class ProcessPhoenix extends Activity {
private static final String KEY_RESTART_INTENTS = "phoenix_restart_intents";
/**
* Call to restart the application process using the {@linkplain Intent#CATEGORY_DEFAULT default}
* activity as an intent.
* <p>
* Behavior of the current process after invoking this method is undefined.
*/
public static void triggerRebirth(Context context) {
triggerRebirth(context, getRestartIntent(context));
}
/**
* Call to restart the application process using the specified intents.
* <p>
* Behavior of the current process after invoking this method is undefined.
*/
public static void triggerRebirth(Context context, Intent... nextIntents) {
Intent intent = new Intent(context, a.r.class);
intent.addFlags(FLAG_ACTIVITY_NEW_TASK); // In case we are called with non-Activity context.
intent.putParcelableArrayListExtra(KEY_RESTART_INTENTS, new ArrayList<>(Arrays.asList(nextIntents)));
context.startActivity(intent);
if (context instanceof Activity) {
((Activity) context).finish();
}
Runtime.getRuntime().exit(0);
}
private static Intent getRestartIntent(Context context) {
String packageName = context.getPackageName();
Intent defaultIntent = context.getPackageManager().getLaunchIntentForPackage(packageName);
if (defaultIntent != null) {
defaultIntent.addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK);
return defaultIntent;
}
throw new IllegalStateException("Unable to determine default activity for "
+ packageName
+ ". Does an activity specify the DEFAULT category in its intent filter?");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ArrayList<Intent> intents = getIntent().getParcelableArrayListExtra(KEY_RESTART_INTENTS);
startActivities(intents.toArray(new Intent[0]));
finish();
Runtime.getRuntime().exit(0);
}
}
......@@ -35,7 +35,7 @@ public class Networking {
return request(url, "GET");
}
public static void init(Context context) {
public static boolean init(Context context) {
try {
// Try installing new SSL provider from Google Play Service
Context gms = context.createPackageContext("com.google.android.gms",
......@@ -45,11 +45,15 @@ public class Networking {
.getMethod("insertProvider", Context.class)
.invoke(null, gms);
} catch (Exception e) {
if (Build.VERSION.SDK_INT < 21) {
// Failed to update SSL provider, use NoSSLv3SocketFactory on SDK < 21
if (Build.VERSION.SDK_INT < 21)
// and return false to notify potential issues
HttpsURLConnection.setDefaultSSLSocketFactory(new NoSSLv3SocketFactory());
return false;
}
}
return true;
}
public static boolean checkNetworkStatus(Context context) {
ConnectivityManager manager = (ConnectivityManager)
......
......@@ -11,18 +11,18 @@ import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
class NoSSLv3SocketFactory extends SSLSocketFactory {
public class NoSSLv3SocketFactory extends SSLSocketFactory {
private final static SSLSocketFactory base = HttpsURLConnection.getDefaultSSLSocketFactory();
private final static SSLSocketFactory delegate = HttpsURLConnection.getDefaultSSLSocketFactory();
@Override
public String[] getDefaultCipherSuites() {
return base.getDefaultCipherSuites();
return delegate.getDefaultCipherSuites();
}
@Override
public String[] getSupportedCipherSuites() {
return base.getSupportedCipherSuites();
return delegate.getSupportedCipherSuites();
}
private Socket createSafeSocket(Socket socket) {
......@@ -40,31 +40,31 @@ class NoSSLv3SocketFactory extends SSLSocketFactory {
@Override
public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
return createSafeSocket(base.createSocket(s, host, port, autoClose));
return createSafeSocket(delegate.createSocket(s, host, port, autoClose));
}
@Override
public Socket createSocket() throws IOException {
return createSafeSocket(base.createSocket());
return createSafeSocket(delegate.createSocket());
}
@Override
public Socket createSocket(String host, int port) throws IOException {
return createSafeSocket(base.createSocket(host, port));
return createSafeSocket(delegate.createSocket(host, port));
}
@Override
public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException {
return createSafeSocket(base.createSocket(host, port, localHost, localPort));
return createSafeSocket(delegate.createSocket(host, port, localHost, localPort));
}
@Override
public Socket createSocket(InetAddress host, int port) throws IOException {
return createSafeSocket(base.createSocket(host, port));
return createSafeSocket(delegate.createSocket(host, port));
}
@Override
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
return createSafeSocket(base.createSocket(address, port, localAddress, localPort));
return createSafeSocket(delegate.createSocket(address, port, localAddress, localPort));
}
}
package com.topjohnwu.magisk.utils;
import java.util.Enumeration;
import java.util.NoSuchElementException;
public class CompoundEnumeration<E> implements Enumeration<E> {
private Enumeration<E>[] enums;
private int index = 0;
@SafeVarargs
public CompoundEnumeration(Enumeration<E> ...enums) {
this.enums = enums;
}
private boolean next() {
while (index < enums.length) {
if (enums[index] != null && enums[index].hasMoreElements()) {
return true;
}
index++;
}
return false;
}
public boolean hasMoreElements() {
return next();
}
public E nextElement() {
if (!next()) {
throw new NoSuchElementException();
}
return enums[index].nextElement();
}
}
package com.topjohnwu.magisk.utils;
import android.content.Context;
import java.io.File;
public class DynAPK {
private static File dynDir;
public static File current(Context context) {
if (dynDir == null)
dynDir = new File(context.getFilesDir().getParent(), "dyn");
return new File(dynDir, "current.apk");
}
}
package com.topjohnwu.magisk.utils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.Enumeration;
import dalvik.system.DexClassLoader;
public class DynamicClassLoader extends DexClassLoader {
private ClassLoader base = Object.class.getClassLoader();
public DynamicClassLoader(File apk, ClassLoader parent) {
super(apk.getPath(), apk.getParent(), null, parent);
}
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
// First check if already loaded
Class cls = findLoadedClass(name);
if (cls != null)
return cls;
try {
// Then check boot classpath
return base.loadClass(name);
} catch (ClassNotFoundException ignored) {
try {
// Next try current dex
return findClass(name);
} catch (ClassNotFoundException fromSuper) {
try {
// Finally try parent
return getParent().loadClass(name);
} catch (ClassNotFoundException e) {
throw fromSuper;
}
}
}
}
@Override
public URL getResource(String name) {
URL resource = base.getResource(name);
if (resource != null)
return resource;
resource = findResource(name);
if (resource != null)
return resource;
resource = getParent().getResource(name);
return resource;
}
@Override
public Enumeration<URL> getResources(String name) throws IOException {
return new CompoundEnumeration<>(base.getResources(name),
findResources(name), getParent().getResources(name));
}
}
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@color/ic_launcher_background" />
<item
android:width="250dp"
android:height="250dp"
android:gravity="center"
android:drawable="@drawable/ic_magisk" />
</layer-list>
\ No newline at end of file
<resources>
<!--Not translatable-->
<string name="app_name" translatable="false">Magisk Manager</string>
<string name="re_app_name" translatable="false">Manager</string>
<string name="magisk" translatable="false">Magisk</string>
<string name="magiskhide" translatable="false">Magisk Hide</string>
<string name="empty" translatable="false"/>
<!--Used in both stub and full app-->
<string name="upgrade_msg">Qurmanı sonlandırmaq üçün full Magisk Manager`ə yüksəldin. Yüklənib qurulsun?</string>
<string name="no_internet_msg">Lütfən internetə qoşulun! Full Magisk Manager\'ə yüksəltmə lazımidir.</string>
<string name="no_thanks">Yox, sağolun</string>
<string name="yes">Bəli</string>
<string name="ok">OK</string>
......
......@@ -2,4 +2,6 @@
<string name="no_thanks">Не, благодаря.</string>
<string name="yes">Да</string>
<string name="ok">OK</string>
<string name="upgrade_msg">Надградете до пълната версия на Magisk Manager, за да довършите първоначалната настройка. Изтегляне и инсталиране сега?</string>
<string name="no_internet_msg">Моля да се свържете към работеща интернет мрежа, защото надграждането до пълната версия на Magisk Manager е задължително.</string>
</resources>
......@@ -3,5 +3,7 @@
<string name="no_thanks">No, gràcies</string>
<string name="yes"></string>
<string name="ok">Ok</string>
<string name="upgrade_msg">Fes una actualització total de Magisk Manager per finalitzar l\'instalació. Descarregar i instalar?</string>
<string name="no_internet_msg">Si us plau, connecta\'t a internet! Es necessari fer una actualització total de Magisk Manager.</string>
</resources>
......@@ -2,4 +2,6 @@
<string name="no_thanks">Nein danke</string>
<string name="yes">Ja</string>
<string name="ok">OK</string>
<string name="upgrade_msg">Upgrade zum vollständigen Magisk Manager, um das Setup abzuschließen. Herunterladen und installieren?</string>
<string name="no_internet_msg">Bitte eine Verbindung mit dem Internet herstellen! Upgrade zum vollständigen Magisk Manager ist erforderlich.</string>
</resources>
......@@ -2,4 +2,6 @@
<string name="no_thanks">No gracias</string>
<string name="yes"></string>
<string name="ok">Aceptar</string>
<string name="upgrade_msg">Actualizar a la Ver. Completa de Magisk Manager para finalizar la instalación. Descargar e instalar?</string>
<string name="no_internet_msg">¡Por favor conectarse a Internet! Se requiere actualizar a la Ver. Completa de Magisk Manager </string>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!--Used in both stub and full app-->
<string name="no_thanks">Tänan ei</string>
<string name="yes">Jah</string>
<string name="ok">OK</string>
<string name="upgrade_msg">Täienda seadistuse lõpetamiseks Magisk Manager\'i täisversioonile. Kas laadid alla ja installid?</string>
<string name="no_internet_msg">Palun ühendu Internetti! Nõutud on Magisk Manager\'i täisversioonile täiendamine.</string>
</resources>
\ No newline at end of file
......@@ -4,4 +4,6 @@
<string name="no_thanks">Non merci</string>
<string name="yes">Oui</string>
<string name="ok">OK</string>
<string name="upgrade_msg">Mettre à jour vers la version complête de Magisk Manager pour finir l\'installation. Télécharger et installer?</string>
<string name="no_internet_msg">Veuillez vous connecter à Internet! Une mise à niveau complête vers le Gestionnaire Magisk est requise.</string>
</resources>
......@@ -2,4 +2,6 @@
<string name="no_thanks">Tidak, terima kasih</string>
<string name="yes">Ya</string>
<string name="ok">OK</string>
<string name="upgrade_msg">Tingkatkan ke Magisk Manager versi penuh untuk menyelesaikan penyiapan. Unduh dan pasang?</string>
<string name="no_internet_msg">Harap menyambungkan ke Internet! Peningkatan ke Magisk Manager versi penuh diperlukan.</string>
</resources>
......@@ -2,4 +2,6 @@
<string name="no_thanks">No, grazie</string>
<string name="yes"></string>
<string name="ok">OK</string>
<string name="upgrade_msg">Aggiorna alla versione completa di Magisk Manager per completare l\'installazione. Vuoi procedere al download e all\'installazione?</string>
<string name="no_internet_msg">Controlla la connessione a Internet! È necessaria per l\'aggiornamento di Magisk Manager.</string>
</resources>
......@@ -2,4 +2,6 @@
<string name="no_thanks">아니오, 괜찮습니다</string>
<string name="yes"></string>
<string name="ok">확인</string>
<string name="upgrade_msg">완전한 Magisk Manager로 업데이트하여 설치를 마치십시오. 다운로드하고 설치하시겠습니까?</string>
<string name="no_internet_msg">인터넷에 연결해 주시기 바랍니다! 완전한 Magisk Manager로 업데이트 해야 합니다.</string>
</resources>
......@@ -2,4 +2,6 @@
<string name="no_thanks">Ačiū, nereikia</string>
<string name="yes">Taip</string>
<string name="ok">Gerai</string>
<string name="upgrade_msg">Atsinaujinkite į pilną Magisk Manager versiją, kad baigtumėte pasiruošimą. Atsisiųsti ir instaliuoti?</string>
<string name="no_internet_msg">Prašome prisijungti prie interneto! Atsinaujinimas į pilną Magisk Manager versiją yra privalomas.</string>
</resources>
<resources>
<!--Not translatable-->
<string name="app_name" translatable="false">Magisk Manager</string>
<string name="re_app_name" translatable="false">Manager</string>
<string name="magisk" translatable="false">Magisk</string>
<string name="magiskhide" translatable="false">Magisk Hide</string>
<string name="empty" translatable="false"/>
<!--Used in both stub and full app-->
<string name="no_thanks">Не, благодарам</string>
<string name="yes">Да</string>
<string name="ok">ОК</string>
<string name="upgrade_msg">Надградете до целосната верзија на Magisk Manager за да го завршите поставувањето. Преземете и инсталирајте?</string>
<string name="no_internet_msg">Ве молиме поврзете се на интернет бидејќи е потребна надградба на целосната верзија на Magisk Manager.</string>
</resources>
......@@ -4,4 +4,6 @@
<string name="no_thanks">Nei takk</string>
<string name="yes">Ja</string>
<string name="ok">OK</string>
<string name="upgrade_msg">Oppgrader til den komplette versjonen av Magisk Manager for å fullføre oppsettet. Vil du laste ned og installere?</string>
<string name="no_internet_msg">Vennligst koble deg på internettet! Å oppgradere til den komplette versjonen av Magisk Manager er påkrevd.</string>
</resources>
......@@ -2,4 +2,6 @@
<string name="no_thanks">Nie dziękuję</string>
<string name="yes">Tak</string>
<string name="ok">OK</string>
<string name="upgrade_msg">Przejdź na pełną wersję programu Magisk Manager, aby ukończyć konfigurację. Ściągnąć i zainstalować?</string>
<string name="no_internet_msg">Połącz się z Internetem! Wymagana jest aktualizacja do pełnego programu Magisk Manager.</string>
</resources>
......@@ -2,4 +2,6 @@
<string name="no_thanks">Nu, mulțumesc</string>
<string name="yes">Da</string>
<string name="ok">Ok</string>
<string name="upgrade_msg">Treci la versiunea completă Magisk Manager pentru a finaliza configurarea. Descarci și instalezi?</string>
<string name="no_internet_msg">Te rugăm să te conectezi la internet! Este necesară actualizarea la versiunea completă Magisk Manager.</string>
</resources>
......@@ -2,4 +2,6 @@
<string name="no_thanks">Нет</string>
<string name="yes">Да</string>
<string name="ok">OK</string>
<string name="upgrade_msg">Обновите Magisk Manager для завершения установки. Загрузить и установить?</string>
<string name="no_internet_msg">Пожалуйста, подключитесь к интернету! Требуется обновление Magisk Manager.</string>
</resources>
......@@ -3,4 +3,6 @@
<string name="no_thanks">Nie, ďakujem</string>
<string name="yes">Áno</string>
<string name="ok">OK</string>
<string name="upgrade_msg">Pre dokončenie inštalácie sa vyžaduje upgrade Magisk Managera. Stiahnuť a nainštalovať?</string>
<string name="no_internet_msg">Pripojte sa na internet! Upgrade Magisk Managera je potrebný.</string>
</resources>
......@@ -2,4 +2,6 @@
<string name="no_thanks">Hayır, teşekkürler</string>
<string name="yes">Evet</string>
<string name="ok">Tamam</string>
<string name="upgrade_msg">Kurulumu tamamlamak için tam Magisk Manager\'a yükseltin. İndirip yüklensin mi?</string>
<string name="no_internet_msg">Lütfen internete bağlanın! Tam Magisk Manager\'a yükseltmek gerekiyor.</string>
</resources>
......@@ -2,4 +2,6 @@
<string name="no_thanks">Ні, дякую</string>
<string name="yes">Так</string>
<string name="ok">OK</string>
<string name="upgrade_msg">Оновіть Magisk Manager для завершення встановлення. Завантажити і встановити?</string>
<string name="no_internet_msg">Будь ласка, підключіться до Інтернету! Потрібно оновити Magisk Manager.</string>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<bool name="enable_system_alarm_service_default">false</bool>
<bool name="enable_system_job_service_default">true</bool>
</resources>
......@@ -2,4 +2,6 @@
<string name="no_thanks">不,谢谢</string>
<string name="yes"></string>
<string name="ok"></string>
<string name="upgrade_msg">需要升级到完整的 Magisk Manager 来完成安装。 现在下载?</string>
<string name="no_internet_msg">请连接到互联网! 这是升级到完整 Magisk Manager 所必需的。</string>
</resources>
......@@ -2,4 +2,6 @@
<string name="no_thanks">不,謝謝</string>
<string name="yes"></string>
<string name="ok"></string>
<string name="upgrade_msg">需要升級到完整版 Magisk Manager。是否下載並安裝?</string>
<string name="no_internet_msg">請連上網路!升級到完整版 Magisk Manager 是必須的。</string>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#00AF9C</color>
</resources>
\ No newline at end of file
......@@ -6,8 +6,9 @@
<string name="magiskhide" translatable="false">Magisk Hide</string>
<string name="empty" translatable="false"/>
<!--Used in both stub and full app-->
<string name="no_thanks">No thanks</string>
<string name="yes">Yes</string>
<string name="ok">OK</string>
<string name="upgrade_msg">Upgrade to full Magisk Manager to finish the setup. Download and install?</string>
<string name="no_internet_msg">Please connect to the Internet! Upgrading to full Magisk Manager is required.</string>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools">
<style name="SplashThemeBase" parent="android:Theme.DeviceDefault.NoActionBar">
<item name="android:windowBackground">@drawable/ic_splash_activity</item>
</style>
<style name="SplashThemeBase.V19" parent="SplashThemeBase">
<item name="android:windowTranslucentStatus" tools:targetApi="19">true</item>
<item name="android:windowTranslucentNavigation" tools:targetApi="19">true</item>
</style>
<style name="SplashTheme" parent="android:Theme.Translucent.NoTitleBar" />
</resources>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<bool name="enable_system_alarm_service_default">true</bool>
<bool name="enable_system_job_service_default">false</bool>
<bool name="workmanager_test_configuration">false</bool>
</resources>
......@@ -9,7 +9,10 @@ android {
buildTypes {
release {
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
minifyEnabled true
shrinkResources false /* Do NOT allow resource shrinking */
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'),
'proguard-rules.pro'
}
}
}
......
<?xml version="1.0" encoding="utf-8"?>
<!--
Try to recreate the main app's AndroidManifest.xml
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.topjohnwu.magisk">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<application
android:name="a.e"
android:appComponentFactory="a.a"
android:allowBackup="true"
android:usesCleartextTraffic="true"
tools:ignore="UnusedAttribute,GoogleAppIndexingWarning" >
<application tools:ignore="GoogleAppIndexingWarning"
android:allowBackup="true">
<!-- Download Activity -->
<activity
android:name=".MainActivity"
android:theme="@android:style/Theme.Translucent.NoTitleBar">
android:name="a.c"
android:configChanges="orientation|screenSize"
android:theme="@style/SplashTheme">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<receiver android:name=".BootLauncher">
<!-- Magisk Manager Components -->
<activity
android:name="a.b"
android:configChanges="orientation|screenSize"
android:exported="true" />
<activity
android:name="a.f"
android:configChanges="keyboardHidden|orientation|screenSize"
android:screenOrientation="nosensor" />
<activity
android:name="a.m"
android:directBootAware="true"
android:excludeFromRecents="true"
android:exported="false" />
<receiver
android:name="a.h"
android:directBootAware="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.LOCALE_CHANGED" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.PACKAGE_REPLACED" />
<action android:name="android.intent.action.PACKAGE_FULLY_REMOVED" />
<data android:scheme="package" />
</intent-filter>
</receiver>
<service
android:name="a.j"
android:exported="false" />
<meta-data
android:name="com.google.android.gms.version"
android:value="12451000" />
<!-- WorkManager -->
<provider
android:name="androidx.work.impl.WorkManagerInitializer"
android:authorities="${applicationId}.workmanager-init"
android:directBootAware="false"
android:exported="false"
android:multiprocess="true"
tools:targetApi="n" />
<service
android:name="androidx.work.impl.background.systemalarm.SystemAlarmService"
android:directBootAware="false"
android:enabled="@bool/enable_system_alarm_service_default"
android:exported="false"
tools:targetApi="n" />
<service
android:name="androidx.work.impl.background.systemjob.SystemJobService"
android:directBootAware="false"
android:enabled="@bool/enable_system_job_service_default"
android:exported="true"
android:permission="android.permission.BIND_JOB_SERVICE"
tools:targetApi="n" />
<receiver
android:name="androidx.work.impl.utils.ForceStopRunnable$BroadcastReceiver"
android:directBootAware="false"
android:enabled="true"
android:exported="false"
tools:targetApi="n" />
<receiver
android:name="androidx.work.impl.background.systemalarm.ConstraintProxy$BatteryChargingProxy"
android:directBootAware="false"
android:enabled="false"
android:exported="false"
tools:targetApi="n" >
<intent-filter>
<action android:name="android.intent.action.ACTION_POWER_CONNECTED" />
<action android:name="android.intent.action.ACTION_POWER_DISCONNECTED" />
</intent-filter>
</receiver>
<receiver
android:name="androidx.work.impl.background.systemalarm.ConstraintProxy$BatteryNotLowProxy"
android:directBootAware="false"
android:enabled="false"
android:exported="false"
tools:targetApi="n" >
<intent-filter>
<action android:name="android.intent.action.BATTERY_OKAY" />
<action android:name="android.intent.action.BATTERY_LOW" />
</intent-filter>
</receiver>
<receiver
android:name="androidx.work.impl.background.systemalarm.ConstraintProxy$StorageNotLowProxy"
android:directBootAware="false"
android:enabled="false"
android:exported="false"
tools:targetApi="n" >
<intent-filter>
<action android:name="android.intent.action.DEVICE_STORAGE_LOW" />
<action android:name="android.intent.action.DEVICE_STORAGE_OK" />
</intent-filter>
</receiver>
<receiver
android:name="androidx.work.impl.background.systemalarm.ConstraintProxy$NetworkStateProxy"
android:directBootAware="false"
android:enabled="false"
android:exported="false"
tools:targetApi="n" >
<intent-filter>
<action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
</intent-filter>
</receiver>
<receiver
android:name="androidx.work.impl.background.systemalarm.RescheduleReceiver"
android:directBootAware="false"
android:enabled="false"
android:exported="false"
tools:targetApi="n" >
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.TIME_SET" />
<action android:name="android.intent.action.TIMEZONE_CHANGED" />
</intent-filter>
</receiver>
<receiver
android:name="androidx.work.impl.background.systemalarm.ConstraintProxyUpdateReceiver"
android:directBootAware="false"
android:enabled="@bool/enable_system_alarm_service_default"
android:exported="false"
tools:targetApi="n" >
<intent-filter>
<action android:name="androidx.work.impl.background.systemalarm.UpdateProxies" />
</intent-filter>
</receiver>
<!-- Room -->
<service
android:name="androidx.room.MultiInstanceInvalidationService"
android:exported="false" />
</application>
</manifest>
package a;
import com.topjohnwu.magisk.DelegateComponentFactory;
public class a extends DelegateComponentFactory {
}
package a;
import com.topjohnwu.magisk.DownloadActivity;
public class c extends DownloadActivity {
}
package a;
import com.topjohnwu.magisk.DelegateApplication;
public class e extends DelegateApplication {
}
package androidx.work.impl;
import com.topjohnwu.magisk.dummy.DummyProvider;
public class WorkManagerInitializer extends DummyProvider {
/* This class have to exist, or else pre 9.0 devices
* will experience ClassNotFoundException crashes when
* launching the stub, as ContentProviders are constructed
* when an app starts up. Pre 9.0 devices do not have
* our custom DelegateComponentFactory to help creating
* dummies. */
}
package com.topjohnwu.magisk;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.text.TextUtils;
public class BootLauncher extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (TextUtils.equals(intent.getAction(), Intent.ACTION_BOOT_COMPLETED)) {
Intent i = new Intent(context, MainActivity.class).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(i);
}
}
}
package com.topjohnwu.magisk;
import android.app.AppComponentFactory;
import android.app.Application;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.res.Configuration;
import android.os.Build;
import android.util.Log;
import com.topjohnwu.magisk.utils.DynAPK;
import com.topjohnwu.magisk.utils.DynamicClassLoader;
import java.io.File;
import java.lang.reflect.Method;
import static com.topjohnwu.magisk.DownloadActivity.TAG;
public class DelegateApplication extends Application {
static File MANAGER_APK;
private Object factory;
private Application delegate;
public DelegateApplication() {}
public DelegateApplication(Object o) {
factory = o;
}
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
if (Build.VERSION.SDK_INT >= 28) {
// If 9.0+, try to dynamically load the APK
DelegateComponentFactory factory = (DelegateComponentFactory) this.factory;
MANAGER_APK = DynAPK.current(this);
MANAGER_APK.getParentFile().mkdir();
if (MANAGER_APK.exists()) {
ClassLoader cl = new DynamicClassLoader(MANAGER_APK, factory.loader);
try {
// Create the delegate AppComponentFactory
Object df = cl.loadClass("a.a").newInstance();
// Create the delegate Application
delegate = (Application) cl.loadClass("a.e").newInstance();
// Call attachBaseContext without ContextImpl to show it is being wrapped
Method m = ContextWrapper.class.getDeclaredMethod("attachBaseContext", Context.class);
m.setAccessible(true);
m.invoke(delegate, this);
// If everything went well, set our loader and delegate
factory.delegate = (AppComponentFactory) df;
factory.loader = cl;
} catch (Exception e) {
Log.e(TAG, "dyn load", e);
MANAGER_APK.delete();
}
}
} else {
MANAGER_APK = new File(base.getCacheDir(), "manager.apk");
}
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
delegate.onConfigurationChanged(newConfig);
}
}
package com.topjohnwu.magisk;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.AppComponentFactory;
import android.app.Application;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.ContentProvider;
import android.content.Intent;
import com.topjohnwu.magisk.dummy.DummyActivity;
import com.topjohnwu.magisk.dummy.DummyProvider;
import com.topjohnwu.magisk.dummy.DummyReceiver;
import com.topjohnwu.magisk.dummy.DummyService;
@SuppressLint("NewApi")
public class DelegateComponentFactory extends AppComponentFactory {
ClassLoader loader;
AppComponentFactory delegate;
@Override
public Application instantiateApplication(ClassLoader cl, String className) {
loader = cl;
return new DelegateApplication(this);
}
@Override
public Activity instantiateActivity(ClassLoader cl, String className, Intent intent)
throws ClassNotFoundException, IllegalAccessException, InstantiationException {
if (delegate != null)
return delegate.instantiateActivity(loader, className, intent);
return create(className, DummyActivity.class);
}
@Override
public BroadcastReceiver instantiateReceiver(ClassLoader cl, String className, Intent intent)
throws ClassNotFoundException, IllegalAccessException, InstantiationException {
if (delegate != null)
return delegate.instantiateReceiver(loader, className, intent);
return create(className, DummyReceiver.class);
}
@Override
public Service instantiateService(ClassLoader cl, String className, Intent intent)
throws ClassNotFoundException, IllegalAccessException, InstantiationException {
if (delegate != null)
return delegate.instantiateService(loader, className, intent);
return create(className, DummyService.class);
}
@Override
public ContentProvider instantiateProvider(ClassLoader cl, String className)
throws ClassNotFoundException, IllegalAccessException, InstantiationException {
if (delegate != null)
return delegate.instantiateProvider(loader, className);
return create(className, DummyProvider.class);
}
/**
* Create the class or dummy implementation if creation failed
*/
private <T> T create(String name, Class<? extends T> dummy)
throws InstantiationException, IllegalAccessException {
try {
return (T) loader.loadClass(name).newInstance();
} catch (IllegalAccessException | InstantiationException | ClassNotFoundException ignored) {
return dummy.newInstance();
}
}
}
......@@ -3,30 +3,55 @@ package com.topjohnwu.magisk;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Application;
import android.app.ProgressDialog;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import com.topjohnwu.magisk.utils.APKInstall;
import com.topjohnwu.magisk.net.ErrorHandler;
import com.topjohnwu.magisk.net.Networking;
import com.topjohnwu.magisk.net.ResponseListener;
import com.topjohnwu.magisk.utils.APKInstall;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.File;
import static com.topjohnwu.magisk.DelegateApplication.MANAGER_APK;
public class MainActivity extends Activity {
public class DownloadActivity extends Activity {
static final String TAG = "MMStub";
private static final boolean IS_CANARY = BuildConfig.VERSION_NAME.contains("-");
private static final String URL =
"https://raw.githubusercontent.com/topjohnwu/magisk_files/master/" +
(BuildConfig.VERSION_NAME.contains("-") ? "canary_builds/release.json" : "stable.json");
"https://raw.githubusercontent.com/topjohnwu/magisk_files/" +
(IS_CANARY ? "canary/release.json" : "master/stable.json");
private String apkLink;
private ErrorHandler err = (conn, e) -> {
Log.e(TAG, "network error", e);
finish();
};
private void showDialog() {
ProgressDialog.show(this,
"Downloading...",
"Downloading Magisk Manager", true);
}
private void dlAPK() {
showDialog();
if (Build.VERSION.SDK_INT >= 28) {
// Download and relaunch the app
Networking.get(apkLink)
.setErrorHandler(err)
.getAsFile(MANAGER_APK, f -> ProcessPhoenix.triggerRebirth(this));
} else {
// Download and upgrade the app
Application app = getApplication();
Networking.get(apkLink)
.getAsFile(new File(getFilesDir(), "manager.apk"), apk -> APKInstall.install(app, apk));
finish();
.setErrorHandler(err)
.getAsFile(MANAGER_APK, apk -> APKInstall.install(app, apk));
}
}
@Override
......@@ -35,10 +60,10 @@ public class MainActivity extends Activity {
Networking.init(this);
if (Networking.checkNetworkStatus(this)) {
Networking.get(URL)
.setErrorHandler(((conn, e) -> finish()))
.setErrorHandler(err)
.getAsJSONObject(new JSONLoader());
} else {
new AlertDialog.Builder(this, AlertDialog.THEME_DEVICE_DEFAULT_LIGHT)
new AlertDialog.Builder(this)
.setCancelable(false)
.setTitle(R.string.app_name)
.setMessage(R.string.no_internet_msg)
......@@ -54,7 +79,7 @@ public class MainActivity extends Activity {
try {
JSONObject manager = json.getJSONObject("app");
apkLink = manager.getString("link");
new AlertDialog.Builder(MainActivity.this, AlertDialog.THEME_DEVICE_DEFAULT_LIGHT)
new AlertDialog.Builder(DownloadActivity.this)
.setCancelable(false)
.setTitle(R.string.app_name)
.setMessage(R.string.upgrade_msg)
......
package com.topjohnwu.magisk.dummy;
import android.app.Activity;
import android.os.Bundle;
public class DummyActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
finish();
}
}
package com.topjohnwu.magisk.dummy;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
public class DummyProvider extends ContentProvider {
@Override
public boolean onCreate() {
return false;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
return null;
}
@Override
public String getType(Uri uri) {
return null;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
return null;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
return 0;
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
return 0;
}
}
package com.topjohnwu.magisk.dummy;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
public class DummyReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {}
}
package com.topjohnwu.magisk.dummy;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
public class DummyService extends Service {
@Override
public IBinder onBind(Intent intent) {
stopSelf();
return null;
}
}
<resources>
<string name="upgrade_msg">Qurmanı sonlandırmaq üçün full Magisk Manager`ə yüksəldin. Yüklənib qurulsun?</string>
<string name="no_internet_msg">Lütfən internetə qoşulun! Full Magisk Manager\'ə yüksəltmə lazımidir.</string>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="upgrade_msg">Надградете до пълната версия на Magisk Manager, за да довършите първоначалната настройка. Изтегляне и инсталиране сега?</string>
<string name="no_internet_msg">Моля да се свържете към работеща интернет мрежа, защото надграждането до пълната версия на Magisk Manager е задължително.</string>
</resources>
<resources>
<string name="upgrade_msg">Fes una actualització total de Magisk Manager per finalitzar l\'instalació. Descarregar i instalar?</string>
<string name="no_internet_msg">Si us plau, connecta\'t a internet! Es necessari fer una actualització total de Magisk Manager.</string>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="upgrade_msg">Upgrade zum vollständigen Magisk Manager, um das Setup abzuschließen. Herunterladen und installieren?</string>
<string name="no_internet_msg">Bitte eine Verbindung mit dem Internet herstellen! Upgrade zum vollständigen Magisk Manager ist erforderlich.</string>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="upgrade_msg">Actualizar a la Ver. Completa de Magisk Manager para finalizar la instalación. Descargar e instalar?</string>
<string name="no_internet_msg">¡Por favor conectarse a Internet! Se requiere actualizar a la Ver. Completa de Magisk Manager </string>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="upgrade_msg">Täienda seadistuse lõpetamiseks Magisk Manager täisversioonile. Kas laadin alla ja installin?</string>
<string name="no_internet_msg">Palun ühendu Internetti! Nõutud on Magisk Manageri täiendamine.</string>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="upgrade_msg">Mettre à jour vers la version complête de Magisk Manager pour finir l\'installation. Télécharger et installer?</string>
<string name="no_internet_msg">Veuillez vous connecter à Internet! Une mise à niveau complête vers le Gestionnaire Magisk est requise.</string>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="upgrade_msg">Tingkatkan ke Magisk Manager versi penuh untuk menyelesaikan penyiapan. Unduh dan pasang?</string>
<string name="no_internet_msg">Harap menyambungkan ke Internet! Peningkatan ke Magisk Manager versi penuh diperlukan.</string>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="upgrade_msg">Aggiorna alla versione completa di Magisk Manager per completare l\'installazione. Vuoi procedere al download e all\'installazione?</string>
<string name="no_internet_msg">Controlla la connessione a Internet! È necessaria per l\'aggiornamento di Magisk Manager.</string>
</resources>
<resources>
<string name="upgrade_msg">완전한 Magisk Manager로 업데이트하여 설치를 마치십시오. 다운로드하고 설치하시겠습니까?</string>
<string name="no_internet_msg">인터넷에 연결해 주시기 바랍니다! 완전한 Magisk Manager로 업데이트 해야 합니다.</string>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="upgrade_msg">Atsinaujinkite į pilną Magisk Manager versiją, kad baigtumėte pasiruošimą. Atsisiųsti ir instaliuoti?</string>
<string name="no_internet_msg">Prašome prisijungti prie interneto! Atsinaujinimas į pilną Magisk Manager versiją yra privalomas.</string>
</resources>
\ No newline at end of file
<resources>
<string name="upgrade_msg">Надградете до целосната верзија на Magisk Manager за да го завршите поставувањето. Преземете и инсталирајте?</string>
<string name="no_internet_msg">Ве молиме поврзете се на интернет бидејќи е потребна надградба на целосната верзија на Magisk Manager.</string>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="upgrade_msg">Oppgrader til den komplette versjonen av Magisk Manager for å fullføre oppsettet. Vil du laste ned og installere?</string>
<string name="no_internet_msg">Vennligst koble deg på internettet! Å oppgradere til den komplette versjonen av Magisk Manager er påkrevd.</string>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="upgrade_msg">Przejdź na pełną wersję programu Magisk Manager, aby ukończyć konfigurację. Ściągnąć i zainstalować?</string>
<string name="no_internet_msg">Połącz się z Internetem! Wymagana jest aktualizacja do pełnego programu Magisk Manager.</string>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="upgrade_msg">Treci la versiunea completă Magisk Manager pentru a finaliza configurarea. Descarci și instalezi?</string>
<string name="no_internet_msg">Te rugăm să te conectezi la internet! Este necesară actualizarea la versiunea completă Magisk Manager.</string>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="upgrade_msg">Обновите Magisk Manager для завершения установки. Загрузить и установить?</string>
<string name="no_internet_msg">Пожалуйста, подключитесь к интернету! Требуется обновление Magisk Manager.</string>
</resources>
<resources>
<string name="upgrade_msg">Pre dokončenie inštalácie sa vyžaduje upgrade Magisk Managera. Stiahnuť a nainštalovať?</string>
<string name="no_internet_msg">Pripojte sa na internet! Upgrade Magisk Managera je potrebný.</string>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="upgrade_msg">Kurulumu tamamlamak için tam Magisk Manager\'a yükseltin. İndirip yüklensin mi?</string>
<string name="no_internet_msg">Lütfen internete bağlanın! Tam Magisk Manager\'a yükseltmek gerekiyor.</string>
</resources>
<resources>
<string name="upgrade_msg">Оновіть Magisk Manager для завершення встановлення. Завантажити і встановити?</string>
<string name="no_internet_msg">Будь ласка, підключіться до Інтернету! Потрібно оновити Magisk Manager.</string>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="SplashTheme" parent="SplashThemeBase.V19" />
</resources>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="upgrade_msg">需要升级到完整的 Magisk Manager 来完成安装。 现在下载?</string>
<string name="no_internet_msg">请连接到互联网! 这是升级到完整 Magisk Manager 所必需的。</string>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="upgrade_msg">需要升級到完整版 Magisk Manager。是否下載並安裝?</string>
<string name="no_internet_msg">請連上網路!升級到完整版 Magisk Manager 是必須的。</string>
</resources>
<resources>
<string name="upgrade_msg">Upgrade to full Magisk Manager to finish the setup. Download and install?</string>
<string name="no_internet_msg">Please connect to the Internet! Upgrading to full Magisk Manager is required.</string>
</resources>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment