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 ...@@ -33,7 +33,6 @@ Furthermore, Magisk provides a **Systemless Interface** to alter the system (or
Default string resources for Magisk Manager are scattered throughout Default string resources for Magisk Manager are scattered throughout
- `app/src/main/res/values/strings.xml` - `app/src/main/res/values/strings.xml`
- `stub/src/main/res/values/strings.xml`
- `shared/src/main/res/values/strings.xml` - `shared/src/main/res/values/strings.xml`
Translate each and place them in the respective locations (`<module>/src/main/res/values-<lang>/strings.xml`). Translate each and place them in the respective locations (`<module>/src/main/res/values-<lang>/strings.xml`).
......
...@@ -60,14 +60,26 @@ dependencies { ...@@ -60,14 +60,26 @@ dependencies {
implementation 'com.github.topjohnwu:jtar:1.0.0' implementation 'com.github.topjohnwu:jtar:1.0.0'
implementation 'com.jakewharton.timber:timber:4.7.1' implementation 'com.jakewharton.timber:timber:4.7.1'
implementation 'com.github.skoumalcz:teanity:0.3.3'
implementation 'com.ncapdevi:frag-nav:3.2.0' implementation 'com.ncapdevi:frag-nav:3.2.0'
implementation 'com.github.pwittchen:reactivenetwork-rx2:3.0.6' implementation 'com.github.pwittchen:reactivenetwork-rx2:3.0.6'
def vMarkwon = '3.1.0' implementation 'io.reactivex.rxjava2:rxjava:2.2.13'
implementation "ru.noties.markwon:core:${vMarkwon}" implementation 'io.reactivex.rxjava2:rxkotlin:2.4.0'
implementation "ru.noties.markwon:html:${vMarkwon}" implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
implementation "ru.noties.markwon:image-svg:${vMarkwon}"
implementation "org.jetbrains.kotlin:kotlin-stdlib:${vKotlin}"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${vKotlin}"
def vBAdapt = '3.1.1'
def bindingAdapter = 'me.tatarka.bindingcollectionadapter2:bindingcollectionadapter'
implementation "${bindingAdapter}:${vBAdapt}"
implementation "${bindingAdapter}-recyclerview:${vBAdapt}"
def vMarkwon = '4.1.1'
implementation "io.noties.markwon:core:${vMarkwon}"
implementation "io.noties.markwon:html:${vMarkwon}"
implementation "io.noties.markwon:image:${vMarkwon}"
implementation 'com.caverock:androidsvg:1.4'
def vLibsu = '2.5.1' def vLibsu = '2.5.1'
implementation "com.github.topjohnwu.libsu:core:${vLibsu}" implementation "com.github.topjohnwu.libsu:core:${vLibsu}"
...@@ -78,13 +90,13 @@ dependencies { ...@@ -78,13 +90,13 @@ dependencies {
implementation "org.koin:koin-android:${vKoin}" implementation "org.koin:koin-android:${vKoin}"
implementation "org.koin:koin-androidx-viewmodel:${vKoin}" implementation "org.koin:koin-androidx-viewmodel:${vKoin}"
def vRetrofit = '2.6.1' def vRetrofit = '2.6.2'
implementation "com.squareup.retrofit2:retrofit:${vRetrofit}" implementation "com.squareup.retrofit2:retrofit:${vRetrofit}"
implementation "com.squareup.retrofit2:converter-moshi:${vRetrofit}" implementation "com.squareup.retrofit2:converter-moshi:${vRetrofit}"
implementation "com.squareup.retrofit2:converter-scalars:${vRetrofit}" implementation "com.squareup.retrofit2:converter-scalars:${vRetrofit}"
implementation "com.squareup.retrofit2:adapter-rxjava2:${vRetrofit}" implementation "com.squareup.retrofit2:adapter-rxjava2:${vRetrofit}"
def vOkHttp = '3.12.2' def vOkHttp = '3.12.6'
implementation "com.squareup.okhttp3:okhttp:${vOkHttp}" implementation "com.squareup.okhttp3:okhttp:${vOkHttp}"
implementation "com.squareup.okhttp3:logging-interceptor:${vOkHttp}" implementation "com.squareup.okhttp3:logging-interceptor:${vOkHttp}"
...@@ -100,19 +112,21 @@ dependencies { ...@@ -100,19 +112,21 @@ dependencies {
replacedBy('com.github.topjohnwu:room-runtime') replacedBy('com.github.topjohnwu:room-runtime')
} }
} }
def vRoom = "2.1.0" def vRoom = "2.2.0"
implementation "com.github.topjohnwu:room-runtime:${vRoom}" implementation "com.github.topjohnwu:room-runtime:${vRoom}"
kapt "androidx.room:room-compiler:${vRoom}" kapt "androidx.room:room-compiler:${vRoom}"
implementation "org.jetbrains.kotlin:kotlin-stdlib:${kotlinVer}" def vNav = "2.1.0"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${kotlinVer}" implementation "androidx.navigation:navigation-fragment-ktx:$vNav"
implementation "androidx.navigation:navigation-ui-ktx:$vNav"
implementation 'androidx.constraintlayout:constraintlayout:1.1.3' implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.browser:browser:1.0.0' implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0-alpha03'
implementation 'androidx.preference:preference:1.1.0' implementation 'androidx.preference:preference:1.1.0'
implementation 'androidx.recyclerview:recyclerview:1.1.0-beta04' implementation 'androidx.recyclerview:recyclerview:1.0.0'
implementation 'androidx.work:work-runtime:2.2.0' implementation 'androidx.work:work-runtime:2.2.0'
implementation 'androidx.transition:transition:1.2.0-rc01' implementation 'androidx.transition:transition:1.2.0'
implementation 'androidx.multidex:multidex:2.0.1' implementation 'androidx.multidex:multidex:2.0.1'
implementation 'androidx.core:core-ktx:1.1.0'
implementation 'com.google.android.material:material:1.1.0-beta01' implementation 'com.google.android.material:material:1.1.0-beta01'
} }
...@@ -29,7 +29,7 @@ ...@@ -29,7 +29,7 @@
} }
# DelegateWorker # DelegateWorker
-keep,allowobfuscation class * extends com.topjohnwu.magisk.model.worker.DelegateWorker -keep,allowobfuscation class * extends com.topjohnwu.magisk.base.DelegateWorker
# BootSigner # BootSigner
-keepclassmembers class com.topjohnwu.signing.BootSigner { *; } -keepclassmembers class com.topjohnwu.signing.BootSigner { *; }
......
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!--
** Special Requirements **
This AndroidManifest.xml will be copied into the stub
APK to allow APK delegation. This is why these special
requirements exist.
* Class names *
Class names a.a, a.c, a.e should not be changed as they are used
externally. All other class names can be changed.
* Resource IDs *
All resource IDs referred in AndroidManifest.xml is required to be
included into the "shared" module to make the ID match with stub.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
package="com.topjohnwu.magisk"> package="com.topjohnwu.magisk">
...@@ -11,25 +29,17 @@ ...@@ -11,25 +29,17 @@
<application <application
android:name="a.e" android:name="a.e"
android:appComponentFactory="a.a"
android:allowBackup="true" android:allowBackup="true"
android:theme="@style/MagiskTheme"
android:usesCleartextTraffic="true" android:usesCleartextTraffic="true"
tools:ignore="UnusedAttribute,GoogleAppIndexingWarning"> tools:ignore="UnusedAttribute,GoogleAppIndexingWarning"
tools:replace="android:appComponentFactory">
<!-- Activities --> <!-- Splash -->
<activity
android:name="a.b"
android:configChanges="orientation|screenSize"
android:exported="true" />
<activity
android:name="a.i"
android:exported="true"
android:theme="@style/Foundation.Default" />
<activity <activity
android:name="a.c" android:name="a.c"
android:configChanges="orientation|screenSize" android:configChanges="orientation|screenSize"
android:exported="true"
android:theme="@style/SplashTheme"> android:theme="@style/SplashTheme">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
...@@ -37,6 +47,18 @@ ...@@ -37,6 +47,18 @@
</intent-filter> </intent-filter>
</activity> </activity>
<!-- Main -->
<activity
android:name="a.b"
android:configChanges="orientation|screenSize"
android:exported="true" />
<activity
android:name="a.i"
android:exported="true"
android:theme="@style/Foundation.Default" />
<activity-alias <activity-alias
android:name="a.s" android:name="a.s"
android:targetActivity="a.c"> android:targetActivity="a.c">
...@@ -46,11 +68,12 @@ ...@@ -46,11 +68,12 @@
</intent-filter> </intent-filter>
</activity-alias> </activity-alias>
<!-- Flashing -->
<activity <activity
android:name="a.f" android:name="a.f"
android:configChanges="keyboardHidden|orientation|screenSize" android:configChanges="keyboardHidden|orientation|screenSize"
android:screenOrientation="nosensor" android:screenOrientation="nosensor" />
android:theme="@style/MagiskTheme.Flashing" />
<!-- Superuser --> <!-- Superuser -->
...@@ -58,8 +81,7 @@ ...@@ -58,8 +81,7 @@
android:name="a.m" android:name="a.m"
android:directBootAware="true" android:directBootAware="true"
android:excludeFromRecents="true" android:excludeFromRecents="true"
android:exported="false" android:exported="false" />
android:theme="@style/MagiskTheme.SU" />
<!-- Receiver --> <!-- Receiver -->
...@@ -78,9 +100,10 @@ ...@@ -78,9 +100,10 @@
</intent-filter> </intent-filter>
</receiver> </receiver>
<!-- Service --> <!-- DownloadService -->
<service android:name="a.j" <service
android:name="a.j"
android:exported="false" /> android:exported="false" />
<!-- Hardcode GMS version --> <!-- Hardcode GMS version -->
......
package a; package a;
import androidx.annotation.Keep;
import androidx.core.app.AppComponentFactory;
import com.topjohnwu.magisk.utils.PatchAPK; import com.topjohnwu.magisk.utils.PatchAPK;
import com.topjohnwu.signing.BootSigner; import com.topjohnwu.signing.BootSigner;
import androidx.annotation.Keep;
@Keep @Keep
public class a extends BootSigner { public class a extends AppComponentFactory {
public static boolean patchAPK(String in, String out, String pkg) { public static boolean patchAPK(String in, String out, String pkg) {
return PatchAPK.patch(in, out, pkg); return PatchAPK.patch(in, out, pkg);
} }
public static void main(String[] args) throws Exception {
BootSigner.main(args);
}
} }
...@@ -6,7 +6,8 @@ import androidx.annotation.NonNull; ...@@ -6,7 +6,8 @@ import androidx.annotation.NonNull;
import androidx.work.Worker; import androidx.work.Worker;
import androidx.work.WorkerParameters; import androidx.work.WorkerParameters;
import com.topjohnwu.magisk.model.worker.DelegateWorker; import com.topjohnwu.magisk.base.DelegateWorker;
import com.topjohnwu.magisk.utils.ResourceMgrKt;
import java.lang.reflect.ParameterizedType; import java.lang.reflect.ParameterizedType;
...@@ -18,7 +19,7 @@ public abstract class w<T extends DelegateWorker> extends Worker { ...@@ -18,7 +19,7 @@ public abstract class w<T extends DelegateWorker> extends Worker {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
w(@NonNull Context context, @NonNull WorkerParameters workerParams) { w(@NonNull Context context, @NonNull WorkerParameters workerParams) {
super(context, workerParams); super(ResourceMgrKt.wrap(context, false), workerParams);
try { try {
base = ((Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()) base = ((Class<T>) ((ParameterizedType) getClass().getGenericSuperclass())
.getActualTypeArguments()[0]).newInstance(); .getActualTypeArguments()[0]).newInstance();
......
...@@ -13,9 +13,11 @@ import com.topjohnwu.magisk.data.database.RepoDatabase_Impl ...@@ -13,9 +13,11 @@ import com.topjohnwu.magisk.data.database.RepoDatabase_Impl
import com.topjohnwu.magisk.di.ActivityTracker import com.topjohnwu.magisk.di.ActivityTracker
import com.topjohnwu.magisk.di.koinModules import com.topjohnwu.magisk.di.koinModules
import com.topjohnwu.magisk.extensions.get import com.topjohnwu.magisk.extensions.get
import com.topjohnwu.magisk.net.Networking import com.topjohnwu.magisk.extensions.unwrap
import com.topjohnwu.magisk.utils.LocaleManager import com.topjohnwu.magisk.utils.ResourceMgr
import com.topjohnwu.magisk.utils.RootUtils import com.topjohnwu.magisk.utils.RootInit
import com.topjohnwu.magisk.utils.isRunningAsStub
import com.topjohnwu.magisk.utils.wrap
import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.Shell
import org.koin.android.ext.koin.androidContext import org.koin.android.ext.koin.androidContext
import org.koin.core.context.startKoin import org.koin.core.context.startKoin
...@@ -27,7 +29,7 @@ open class App : Application() { ...@@ -27,7 +29,7 @@ open class App : Application() {
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true) AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)
Shell.Config.setFlags(Shell.FLAG_MOUNT_MASTER or Shell.FLAG_USE_MAGISK_BUSYBOX) Shell.Config.setFlags(Shell.FLAG_MOUNT_MASTER or Shell.FLAG_USE_MAGISK_BUSYBOX)
Shell.Config.verboseLogging(BuildConfig.DEBUG) Shell.Config.verboseLogging(BuildConfig.DEBUG)
Shell.Config.addInitializers(RootUtils::class.java) Shell.Config.addInitializers(RootInit::class.java)
Shell.Config.setTimeout(2) Shell.Config.setTimeout(2)
Room.setFactory { Room.setFactory {
when (it) { when (it) {
...@@ -39,24 +41,42 @@ open class App : Application() { ...@@ -39,24 +41,42 @@ open class App : Application() {
} }
override fun attachBaseContext(base: Context) { override fun attachBaseContext(base: Context) {
super.attachBaseContext(base) // Basic setup
if (BuildConfig.DEBUG) if (BuildConfig.DEBUG)
MultiDex.install(base) MultiDex.install(base)
Timber.plant(Timber.DebugTree()) Timber.plant(Timber.DebugTree())
// Some context magic
val app: Application
val impl: Context
if (base is Application) {
isRunningAsStub = true
app = base
impl = base.baseContext
} else {
app = this
impl = base
}
ResourceMgr.init(impl)
super.attachBaseContext(impl.wrap())
// Normal startup
startKoin { startKoin {
androidContext(this@App) androidContext(baseContext)
modules(koinModules) modules(koinModules)
} }
ResourceMgr.reload()
app.registerActivityLifecycleCallbacks(get<ActivityTracker>())
}
registerActivityLifecycleCallbacks(get<ActivityTracker>()) // This is required as some platforms expect ContextImpl
override fun getBaseContext(): Context {
Networking.init(base) return super.getBaseContext().unwrap()
LocaleManager.setLocale(this)
} }
override fun onConfigurationChanged(newConfig: Configuration) { override fun onConfigurationChanged(newConfig: Configuration) {
ResourceMgr.reload(newConfig)
if (!isRunningAsStub)
super.onConfigurationChanged(newConfig) super.onConfigurationChanged(newConfig)
LocaleManager.setLocale(this)
} }
} }
...@@ -13,8 +13,8 @@ object Const { ...@@ -13,8 +13,8 @@ object Const {
// Versions // Versions
const val SNET_EXT_VER = 13 const val SNET_EXT_VER = 13
const val SNET_REVISION = "5adbc435ce93ded953c30ebe587edfd50b5503bc" const val SNET_REVISION = "a6c47f86f10b310358afa9dbe837037dd5d561df"
const val BOOTCTL_REVISION = "9c5dfc1b8245c0b5b524901ef0ff0f8335757b77" const val BOOTCTL_REVISION = "a6c47f86f10b310358afa9dbe837037dd5d561df"
// Misc // Misc
const val ANDROID_MANIFEST = "AndroidManifest.xml" const val ANDROID_MANIFEST = "AndroidManifest.xml"
......
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.annotation.SuppressLint
import android.content.SharedPreferences import android.content.SharedPreferences
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.view.children
import androidx.core.view.isVisible
import androidx.preference.* import androidx.preference.*
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.topjohnwu.magisk.R
import org.koin.android.ext.android.inject import org.koin.android.ext.android.inject
abstract class BasePreferenceFragment : PreferenceFragmentCompat(), abstract class BasePreferenceFragment : PreferenceFragmentCompat(),
SharedPreferences.OnSharedPreferenceChangeListener { SharedPreferences.OnSharedPreferenceChangeListener {
protected val prefs: SharedPreferences by inject() protected val prefs: SharedPreferences by inject()
protected val activity get() = requireActivity() as MagiskActivity<*, *> protected val activity get() = requireActivity() as BaseActivity<*, *>
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,
...@@ -35,28 +31,26 @@ abstract class BasePreferenceFragment : PreferenceFragmentCompat(), ...@@ -35,28 +31,26 @@ abstract class BasePreferenceFragment : PreferenceFragmentCompat(),
super.onDestroyView() super.onDestroyView()
} }
override fun onCreateAdapter(preferenceScreen: PreferenceScreen): RecyclerView.Adapter<*> { private fun setAllPreferencesToAvoidHavingExtraSpace(preference: Preference) {
return object : PreferenceGroupAdapter(preferenceScreen) { preference.isIconSpaceReserved = false
@SuppressLint("RestrictedApi") if (preference is PreferenceGroup)
override fun onBindViewHolder(holder: PreferenceViewHolder, position: Int) { for (i in 0 until preference.preferenceCount)
super.onBindViewHolder(holder, position) setAllPreferencesToAvoidHavingExtraSpace(preference.getPreference(i))
when (val preference = getItem(position)) {
is PreferenceCategory -> setZeroPaddingToLayoutChildren(holder.itemView)
else -> holder.itemView.findViewById<View>(R.id.icon_frame)?.isVisible =
preference.icon != null
}
}
}
} }
private fun setZeroPaddingToLayoutChildren(view: View) { override fun setPreferenceScreen(preferenceScreen: PreferenceScreen?) {
(view as? ViewGroup)?.children?.forEach { if (preferenceScreen != null)
setZeroPaddingToLayoutChildren(it) setAllPreferencesToAvoidHavingExtraSpace(preferenceScreen)
} ?: return super.setPreferenceScreen(preferenceScreen)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) override fun onCreateAdapter(preferenceScreen: PreferenceScreen?): RecyclerView.Adapter<*> =
view.setPaddingRelative(0, view.paddingTop, view.paddingEnd, view.paddingBottom) object : PreferenceGroupAdapter(preferenceScreen) {
else @SuppressLint("RestrictedApi")
view.setPadding(0, view.paddingTop, view.paddingRight, view.paddingBottom) 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.content.Context
import android.net.Network import android.net.Network
......
package com.topjohnwu.magisk.ui.base package com.topjohnwu.magisk.base.viewmodel
import android.app.Activity import android.app.Activity
import com.github.pwittchen.reactivenetwork.library.rx2.ReactiveNetwork import com.github.pwittchen.reactivenetwork.library.rx2.ReactiveNetwork
import com.skoumal.teanity.extensions.doOnSubscribeUi import com.topjohnwu.magisk.extensions.doOnSubscribeUi
import com.skoumal.teanity.extensions.subscribeK
import com.skoumal.teanity.util.KObservableField
import com.skoumal.teanity.viewmodel.LoadingViewModel
import com.topjohnwu.magisk.extensions.get import com.topjohnwu.magisk.extensions.get
import com.topjohnwu.magisk.extensions.subscribeK
import com.topjohnwu.magisk.model.events.BackPressEvent import com.topjohnwu.magisk.model.events.BackPressEvent
import com.topjohnwu.magisk.model.events.PermissionEvent import com.topjohnwu.magisk.model.events.PermissionEvent
import com.topjohnwu.magisk.model.events.ViewActionEvent import com.topjohnwu.magisk.model.events.ViewActionEvent
import com.topjohnwu.magisk.utils.KObservableField
import io.reactivex.Observable import io.reactivex.Observable
import io.reactivex.subjects.PublishSubject import io.reactivex.subjects.PublishSubject
abstract class MagiskViewModel( abstract class BaseViewModel(
initialState: State = State.LOADING initialState: State = State.LOADING
) : LoadingViewModel(initialState) { ) : LoadingViewModel(initialState) {
val isConnected = KObservableField(true) val isConnected = KObservableField(false)
init { init {
ReactiveNetwork.observeNetworkConnectivity(get()) ReactiveNetwork.observeNetworkConnectivity(get())
......
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 { ...@@ -19,10 +19,10 @@ interface GithubRawServices {
@GET("$MAGISK_FILES/master/beta.json") @GET("$MAGISK_FILES/master/beta.json")
fun fetchBetaUpdate(): Single<UpdateInfo> fun fetchBetaUpdate(): Single<UpdateInfo>
@GET("$MAGISK_FILES/master/canary_builds/release.json") @GET("$MAGISK_FILES/canary/release.json")
fun fetchCanaryUpdate(): Single<UpdateInfo> fun fetchCanaryUpdate(): Single<UpdateInfo>
@GET("$MAGISK_FILES/master/canary_builds/canary.json") @GET("$MAGISK_FILES/canary/debug.json")
fun fetchCanaryDebugUpdate(): Single<UpdateInfo> fun fetchCanaryDebugUpdate(): Single<UpdateInfo>
@GET @GET
......
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 ...@@ -7,7 +7,7 @@ import android.content.Context
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import com.skoumal.teanity.rxbus.RxBus import com.topjohnwu.magisk.utils.RxBus
import org.koin.core.qualifier.named import org.koin.core.qualifier.named
import org.koin.dsl.module import org.koin.dsl.module
......
package com.topjohnwu.magisk.di package com.topjohnwu.magisk.di
import android.content.Context
import com.squareup.moshi.JsonAdapter import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.Moshi import com.squareup.moshi.Moshi
import com.topjohnwu.magisk.BuildConfig import com.topjohnwu.magisk.BuildConfig
import com.topjohnwu.magisk.Const import com.topjohnwu.magisk.Const
import com.topjohnwu.magisk.data.network.GithubApiServices import com.topjohnwu.magisk.data.network.GithubApiServices
import com.topjohnwu.magisk.data.network.GithubRawServices import com.topjohnwu.magisk.data.network.GithubRawServices
import com.topjohnwu.magisk.net.Networking
import com.topjohnwu.magisk.net.NoSSLv3SocketFactory
import io.noties.markwon.Markwon
import io.noties.markwon.html.HtmlPlugin
import io.noties.markwon.image.ImagesPlugin
import io.noties.markwon.image.network.OkHttpNetworkSchemeHandler
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor import okhttp3.logging.HttpLoggingInterceptor
import org.koin.dsl.module import org.koin.dsl.module
...@@ -16,14 +23,15 @@ import retrofit2.converter.scalars.ScalarsConverterFactory ...@@ -16,14 +23,15 @@ import retrofit2.converter.scalars.ScalarsConverterFactory
import se.ansman.kotshi.KotshiJsonAdapterFactory import se.ansman.kotshi.KotshiJsonAdapterFactory
val networkingModule = module { val networkingModule = module {
single { createOkHttpClient() } single { createOkHttpClient(get()) }
single { createMoshiConverterFactory() } single { createRetrofit(get()) }
single { createRetrofit(get(), get()) }
single { createApiService<GithubRawServices>(get(), Const.Url.GITHUB_RAW_URL) } single { createApiService<GithubRawServices>(get(), Const.Url.GITHUB_RAW_URL) }
single { createApiService<GithubApiServices>(get(), Const.Url.GITHUB_API_URL) } single { createApiService<GithubApiServices>(get(), Const.Url.GITHUB_API_URL) }
single { createMarkwon(get(), get()) }
} }
fun createOkHttpClient(): OkHttpClient { @Suppress("DEPRECATION")
fun createOkHttpClient(context: Context): OkHttpClient {
val builder = OkHttpClient.Builder() val builder = OkHttpClient.Builder()
if (BuildConfig.DEBUG) { if (BuildConfig.DEBUG) {
...@@ -33,6 +41,10 @@ fun createOkHttpClient(): OkHttpClient { ...@@ -33,6 +41,10 @@ fun createOkHttpClient(): OkHttpClient {
builder.addInterceptor(httpLoggingInterceptor) builder.addInterceptor(httpLoggingInterceptor)
} }
if (!Networking.init(context)) {
builder.sslSocketFactory(NoSSLv3SocketFactory())
}
return builder.build() return builder.build()
} }
...@@ -43,13 +55,10 @@ fun createMoshiConverterFactory(): MoshiConverterFactory { ...@@ -43,13 +55,10 @@ fun createMoshiConverterFactory(): MoshiConverterFactory {
return MoshiConverterFactory.create(moshi) return MoshiConverterFactory.create(moshi)
} }
fun createRetrofit( fun createRetrofit(okHttpClient: OkHttpClient): Retrofit.Builder {
okHttpClient: OkHttpClient,
converterFactory: MoshiConverterFactory
): Retrofit.Builder {
return Retrofit.Builder() return Retrofit.Builder()
.addConverterFactory(ScalarsConverterFactory.create()) .addConverterFactory(ScalarsConverterFactory.create())
.addConverterFactory(converterFactory) .addConverterFactory(createMoshiConverterFactory())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(okHttpClient) .client(okHttpClient)
} }
...@@ -63,3 +72,12 @@ inline fun <reified T> createApiService(retrofitBuilder: Retrofit.Builder, baseU ...@@ -63,3 +72,12 @@ inline fun <reified T> createApiService(retrofitBuilder: Retrofit.Builder, baseU
.build() .build()
.create(T::class.java) .create(T::class.java)
} }
fun createMarkwon(context: Context, okHttpClient: OkHttpClient): Markwon {
return Markwon.builder(context)
.usePlugin(HtmlPlugin.create())
.usePlugin(ImagesPlugin.create {
it.addSchemeHandler(OkHttpNetworkSchemeHandler.create(okHttpClient))
})
.build()
}
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 package com.topjohnwu.magisk.extensions
import android.content.ComponentName
import android.content.Context import android.content.Context
import android.content.ContextWrapper
import android.content.Intent import android.content.Intent
import android.content.pm.ApplicationInfo import android.content.pm.ApplicationInfo
import android.content.pm.ComponentInfo import android.content.pm.ComponentInfo
...@@ -8,13 +10,23 @@ import android.content.pm.PackageInfo ...@@ -8,13 +10,23 @@ import android.content.pm.PackageInfo
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.content.pm.PackageManager.* import android.content.pm.PackageManager.*
import android.content.res.Configuration import android.content.res.Configuration
import android.content.res.Resources
import android.database.Cursor import android.database.Cursor
import android.net.Uri import android.net.Uri
import android.os.Build
import android.provider.OpenableColumns import android.provider.OpenableColumns
import android.view.View
import androidx.annotation.ColorRes
import androidx.annotation.DrawableRes
import androidx.core.content.ContextCompat
import androidx.core.net.toUri
import com.topjohnwu.magisk.utils.DynamicClassLoader
import com.topjohnwu.magisk.utils.FileProvider import com.topjohnwu.magisk.utils.FileProvider
import com.topjohnwu.magisk.utils.Utils
import com.topjohnwu.magisk.utils.currentLocale import com.topjohnwu.magisk.utils.currentLocale
import java.io.File import java.io.File
import java.io.FileNotFoundException import java.io.FileNotFoundException
import java.util.*
val packageName: String get() = get<Context>().packageName val packageName: String get() = get<Context>().packageName
...@@ -85,6 +97,105 @@ fun Context.readUri(uri: Uri) = ...@@ -85,6 +97,105 @@ fun Context.readUri(uri: Uri) =
fun Intent.startActivity(context: Context) = context.startActivity(this) fun Intent.startActivity(context: Context) = context.startActivity(this)
fun Intent.toCommand(args: MutableList<String>) {
if (action != null) {
args.add("-a")
args.add(action!!)
}
if (component != null) {
args.add("-n")
args.add(component!!.flattenToString())
}
if (data != null) {
args.add("-d")
args.add(dataString!!)
}
if (categories != null) {
for (cat in categories) {
args.add("-c")
args.add(cat)
}
}
if (type != null) {
args.add("-t")
args.add(type!!)
}
val extras = extras
if (extras != null) {
loop@ for (key in extras.keySet()) {
val v = extras.get(key) ?: continue
var value: Any = v
val arg: String
when {
v is String -> arg = "--es"
v is Boolean -> arg = "--ez"
v is Int -> arg = "--ei"
v is Long -> arg = "--el"
v is Float -> arg = "--ef"
v is Uri -> arg = "--eu"
v is ComponentName -> {
arg = "--ecn"
value = v.flattenToString()
}
v is ArrayList<*> -> {
if (v.size <= 0)
/* Impossible to know the type due to type erasure */
continue@loop
arg = if (v[0] is Int)
"--eial"
else if (v[0] is Long)
"--elal"
else if (v[0] is Float)
"--efal"
else if (v[0] is String)
"--esal"
else
continue@loop /* Unsupported */
val sb = StringBuilder()
for (o in v) {
sb.append(o.toString().replace(",", "\\,"))
sb.append(',')
}
// Remove trailing comma
sb.deleteCharAt(sb.length - 1)
value = sb
}
v.javaClass.isArray -> {
arg = if (v is IntArray)
"--eia"
else if (v is LongArray)
"--ela"
else if (v is FloatArray)
"--efa"
else if (v is Array<*> && v.isArrayOf<String>())
"--esa"
else
continue@loop /* Unsupported */
val sb = StringBuilder()
val len = java.lang.reflect.Array.getLength(v)
for (i in 0 until len) {
sb.append(java.lang.reflect.Array.get(v, i)!!.toString().replace(",", "\\,"))
sb.append(',')
}
// Remove trailing comma
sb.deleteCharAt(sb.length - 1)
value = sb
}
else -> continue@loop
} /* Unsupported */
args.add(arg)
args.add(key)
args.add(value.toString())
}
}
args.add("-f")
args.add(flags.toString())
}
fun File.provide(context: Context = get()): Uri { fun File.provide(context: Context = get()): Uri {
return FileProvider.getUriForFile(context, context.packageName + ".provider", this) return FileProvider.getUriForFile(context, context.packageName + ".provider", this)
} }
...@@ -119,3 +230,48 @@ fun ApplicationInfo.getLabel(pm: PackageManager): String { ...@@ -119,3 +230,48 @@ fun ApplicationInfo.getLabel(pm: PackageManager): String {
return loadLabel(pm).toString() return loadLabel(pm).toString()
} }
fun Intent.exists(packageManager: PackageManager) = resolveActivity(packageManager) != null
fun Context.colorCompat(@ColorRes id: Int) = try {
ContextCompat.getColor(this, id)
} catch (e: Resources.NotFoundException) {
null
}
fun Context.colorStateListCompat(@ColorRes id: Int) = try {
ContextCompat.getColorStateList(this, id)
} catch (e: Resources.NotFoundException) {
null
}
fun Context.drawableCompat(@DrawableRes id: Int) = ContextCompat.getDrawable(this, id)
/**
* Pass [start] and [end] dimensions, function will return left and right
* with respect to RTL layout direction
*/
fun Context.startEndToLeftRight(start: Int, end: Int): Pair<Int, Int> {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 &&
resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_RTL
) {
return end to start
}
return start to end
}
fun Context.openUrl(url: String) = Utils.openLink(this, url.toUri())
@Suppress("FunctionName")
inline fun <reified T> T.DynamicClassLoader(apk: File)
= DynamicClassLoader(apk, T::class.java.classLoader)
fun Context.unwrap() : Context {
var context = this
while (true) {
if (context is ContextWrapper)
context = context.baseContext
else
break
}
return context
}
package com.topjohnwu.magisk.extensions package com.topjohnwu.magisk.extensions
import com.skoumal.teanity.util.KObservableField import com.topjohnwu.magisk.utils.KObservableField
fun KObservableField<Boolean>.toggle() { fun KObservableField<Boolean>.toggle() {
......
...@@ -2,8 +2,7 @@ package com.topjohnwu.magisk.extensions ...@@ -2,8 +2,7 @@ package com.topjohnwu.magisk.extensions
import androidx.collection.SparseArrayCompat import androidx.collection.SparseArrayCompat
import androidx.databinding.ObservableList import androidx.databinding.ObservableList
import com.skoumal.teanity.extensions.subscribeK import com.topjohnwu.magisk.utils.DiffObservableList
import com.skoumal.teanity.util.DiffObservableList
import io.reactivex.disposables.Disposable import io.reactivex.disposables.Disposable
fun <T> MutableList<T>.update(newList: List<T>) { fun <T> MutableList<T>.update(newList: List<T>) {
......
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 ...@@ -2,7 +2,7 @@ package com.topjohnwu.magisk.model.binding
import androidx.databinding.ViewDataBinding import androidx.databinding.ViewDataBinding
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.skoumal.teanity.databinding.ComparableRvItem import com.topjohnwu.magisk.databinding.ComparableRvItem
import com.topjohnwu.magisk.model.entity.recycler.LenientRvItem import com.topjohnwu.magisk.model.entity.recycler.LenientRvItem
import me.tatarka.bindingcollectionadapter2.BindingRecyclerViewAdapter import me.tatarka.bindingcollectionadapter2.BindingRecyclerViewAdapter
......
...@@ -10,6 +10,7 @@ import androidx.core.app.NotificationCompat ...@@ -10,6 +10,7 @@ import androidx.core.app.NotificationCompat
import com.topjohnwu.magisk.ClassMap import com.topjohnwu.magisk.ClassMap
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.extensions.chooser import com.topjohnwu.magisk.extensions.chooser
import com.topjohnwu.magisk.extensions.exists
import com.topjohnwu.magisk.extensions.provide import com.topjohnwu.magisk.extensions.provide
import com.topjohnwu.magisk.model.entity.internal.Configuration.* import com.topjohnwu.magisk.model.entity.internal.Configuration.*
import com.topjohnwu.magisk.model.entity.internal.Configuration.Flash.Secondary import com.topjohnwu.magisk.model.entity.internal.Configuration.Flash.Secondary
...@@ -17,6 +18,7 @@ import com.topjohnwu.magisk.model.entity.internal.DownloadSubject ...@@ -17,6 +18,7 @@ import com.topjohnwu.magisk.model.entity.internal.DownloadSubject
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject.* import com.topjohnwu.magisk.model.entity.internal.DownloadSubject.*
import com.topjohnwu.magisk.ui.flash.FlashActivity import com.topjohnwu.magisk.ui.flash.FlashActivity
import com.topjohnwu.magisk.utils.APKInstall import com.topjohnwu.magisk.utils.APKInstall
import org.koin.core.get
import java.io.File import java.io.File
import kotlin.random.Random.Default.nextInt import kotlin.random.Random.Default.nextInt
...@@ -76,8 +78,14 @@ open class DownloadService : RemoteFileService() { ...@@ -76,8 +78,14 @@ open class DownloadService : RemoteFileService() {
private fun NotificationCompat.Builder.addActionsInternal(subject: Magisk) private fun NotificationCompat.Builder.addActionsInternal(subject: Magisk)
= when (val conf = subject.configuration) { = when (val conf = subject.configuration) {
Download -> addAction(0, R.string.download_open_parent, fileIntent(subject.file.parentFile!!)) Download -> this.apply {
.addAction(0, R.string.download_open_self, fileIntent(subject.file)) fileIntent(subject.file.parentFile!!)
.takeIf { it.exists(get()) }
?.let { addAction(0, R.string.download_open_parent, it.chooser()) }
fileIntent(subject.file)
.takeIf { it.exists(get()) }
?.let { addAction(0, R.string.download_open_self, it.chooser()) }
}
Uninstall -> setContentIntent(FlashActivity.uninstallIntent(context, subject.file)) Uninstall -> setContentIntent(FlashActivity.uninstallIntent(context, subject.file))
is Flash -> setContentIntent(FlashActivity.flashIntent(context, subject.file, conf is Secondary)) is Flash -> setContentIntent(FlashActivity.flashIntent(context, subject.file, conf is Secondary))
is Patch -> setContentIntent(FlashActivity.patchIntent(context, subject.file, conf.fileUri)) is Patch -> setContentIntent(FlashActivity.patchIntent(context, subject.file, conf.fileUri))
...@@ -86,8 +94,14 @@ open class DownloadService : RemoteFileService() { ...@@ -86,8 +94,14 @@ open class DownloadService : RemoteFileService() {
private fun NotificationCompat.Builder.addActionsInternal(subject: Module) private fun NotificationCompat.Builder.addActionsInternal(subject: Module)
= when (subject.configuration) { = when (subject.configuration) {
Download -> addAction(0, R.string.download_open_parent, fileIntent(subject.file.parentFile!!)) Download -> this.apply {
.addAction(0, R.string.download_open_self, fileIntent(subject.file)) fileIntent(subject.file.parentFile!!)
.takeIf { it.exists(get()) }
?.let { addAction(0, R.string.download_open_parent, it.chooser()) }
fileIntent(subject.file)
.takeIf { it.exists(get()) }
?.let { addAction(0, R.string.download_open_self, it.chooser()) }
}
is Flash -> setContentIntent(FlashActivity.installIntent(context, subject.file)) is Flash -> setContentIntent(FlashActivity.installIntent(context, subject.file))
else -> this else -> this
} }
...@@ -115,7 +129,6 @@ open class DownloadService : RemoteFileService() { ...@@ -115,7 +129,6 @@ open class DownloadService : RemoteFileService() {
.setDataAndType(file.provide(this), file.type) .setDataAndType(file.provide(this), file.type)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
.chooser()
} }
class Builder { class Builder {
......
...@@ -5,13 +5,13 @@ import com.topjohnwu.magisk.BuildConfig ...@@ -5,13 +5,13 @@ import com.topjohnwu.magisk.BuildConfig
import com.topjohnwu.magisk.ClassMap import com.topjohnwu.magisk.ClassMap
import com.topjohnwu.magisk.Config import com.topjohnwu.magisk.Config
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.extensions.DynamicClassLoader
import com.topjohnwu.magisk.model.entity.internal.Configuration.APK.Restore import com.topjohnwu.magisk.model.entity.internal.Configuration.APK.Restore
import com.topjohnwu.magisk.model.entity.internal.Configuration.APK.Upgrade import com.topjohnwu.magisk.model.entity.internal.Configuration.APK.Upgrade
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject import com.topjohnwu.magisk.model.entity.internal.DownloadSubject
import com.topjohnwu.magisk.ui.SplashActivity import com.topjohnwu.magisk.ui.SplashActivity
import com.topjohnwu.magisk.utils.DynamicClassLoader
import com.topjohnwu.magisk.utils.PatchAPK import com.topjohnwu.magisk.utils.PatchAPK
import com.topjohnwu.magisk.utils.RootUtils import com.topjohnwu.magisk.utils.Utils
import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.Shell
import timber.log.Timber import timber.log.Timber
import java.io.File import java.io.File
...@@ -54,7 +54,7 @@ private fun RemoteFileService.restore(apk: File, id: Int) { ...@@ -54,7 +54,7 @@ private fun RemoteFileService.restore(apk: File, id: Int) {
if (Shell.su("pm install $apk").exec().isSuccess) { if (Shell.su("pm install $apk").exec().isSuccess) {
val component = ComponentName(BuildConfig.APPLICATION_ID, val component = ComponentName(BuildConfig.APPLICATION_ID,
ClassMap.get<Class<*>>(SplashActivity::class.java).name) ClassMap.get<Class<*>>(SplashActivity::class.java).name)
RootUtils.rmAndLaunch(packageName, component) Utils.rmAndLaunch(packageName, component)
} }
} }
......
package com.topjohnwu.magisk.model.download package com.topjohnwu.magisk.model.download
import android.app.Notification import android.app.Notification
import android.app.Service
import android.content.Intent import android.content.Intent
import android.os.IBinder import android.os.IBinder
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat import androidx.core.app.NotificationManagerCompat
import com.topjohnwu.magisk.base.BaseService
import org.koin.core.KoinComponent
import java.util.* import java.util.*
import kotlin.random.Random.Default.nextInt import kotlin.random.Random.Default.nextInt
abstract class NotificationService : Service() { abstract class NotificationService : BaseService(), KoinComponent {
abstract val defaultNotification: NotificationCompat.Builder abstract val defaultNotification: NotificationCompat.Builder
......
...@@ -3,11 +3,11 @@ package com.topjohnwu.magisk.model.download ...@@ -3,11 +3,11 @@ package com.topjohnwu.magisk.model.download
import android.app.Activity import android.app.Activity
import android.content.Intent import android.content.Intent
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import com.skoumal.teanity.extensions.subscribeK
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.data.network.GithubRawServices import com.topjohnwu.magisk.data.network.GithubRawServices
import com.topjohnwu.magisk.di.NullActivity import com.topjohnwu.magisk.di.NullActivity
import com.topjohnwu.magisk.extensions.get import com.topjohnwu.magisk.extensions.get
import com.topjohnwu.magisk.extensions.subscribeK
import com.topjohnwu.magisk.extensions.writeTo import com.topjohnwu.magisk.extensions.writeTo
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject import com.topjohnwu.magisk.model.entity.internal.DownloadSubject
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject.* import com.topjohnwu.magisk.model.entity.internal.DownloadSubject.*
......
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() { ...@@ -48,7 +48,7 @@ class Module(path: String) : BaseModule() {
} }
if (name.isEmpty()) { if (name.isEmpty()) {
name = id; name = id
} }
} }
...@@ -65,7 +65,7 @@ class Module(path: String) : BaseModule() { ...@@ -65,7 +65,7 @@ class Module(path: String) : BaseModule() {
val module = Module(Const.MAGISK_PATH + "/" + file.name) val module = Module(Const.MAGISK_PATH + "/" + file.name)
moduleList.add(module) moduleList.add(module)
} }
return moduleList return moduleList.sortedBy { it.name }
} }
} }
} }
package com.topjohnwu.magisk.model.entity.recycler package com.topjohnwu.magisk.model.entity.recycler
import com.skoumal.teanity.databinding.ComparableRvItem
import com.skoumal.teanity.extensions.addOnPropertyChangedCallback
import com.skoumal.teanity.rxbus.RxBus
import com.skoumal.teanity.util.DiffObservableList
import com.skoumal.teanity.util.KObservableField
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.databinding.ComparableRvItem
import com.topjohnwu.magisk.extensions.addOnPropertyChangedCallback
import com.topjohnwu.magisk.extensions.inject import com.topjohnwu.magisk.extensions.inject
import com.topjohnwu.magisk.extensions.toggle import com.topjohnwu.magisk.extensions.toggle
import com.topjohnwu.magisk.model.entity.HideAppInfo import com.topjohnwu.magisk.model.entity.HideAppInfo
import com.topjohnwu.magisk.model.entity.HideTarget import com.topjohnwu.magisk.model.entity.HideTarget
import com.topjohnwu.magisk.model.entity.state.IndeterminateState import com.topjohnwu.magisk.model.entity.state.IndeterminateState
import com.topjohnwu.magisk.model.events.HideProcessEvent import com.topjohnwu.magisk.model.events.HideProcessEvent
import com.topjohnwu.magisk.utils.DiffObservableList
import com.topjohnwu.magisk.utils.KObservableField
import com.topjohnwu.magisk.utils.RxBus
class HideRvItem(val item: HideAppInfo, targets: List<HideTarget>) : class HideRvItem(val item: HideAppInfo, targets: List<HideTarget>) :
ComparableRvItem<HideRvItem>() { ComparableRvItem<HideRvItem>() {
......
...@@ -2,7 +2,7 @@ package com.topjohnwu.magisk.model.entity.recycler ...@@ -2,7 +2,7 @@ package com.topjohnwu.magisk.model.entity.recycler
import androidx.databinding.ViewDataBinding import androidx.databinding.ViewDataBinding
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.skoumal.teanity.databinding.ComparableRvItem import com.topjohnwu.magisk.databinding.ComparableRvItem
/** /**
* This item addresses issues where enclosing recycler has to be invalidated or generally * This item addresses issues where enclosing recycler has to be invalidated or generally
......
package com.topjohnwu.magisk.model.entity.recycler package com.topjohnwu.magisk.model.entity.recycler
import com.skoumal.teanity.databinding.ComparableRvItem
import com.skoumal.teanity.util.DiffObservableList
import com.skoumal.teanity.util.KObservableField
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.databinding.ComparableRvItem
import com.topjohnwu.magisk.extensions.timeFormatMedium import com.topjohnwu.magisk.extensions.timeFormatMedium
import com.topjohnwu.magisk.extensions.toTime import com.topjohnwu.magisk.extensions.toTime
import com.topjohnwu.magisk.extensions.toggle import com.topjohnwu.magisk.extensions.toggle
import com.topjohnwu.magisk.model.entity.MagiskLog import com.topjohnwu.magisk.model.entity.MagiskLog
import com.topjohnwu.magisk.model.entity.WrappedMagiskLog import com.topjohnwu.magisk.model.entity.WrappedMagiskLog
import com.topjohnwu.magisk.utils.DiffObservableList
import com.topjohnwu.magisk.utils.KObservableField
class LogRvItem : ComparableRvItem<LogRvItem>() { class LogRvItem : ComparableRvItem<LogRvItem>() {
override val layoutRes: Int = R.layout.item_page_log override val layoutRes: Int = R.layout.item_page_log
......
...@@ -2,14 +2,14 @@ package com.topjohnwu.magisk.model.entity.recycler ...@@ -2,14 +2,14 @@ package com.topjohnwu.magisk.model.entity.recycler
import android.content.res.Resources import android.content.res.Resources
import androidx.annotation.StringRes import androidx.annotation.StringRes
import com.skoumal.teanity.databinding.ComparableRvItem
import com.skoumal.teanity.extensions.addOnPropertyChangedCallback
import com.skoumal.teanity.util.KObservableField
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.databinding.ComparableRvItem
import com.topjohnwu.magisk.extensions.addOnPropertyChangedCallback
import com.topjohnwu.magisk.extensions.get import com.topjohnwu.magisk.extensions.get
import com.topjohnwu.magisk.extensions.toggle import com.topjohnwu.magisk.extensions.toggle
import com.topjohnwu.magisk.model.entity.module.Module import com.topjohnwu.magisk.model.entity.module.Module
import com.topjohnwu.magisk.model.entity.module.Repo import com.topjohnwu.magisk.model.entity.module.Repo
import com.topjohnwu.magisk.utils.KObservableField
class ModuleRvItem(val item: Module) : ComparableRvItem<ModuleRvItem>() { class ModuleRvItem(val item: Module) : ComparableRvItem<ModuleRvItem>() {
......
package com.topjohnwu.magisk.model.entity.recycler package com.topjohnwu.magisk.model.entity.recycler
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import com.skoumal.teanity.databinding.ComparableRvItem
import com.skoumal.teanity.extensions.addOnPropertyChangedCallback
import com.skoumal.teanity.rxbus.RxBus
import com.skoumal.teanity.util.KObservableField
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.databinding.ComparableRvItem
import com.topjohnwu.magisk.extensions.addOnPropertyChangedCallback
import com.topjohnwu.magisk.extensions.inject import com.topjohnwu.magisk.extensions.inject
import com.topjohnwu.magisk.extensions.toggle import com.topjohnwu.magisk.extensions.toggle
import com.topjohnwu.magisk.model.entity.MagiskPolicy import com.topjohnwu.magisk.model.entity.MagiskPolicy
import com.topjohnwu.magisk.model.events.PolicyEnableEvent import com.topjohnwu.magisk.model.events.PolicyEnableEvent
import com.topjohnwu.magisk.model.events.PolicyUpdateEvent import com.topjohnwu.magisk.model.events.PolicyUpdateEvent
import com.topjohnwu.magisk.utils.KObservableField
import com.topjohnwu.magisk.utils.RxBus
class PolicyRvItem(val item: MagiskPolicy, val icon: Drawable) : ComparableRvItem<PolicyRvItem>() { class PolicyRvItem(val item: MagiskPolicy, val icon: Drawable) : ComparableRvItem<PolicyRvItem>() {
......
package com.topjohnwu.magisk.model.entity.recycler package com.topjohnwu.magisk.model.entity.recycler
import com.skoumal.teanity.databinding.ComparableRvItem
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.databinding.ComparableRvItem
class SectionRvItem(val text: String) : ComparableRvItem<SectionRvItem>() { class SectionRvItem(val text: String) : ComparableRvItem<SectionRvItem>() {
override val layoutRes: Int = R.layout.item_section override val layoutRes: Int = R.layout.item_section
......
package com.topjohnwu.magisk.model.entity.recycler package com.topjohnwu.magisk.model.entity.recycler
import com.skoumal.teanity.databinding.ComparableRvItem
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.databinding.ComparableRvItem
class SpinnerRvItem(val item: String) : ComparableRvItem<SpinnerRvItem>() { class SpinnerRvItem(val item: String) : ComparableRvItem<SpinnerRvItem>() {
......
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 package com.topjohnwu.magisk.model.events
import com.skoumal.teanity.rxbus.RxBus
import com.topjohnwu.magisk.model.entity.MagiskPolicy import com.topjohnwu.magisk.model.entity.MagiskPolicy
import com.topjohnwu.magisk.model.entity.recycler.HideProcessRvItem import com.topjohnwu.magisk.model.entity.recycler.HideProcessRvItem
import com.topjohnwu.magisk.model.entity.recycler.ModuleRvItem import com.topjohnwu.magisk.model.entity.recycler.ModuleRvItem
import com.topjohnwu.magisk.model.entity.recycler.PolicyRvItem import com.topjohnwu.magisk.model.entity.recycler.PolicyRvItem
import com.topjohnwu.magisk.utils.RxBus
class HideProcessEvent(val item: HideProcessRvItem) : RxBus.Event class HideProcessEvent(val item: HideProcessRvItem) : RxBus.Event
......
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 ...@@ -12,6 +12,16 @@ import com.topjohnwu.magisk.model.entity.module.Repo
import com.topjohnwu.magisk.model.permissions.PermissionRequestBuilder import com.topjohnwu.magisk.model.permissions.PermissionRequestBuilder
import io.reactivex.subjects.PublishSubject import io.reactivex.subjects.PublishSubject
/**
* Class for passing events from ViewModels to Activities/Fragments
* Variable [handled] used so each event is handled only once
* (see https://medium.com/google-developers/livedata-with-snackbar-navigation-and-other-events-the-singleliveevent-case-ac2622673150)
* Use [ViewEventObserver] for observing these events
*/
abstract class ViewEvent {
var handled = false
}
data class OpenLinkEvent(val url: String) : ViewEvent() data class OpenLinkEvent(val url: String) : ViewEvent()
......
...@@ -5,12 +5,14 @@ import androidx.annotation.AnimRes ...@@ -5,12 +5,14 @@ import androidx.annotation.AnimRes
import androidx.annotation.AnimatorRes import androidx.annotation.AnimatorRes
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import com.skoumal.teanity.viewevents.NavigationDslMarker import com.topjohnwu.magisk.model.events.ViewEvent
import com.skoumal.teanity.viewevents.ViewEvent
import com.topjohnwu.magisk.model.events.ActivityExecutor import com.topjohnwu.magisk.model.events.ActivityExecutor
import com.topjohnwu.magisk.redesign.compat.CompatActivity import com.topjohnwu.magisk.redesign.compat.CompatActivity
import kotlin.reflect.KClass import kotlin.reflect.KClass
@DslMarker
annotation class NavigationDslMarker
class MagiskNavigationEvent( class MagiskNavigationEvent(
val navDirections: MagiskNavDirectionsBuilder, val navDirections: MagiskNavDirectionsBuilder,
val navOptions: MagiskNavOptions, val navOptions: MagiskNavOptions,
......
package com.topjohnwu.magisk.model.receiver package com.topjohnwu.magisk.model.receiver
import android.content.BroadcastReceiver import android.content.ContextWrapper
import android.content.Context
import android.content.Intent import android.content.Intent
import com.topjohnwu.magisk.ClassMap import com.topjohnwu.magisk.ClassMap
import com.topjohnwu.magisk.Config import com.topjohnwu.magisk.Config
import com.topjohnwu.magisk.Const import com.topjohnwu.magisk.Const
import com.topjohnwu.magisk.Info import com.topjohnwu.magisk.Info
import com.topjohnwu.magisk.base.BaseReceiver
import com.topjohnwu.magisk.data.database.PolicyDao import com.topjohnwu.magisk.data.database.PolicyDao
import com.topjohnwu.magisk.data.database.base.su import com.topjohnwu.magisk.data.database.base.su
import com.topjohnwu.magisk.extensions.inject
import com.topjohnwu.magisk.extensions.reboot import com.topjohnwu.magisk.extensions.reboot
import com.topjohnwu.magisk.model.download.DownloadService import com.topjohnwu.magisk.model.download.DownloadService
import com.topjohnwu.magisk.model.entity.ManagerJson import com.topjohnwu.magisk.model.entity.ManagerJson
...@@ -20,8 +19,9 @@ import com.topjohnwu.magisk.utils.SuLogger ...@@ -20,8 +19,9 @@ import com.topjohnwu.magisk.utils.SuLogger
import com.topjohnwu.magisk.view.Notifications import com.topjohnwu.magisk.view.Notifications
import com.topjohnwu.magisk.view.Shortcuts import com.topjohnwu.magisk.view.Shortcuts
import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.Shell
import org.koin.core.inject
open class GeneralReceiver : BroadcastReceiver() { open class GeneralReceiver : BaseReceiver() {
private val policyDB: PolicyDao by inject() private val policyDB: PolicyDao by inject()
...@@ -36,7 +36,7 @@ open class GeneralReceiver : BroadcastReceiver() { ...@@ -36,7 +36,7 @@ open class GeneralReceiver : BroadcastReceiver() {
return intent.data?.encodedSchemeSpecificPart.orEmpty() return intent.data?.encodedSchemeSpecificPart.orEmpty()
} }
override fun onReceive(context: Context, intent: Intent?) { override fun onReceive(context: ContextWrapper, intent: Intent?) {
intent ?: return intent ?: return
when (intent.action ?: return) { when (intent.action ?: return) {
Intent.ACTION_REBOOT, Intent.ACTION_BOOT_COMPLETED -> { Intent.ACTION_REBOOT, Intent.ACTION_BOOT_COMPLETED -> {
......
...@@ -3,9 +3,9 @@ package com.topjohnwu.magisk.model.update ...@@ -3,9 +3,9 @@ package com.topjohnwu.magisk.model.update
import androidx.work.ListenableWorker import androidx.work.ListenableWorker
import com.topjohnwu.magisk.BuildConfig import com.topjohnwu.magisk.BuildConfig
import com.topjohnwu.magisk.Info import com.topjohnwu.magisk.Info
import com.topjohnwu.magisk.base.DelegateWorker
import com.topjohnwu.magisk.data.repository.MagiskRepository import com.topjohnwu.magisk.data.repository.MagiskRepository
import com.topjohnwu.magisk.extensions.inject import com.topjohnwu.magisk.extensions.inject
import com.topjohnwu.magisk.model.worker.DelegateWorker
import com.topjohnwu.magisk.view.Notifications import com.topjohnwu.magisk.view.Notifications
import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.Shell
......
...@@ -2,11 +2,11 @@ package com.topjohnwu.magisk.tasks ...@@ -2,11 +2,11 @@ package com.topjohnwu.magisk.tasks
import android.content.Context import android.content.Context
import android.net.Uri import android.net.Uri
import com.skoumal.teanity.extensions.subscribeK
import com.topjohnwu.magisk.Const import com.topjohnwu.magisk.Const
import com.topjohnwu.magisk.extensions.fileName import com.topjohnwu.magisk.extensions.fileName
import com.topjohnwu.magisk.extensions.inject import com.topjohnwu.magisk.extensions.inject
import com.topjohnwu.magisk.extensions.readUri import com.topjohnwu.magisk.extensions.readUri
import com.topjohnwu.magisk.extensions.subscribeK
import com.topjohnwu.magisk.utils.unzip import com.topjohnwu.magisk.utils.unzip
import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.Shell
import io.reactivex.Single import io.reactivex.Single
......
...@@ -6,7 +6,6 @@ import android.os.Build ...@@ -6,7 +6,6 @@ import android.os.Build
import android.text.TextUtils import android.text.TextUtils
import androidx.annotation.MainThread import androidx.annotation.MainThread
import androidx.annotation.WorkerThread import androidx.annotation.WorkerThread
import com.skoumal.teanity.extensions.subscribeK
import com.topjohnwu.magisk.Config import com.topjohnwu.magisk.Config
import com.topjohnwu.magisk.Info import com.topjohnwu.magisk.Info
import com.topjohnwu.magisk.data.network.GithubRawServices import com.topjohnwu.magisk.data.network.GithubRawServices
...@@ -25,9 +24,11 @@ import org.kamranzafar.jtar.TarHeader ...@@ -25,9 +24,11 @@ import org.kamranzafar.jtar.TarHeader
import org.kamranzafar.jtar.TarInputStream import org.kamranzafar.jtar.TarInputStream
import org.kamranzafar.jtar.TarOutputStream import org.kamranzafar.jtar.TarOutputStream
import timber.log.Timber import timber.log.Timber
import java.io.* import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.io.InputStream
import java.nio.ByteBuffer import java.nio.ByteBuffer
import java.util.*
import java.util.zip.ZipEntry import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream import java.util.zip.ZipInputStream
...@@ -40,7 +41,7 @@ abstract class MagiskInstaller { ...@@ -40,7 +41,7 @@ abstract class MagiskInstaller {
private val console: MutableList<String> private val console: MutableList<String>
private val logs: MutableList<String> private val logs: MutableList<String>
private var isTar = false private var tarOut: TarOutputStream? = null
private val service: GithubRawServices by inject() private val service: GithubRawServices by inject()
private val context: Context by inject() private val context: Context by inject()
...@@ -151,7 +152,9 @@ abstract class MagiskInstaller { ...@@ -151,7 +152,9 @@ abstract class MagiskInstaller {
private fun handleTar(input: InputStream) { private fun handleTar(input: InputStream) {
console.add("- Processing tar file") console.add("- Processing tar file")
var vbmeta = false var vbmeta = false
withStreams(TarInputStream(input), TarOutputStream(destFile)) { tarIn, tarOut -> val tarOut = TarOutputStream(destFile)
this.tarOut = tarOut
TarInputStream(input).use { tarIn ->
lateinit var entry: TarEntry lateinit var entry: TarEntry
while (tarIn.nextEntry?.let { entry = it } != null) { while (tarIn.nextEntry?.let { entry = it } != null) {
if (entry.name.contains("boot.img") || entry.name.contains("recovery.img")) { if (entry.name.contains("boot.img") || entry.name.contains("recovery.img")) {
...@@ -215,8 +218,7 @@ abstract class MagiskInstaller { ...@@ -215,8 +218,7 @@ abstract class MagiskInstaller {
return false return false
} }
it.reset() it.reset()
if (Arrays.equals(magic, "ustar".toByteArray())) { if (magic.contentEquals("ustar".toByteArray())) {
isTar = true
destFile = File(Config.downloadDirectory, "magisk_patched.tar") destFile = File(Config.downloadDirectory, "magisk_patched.tar")
handleTar(it) handleTar(it)
} else { } else {
...@@ -293,15 +295,13 @@ abstract class MagiskInstaller { ...@@ -293,15 +295,13 @@ abstract class MagiskInstaller {
protected fun storeBoot(): Boolean { protected fun storeBoot(): Boolean {
val patched = SuFile.open(installDir, "new-boot.img") val patched = SuFile.open(installDir, "new-boot.img")
try { try {
val os: OutputStream val os = tarOut?.let {
if (isTar) { it.putNextEntry(newEntry(
os = TarOutputStream(destFile, true)
os.putNextEntry(newEntry(
if (srcBoot.contains("recovery")) "recovery.img" else "boot.img", if (srcBoot.contains("recovery")) "recovery.img" else "boot.img",
patched.length())) patched.length()))
} else { tarOut = null
os = destFile.outputStream() it
} } ?: destFile.outputStream()
patched.suInputStream().use { it.copyTo(os); os.close() } patched.suInputStream().use { it.copyTo(os); os.close() }
} catch (e: IOException) { } catch (e: IOException) {
console.add("! Failed to output to $destFile") console.add("! Failed to output to $destFile")
......
...@@ -4,15 +4,24 @@ import android.content.Intent ...@@ -4,15 +4,24 @@ import android.content.Intent
import android.os.Bundle import android.os.Bundle
import androidx.core.view.GravityCompat import androidx.core.view.GravityCompat
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import com.skoumal.teanity.extensions.addOnPropertyChangedCallback import androidx.fragment.app.FragmentTransaction
import com.ncapdevi.fragnav.FragNavController
import com.ncapdevi.fragnav.FragNavTransactionOptions
import com.topjohnwu.magisk.ClassMap import com.topjohnwu.magisk.ClassMap
import com.topjohnwu.magisk.Config import com.topjohnwu.magisk.Config
import com.topjohnwu.magisk.Const.Key.OPEN_SECTION import com.topjohnwu.magisk.Const.Key.OPEN_SECTION
import com.topjohnwu.magisk.Info import com.topjohnwu.magisk.Info
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.base.BaseActivity
import com.topjohnwu.magisk.base.BaseFragment
import com.topjohnwu.magisk.databinding.ActivityMainBinding import com.topjohnwu.magisk.databinding.ActivityMainBinding
import com.topjohnwu.magisk.extensions.addOnPropertyChangedCallback
import com.topjohnwu.magisk.extensions.snackbar
import com.topjohnwu.magisk.model.events.*
import com.topjohnwu.magisk.model.navigation.MagiskAnimBuilder
import com.topjohnwu.magisk.model.navigation.MagiskNavigationEvent
import com.topjohnwu.magisk.model.navigation.Navigation import com.topjohnwu.magisk.model.navigation.Navigation
import com.topjohnwu.magisk.ui.base.MagiskActivity import com.topjohnwu.magisk.model.navigation.Navigator
import com.topjohnwu.magisk.ui.hide.MagiskHideFragment import com.topjohnwu.magisk.ui.hide.MagiskHideFragment
import com.topjohnwu.magisk.ui.home.HomeFragment import com.topjohnwu.magisk.ui.home.HomeFragment
import com.topjohnwu.magisk.ui.log.LogFragment import com.topjohnwu.magisk.ui.log.LogFragment
...@@ -23,15 +32,22 @@ import com.topjohnwu.magisk.ui.superuser.SuperuserFragment ...@@ -23,15 +32,22 @@ import com.topjohnwu.magisk.ui.superuser.SuperuserFragment
import com.topjohnwu.magisk.utils.Utils import com.topjohnwu.magisk.utils.Utils
import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.Shell
import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.androidx.viewmodel.ext.android.viewModel
import timber.log.Timber
import kotlin.reflect.KClass import kotlin.reflect.KClass
open class MainActivity : BaseActivity<MainViewModel, ActivityMainBinding>(), Navigator,
open class MainActivity : MagiskActivity<MainViewModel, ActivityMainBinding>() { FragNavController.RootFragmentListener, FragNavController.TransactionListener {
override val layoutRes: Int = R.layout.activity_main override val layoutRes: Int = R.layout.activity_main
override val viewModel: MainViewModel by viewModel() override val viewModel: MainViewModel by viewModel()
override val navHostId: Int = R.id.main_nav_host private val navHostId: Int = R.id.main_nav_host
override val defaultPosition: Int = 0 private val defaultPosition: Int = 0
private val navigationController by lazy {
FragNavController(supportFragmentManager, navHostId)
}
private val isRootFragment get() =
navigationController.currentStackIndex != defaultPosition
override val baseFragments: List<KClass<out Fragment>> = listOf( override val baseFragments: List<KClass<out Fragment>> = listOf(
HomeFragment::class, HomeFragment::class,
...@@ -43,10 +59,6 @@ open class MainActivity : MagiskActivity<MainViewModel, ActivityMainBinding>() { ...@@ -43,10 +59,6 @@ open class MainActivity : MagiskActivity<MainViewModel, ActivityMainBinding>() {
SettingsFragment::class SettingsFragment::class
) )
/*override fun getDarkTheme(): Int {
return R.style.AppTheme_Dark
}*/
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
if (!SplashActivity.DONE) { if (!SplashActivity.DONE) {
startActivity(Intent(this, ClassMap[SplashActivity::class.java])) startActivity(Intent(this, ClassMap[SplashActivity::class.java]))
...@@ -54,6 +66,13 @@ open class MainActivity : MagiskActivity<MainViewModel, ActivityMainBinding>() { ...@@ -54,6 +66,13 @@ open class MainActivity : MagiskActivity<MainViewModel, ActivityMainBinding>() {
} }
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
navigationController.apply {
rootFragmentListener = this@MainActivity
transactionListener = this@MainActivity
initialize(defaultPosition, savedInstanceState)
}
checkHideSection() checkHideSection()
setSupportActionBar(binding.mainInclude.mainToolbar) setSupportActionBar(binding.mainInclude.mainToolbar)
...@@ -68,6 +87,11 @@ open class MainActivity : MagiskActivity<MainViewModel, ActivityMainBinding>() { ...@@ -68,6 +87,11 @@ open class MainActivity : MagiskActivity<MainViewModel, ActivityMainBinding>() {
} }
} }
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
navigationController.onSaveInstanceState(outState)
}
override fun setTitle(title: CharSequence?) { override fun setTitle(title: CharSequence?) {
supportActionBar?.title = title supportActionBar?.title = title
} }
...@@ -76,25 +100,46 @@ open class MainActivity : MagiskActivity<MainViewModel, ActivityMainBinding>() { ...@@ -76,25 +100,46 @@ open class MainActivity : MagiskActivity<MainViewModel, ActivityMainBinding>() {
supportActionBar?.setTitle(titleId) supportActionBar?.setTitle(titleId)
} }
override fun onTabTransaction(fragment: Fragment?, index: Int) {
val fragmentId = when (fragment) {
is HomeFragment -> R.id.magiskFragment
is SuperuserFragment -> R.id.superuserFragment
is MagiskHideFragment -> R.id.magiskHideFragment
is ModulesFragment -> R.id.modulesFragment
is ReposFragment -> R.id.reposFragment
is LogFragment -> R.id.logFragment
is SettingsFragment -> R.id.settings
else -> return
}
binding.navView.setCheckedItem(fragmentId)
}
override fun onBackPressed() { override fun onBackPressed() {
if (binding.drawerLayout.isDrawerOpen(binding.navView)) { if (binding.drawerLayout.isDrawerOpen(binding.navView)) {
binding.drawerLayout.closeDrawer(binding.navView) binding.drawerLayout.closeDrawer(binding.navView)
} else { } else {
super.onBackPressed() val fragment = navigationController.currentFrag as? BaseFragment<*, *>
if (fragment?.onBackPressed() == true) {
return
}
try {
navigationController.popFragment()
} catch (e: UnsupportedOperationException) {
when {
isRootFragment -> {
val options = FragNavTransactionOptions.newBuilder()
.transition(FragmentTransaction.TRANSIT_FRAGMENT_CLOSE)
.build()
navigationController.switchTab(defaultPosition, options)
}
else -> super.onBackPressed()
}
}
}
}
override fun onEventDispatched(event: ViewEvent) {
super.onEventDispatched(event)
when (event) {
is SnackbarEvent -> snackbar(snackbarView, event.message(this), event.length, event.f)
is BackPressEvent -> onBackPressed()
is MagiskNavigationEvent -> navigateTo(event)
is ViewActionEvent -> event.action(this)
is PermissionEvent -> withPermissions(*event.permissions.toTypedArray()) {
onSuccess { event.callback.onNext(true) }
onFailure {
event.callback.onNext(false)
event.callback.onError(SecurityException("User refused permissions"))
}
}
} }
} }
...@@ -120,4 +165,84 @@ open class MainActivity : MagiskActivity<MainViewModel, ActivityMainBinding>() { ...@@ -120,4 +165,84 @@ open class MainActivity : MagiskActivity<MainViewModel, ActivityMainBinding>() {
menu.findItem(R.id.superuserFragment).isVisible = menu.findItem(R.id.superuserFragment).isVisible =
Utils.showSuperUser() Utils.showSuperUser()
} }
private fun FragNavTransactionOptions.Builder.customAnimations(options: MagiskAnimBuilder) =
customAnimations(options.enter, options.exit, options.popEnter, options.popExit).apply {
if (!options.anySet) {
transition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
}
}
override val numberOfRootFragments: Int get() = baseFragments.size
override fun getRootFragment(index: Int) = baseFragments[index].java.newInstance()
override fun onTabTransaction(fragment: Fragment?, index: Int) {
val fragmentId = when (fragment) {
is HomeFragment -> R.id.magiskFragment
is SuperuserFragment -> R.id.superuserFragment
is MagiskHideFragment -> R.id.magiskHideFragment
is ModulesFragment -> R.id.modulesFragment
is ReposFragment -> R.id.reposFragment
is LogFragment -> R.id.logFragment
is SettingsFragment -> R.id.settings
else -> return
}
binding.navView.setCheckedItem(fragmentId)
}
override fun navigateTo(event: MagiskNavigationEvent) {
val directions = event.navDirections
navigationController.defaultTransactionOptions = FragNavTransactionOptions.newBuilder()
.customAnimations(event.animOptions)
.build()
navigationController.currentStack
?.indexOfFirst { it.javaClass == event.navOptions.popUpTo }
?.let { if (it == -1) null else it } // invalidate if class is not found
?.let { if (event.navOptions.inclusive) it + 1 else it }
?.let { navigationController.popFragments(it) }
when (directions.isActivity) {
true -> navigateToActivity(event)
else -> navigateToFragment(event)
}
}
private fun navigateToActivity(event: MagiskNavigationEvent) {
val destination = event.navDirections.destination?.java ?: let {
Timber.e("Cannot navigate to null destination")
return
}
val options = event.navOptions
Intent(this, destination)
.putExtras(event.navDirections.args)
.apply {
if (options.singleTop) addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
if (options.clearTask) addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
}
.let { startActivity(it) }
}
private fun navigateToFragment(event: MagiskNavigationEvent) {
val destination = event.navDirections.destination?.java ?: let {
Timber.e("Cannot navigate to null destination")
return
}
when (val index = baseFragments.indexOfFirst { it.java.name == destination.name }) {
-1 -> destination.newInstance()
.apply { arguments = event.navDirections.args }
.let { navigationController.pushFragment(it) }
// When it's desired that fragments of same class are put on top of one another edit this
else -> navigationController.switchTab(index)
}
}
override fun onFragmentTransaction(
fragment: Fragment?,
transactionType: FragNavController.TransactionType
) = Unit
} }
...@@ -2,11 +2,11 @@ package com.topjohnwu.magisk.ui ...@@ -2,11 +2,11 @@ package com.topjohnwu.magisk.ui
import android.view.MenuItem import android.view.MenuItem
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.base.viewmodel.BaseViewModel
import com.topjohnwu.magisk.model.navigation.Navigation import com.topjohnwu.magisk.model.navigation.Navigation
import com.topjohnwu.magisk.ui.base.MagiskViewModel
class MainViewModel : MagiskViewModel() { class MainViewModel : BaseViewModel() {
fun navPressed() = Navigation.Main.OPEN_NAV.publish() fun navPressed() = Navigation.Main.OPEN_NAV.publish()
......
package com.topjohnwu.magisk.ui package com.topjohnwu.magisk.ui
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.text.TextUtils import android.text.TextUtils
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import com.topjohnwu.magisk.* import com.topjohnwu.magisk.*
import com.topjohnwu.magisk.model.navigation.Navigation import com.topjohnwu.magisk.model.navigation.Navigation
import com.topjohnwu.magisk.utils.Utils import com.topjohnwu.magisk.utils.Utils
import com.topjohnwu.magisk.utils.wrap
import com.topjohnwu.magisk.view.Notifications import com.topjohnwu.magisk.view.Notifications
import com.topjohnwu.magisk.view.Shortcuts import com.topjohnwu.magisk.view.Shortcuts
import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.Shell
open class SplashActivity : AppCompatActivity() { open class SplashActivity : Activity() {
override fun attachBaseContext(base: Context) {
super.attachBaseContext(base.wrap())
}
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
......
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 ...@@ -9,15 +9,21 @@ import androidx.core.net.toUri
import com.topjohnwu.magisk.ClassMap import com.topjohnwu.magisk.ClassMap
import com.topjohnwu.magisk.Const import com.topjohnwu.magisk.Const
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.base.BaseActivity
import com.topjohnwu.magisk.databinding.ActivityFlashBinding import com.topjohnwu.magisk.databinding.ActivityFlashBinding
import com.topjohnwu.magisk.ui.base.MagiskActivity import com.topjohnwu.magisk.extensions.snackbar
import com.topjohnwu.magisk.model.events.BackPressEvent
import com.topjohnwu.magisk.model.events.PermissionEvent
import com.topjohnwu.magisk.model.events.SnackbarEvent
import com.topjohnwu.magisk.model.events.ViewEvent
import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koin.core.parameter.parametersOf import org.koin.core.parameter.parametersOf
import java.io.File import java.io.File
open class FlashActivity : MagiskActivity<FlashViewModel, ActivityFlashBinding>() { open class FlashActivity : BaseActivity<FlashViewModel, ActivityFlashBinding>() {
override val layoutRes: Int = R.layout.activity_flash override val layoutRes: Int = R.layout.activity_flash
override val themeRes: Int = R.style.MagiskTheme_Flashing
override val viewModel: FlashViewModel by viewModel { override val viewModel: FlashViewModel by viewModel {
val uri = intent.data ?: let { finish(); Uri.EMPTY } val uri = intent.data ?: let { finish(); Uri.EMPTY }
val additionalUri = intent.getParcelableExtra(Const.Key.FLASH_DATA) ?: uri val additionalUri = intent.getParcelableExtra(Const.Key.FLASH_DATA) ?: uri
...@@ -37,6 +43,21 @@ open class FlashActivity : MagiskActivity<FlashViewModel, ActivityFlashBinding>( ...@@ -37,6 +43,21 @@ open class FlashActivity : MagiskActivity<FlashViewModel, ActivityFlashBinding>(
super.onBackPressed() super.onBackPressed()
} }
override fun onEventDispatched(event: ViewEvent) {
super.onEventDispatched(event)
when (event) {
is SnackbarEvent -> snackbar(snackbarView, event.message(this), event.length, event.f)
is BackPressEvent -> onBackPressed()
is PermissionEvent -> withPermissions(*event.permissions.toTypedArray()) {
onSuccess { event.callback.onNext(true) }
onFailure {
event.callback.onNext(false)
event.callback.onError(SecurityException("User refused permissions"))
}
}
}
}
companion object { companion object {
private fun intent(context: Context) = Intent(context, ClassMap[FlashActivity::class.java]) private fun intent(context: Context) = Intent(context, ClassMap[FlashActivity::class.java])
......
...@@ -7,21 +7,20 @@ import android.net.Uri ...@@ -7,21 +7,20 @@ import android.net.Uri
import android.os.Handler import android.os.Handler
import androidx.core.os.postDelayed import androidx.core.os.postDelayed
import androidx.databinding.ObservableArrayList import androidx.databinding.ObservableArrayList
import com.skoumal.teanity.databinding.ComparableRvItem
import com.skoumal.teanity.extensions.subscribeK
import com.skoumal.teanity.util.DiffObservableList
import com.skoumal.teanity.util.KObservableField
import com.skoumal.teanity.viewevents.SnackbarEvent
import com.topjohnwu.magisk.BR import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.Config import com.topjohnwu.magisk.Config
import com.topjohnwu.magisk.Const import com.topjohnwu.magisk.Const
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.base.viewmodel.BaseViewModel
import com.topjohnwu.magisk.databinding.ComparableRvItem
import com.topjohnwu.magisk.extensions.* import com.topjohnwu.magisk.extensions.*
import com.topjohnwu.magisk.model.entity.recycler.ConsoleRvItem import com.topjohnwu.magisk.model.entity.recycler.ConsoleRvItem
import com.topjohnwu.magisk.model.events.SnackbarEvent
import com.topjohnwu.magisk.model.flash.FlashResultListener import com.topjohnwu.magisk.model.flash.FlashResultListener
import com.topjohnwu.magisk.model.flash.Flashing import com.topjohnwu.magisk.model.flash.Flashing
import com.topjohnwu.magisk.model.flash.Patching import com.topjohnwu.magisk.model.flash.Patching
import com.topjohnwu.magisk.ui.base.MagiskViewModel import com.topjohnwu.magisk.utils.DiffObservableList
import com.topjohnwu.magisk.utils.KObservableField
import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.Shell
import me.tatarka.bindingcollectionadapter2.ItemBinding import me.tatarka.bindingcollectionadapter2.ItemBinding
import java.io.File import java.io.File
...@@ -32,7 +31,7 @@ class FlashViewModel( ...@@ -32,7 +31,7 @@ class FlashViewModel(
installer: Uri, installer: Uri,
uri: Uri, uri: Uri,
private val resources: Resources private val resources: Resources
) : MagiskViewModel(), FlashResultListener { ) : BaseViewModel(), FlashResultListener {
val canShowReboot = Shell.rootAccess() val canShowReboot = Shell.rootAccess()
val showRestartTitle = KObservableField(false) val showRestartTitle = KObservableField(false)
......
package com.topjohnwu.magisk.ui.hide package com.topjohnwu.magisk.ui.hide
import android.content.pm.ApplicationInfo import android.content.pm.ApplicationInfo
import com.skoumal.teanity.databinding.ComparableRvItem
import com.skoumal.teanity.extensions.addOnPropertyChangedCallback
import com.skoumal.teanity.extensions.subscribeK
import com.skoumal.teanity.rxbus.RxBus
import com.skoumal.teanity.util.DiffObservableList
import com.skoumal.teanity.util.KObservableField
import com.topjohnwu.magisk.BR import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.base.viewmodel.BaseViewModel
import com.topjohnwu.magisk.data.repository.MagiskRepository import com.topjohnwu.magisk.data.repository.MagiskRepository
import com.topjohnwu.magisk.databinding.ComparableRvItem
import com.topjohnwu.magisk.extensions.addOnPropertyChangedCallback
import com.topjohnwu.magisk.extensions.subscribeK
import com.topjohnwu.magisk.extensions.toSingle import com.topjohnwu.magisk.extensions.toSingle
import com.topjohnwu.magisk.extensions.update import com.topjohnwu.magisk.extensions.update
import com.topjohnwu.magisk.model.entity.recycler.HideProcessRvItem import com.topjohnwu.magisk.model.entity.recycler.HideProcessRvItem
import com.topjohnwu.magisk.model.entity.recycler.HideRvItem import com.topjohnwu.magisk.model.entity.recycler.HideRvItem
import com.topjohnwu.magisk.model.entity.state.IndeterminateState import com.topjohnwu.magisk.model.entity.state.IndeterminateState
import com.topjohnwu.magisk.model.events.HideProcessEvent import com.topjohnwu.magisk.model.events.HideProcessEvent
import com.topjohnwu.magisk.ui.base.MagiskViewModel import com.topjohnwu.magisk.utils.DiffObservableList
import com.topjohnwu.magisk.utils.KObservableField
import com.topjohnwu.magisk.utils.RxBus
import me.tatarka.bindingcollectionadapter2.OnItemBind import me.tatarka.bindingcollectionadapter2.OnItemBind
import timber.log.Timber import timber.log.Timber
class HideViewModel( class HideViewModel(
private val magiskRepo: MagiskRepository, private val magiskRepo: MagiskRepository,
rxBus: RxBus rxBus: RxBus
) : MagiskViewModel() { ) : BaseViewModel() {
val query = KObservableField("") val query = KObservableField("")
val isShowSystem = KObservableField(false) val isShowSystem = KObservableField(false)
......
...@@ -6,11 +6,11 @@ import android.view.MenuItem ...@@ -6,11 +6,11 @@ import android.view.MenuItem
import android.widget.SearchView import android.widget.SearchView
import com.topjohnwu.magisk.Config import com.topjohnwu.magisk.Config
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.base.BaseFragment
import com.topjohnwu.magisk.databinding.FragmentMagiskHideBinding import com.topjohnwu.magisk.databinding.FragmentMagiskHideBinding
import com.topjohnwu.magisk.ui.base.MagiskFragment
import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.androidx.viewmodel.ext.android.viewModel
class MagiskHideFragment : MagiskFragment<HideViewModel, FragmentMagiskHideBinding>(), class MagiskHideFragment : BaseFragment<HideViewModel, FragmentMagiskHideBinding>(),
SearchView.OnQueryTextListener { SearchView.OnQueryTextListener {
override val layoutRes: Int = R.layout.fragment_magisk_hide override val layoutRes: Int = R.layout.fragment_magisk_hide
......
package com.topjohnwu.magisk.ui.home package com.topjohnwu.magisk.ui.home
import android.content.Context import android.content.Context
import com.skoumal.teanity.extensions.subscribeK
import com.skoumal.teanity.viewevents.ViewEvent
import com.topjohnwu.magisk.BuildConfig import com.topjohnwu.magisk.BuildConfig
import com.topjohnwu.magisk.Const import com.topjohnwu.magisk.Const
import com.topjohnwu.magisk.Info import com.topjohnwu.magisk.Info
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.base.BaseActivity
import com.topjohnwu.magisk.base.BaseFragment
import com.topjohnwu.magisk.data.repository.MagiskRepository import com.topjohnwu.magisk.data.repository.MagiskRepository
import com.topjohnwu.magisk.databinding.FragmentMagiskBinding import com.topjohnwu.magisk.databinding.FragmentMagiskBinding
import com.topjohnwu.magisk.extensions.inject import com.topjohnwu.magisk.extensions.DynamicClassLoader
import com.topjohnwu.magisk.extensions.openUrl
import com.topjohnwu.magisk.extensions.subscribeK
import com.topjohnwu.magisk.extensions.writeTo import com.topjohnwu.magisk.extensions.writeTo
import com.topjohnwu.magisk.model.events.* import com.topjohnwu.magisk.model.events.*
import com.topjohnwu.magisk.ui.base.MagiskActivity
import com.topjohnwu.magisk.ui.base.MagiskFragment
import com.topjohnwu.magisk.utils.DynamicClassLoader
import com.topjohnwu.magisk.utils.SafetyNetHelper import com.topjohnwu.magisk.utils.SafetyNetHelper
import com.topjohnwu.magisk.view.MarkDownWindow import com.topjohnwu.magisk.view.MarkDownWindow
import com.topjohnwu.magisk.view.dialogs.* import com.topjohnwu.magisk.view.dialogs.*
import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.Shell
import dalvik.system.DexFile import dalvik.system.DexFile
import io.reactivex.Completable import io.reactivex.Completable
import org.koin.android.ext.android.inject
import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.androidx.viewmodel.ext.android.viewModel
import java.io.File import java.io.File
import java.lang.reflect.InvocationHandler import java.lang.reflect.InvocationHandler
class HomeFragment : MagiskFragment<HomeViewModel, FragmentMagiskBinding>(), class HomeFragment : BaseFragment<HomeViewModel, FragmentMagiskBinding>(),
SafetyNetHelper.Callback { SafetyNetHelper.Callback {
override val layoutRes: Int = R.layout.fragment_magisk override val layoutRes: Int = R.layout.fragment_magisk
...@@ -33,13 +33,14 @@ class HomeFragment : MagiskFragment<HomeViewModel, FragmentMagiskBinding>(), ...@@ -33,13 +33,14 @@ class HomeFragment : MagiskFragment<HomeViewModel, FragmentMagiskBinding>(),
private val magiskRepo: MagiskRepository by inject() private val magiskRepo: MagiskRepository by inject()
private val EXT_APK by lazy { File("${activity.filesDir.parent}/snet", "snet.jar") } private val EXT_APK by lazy { File("${activity.filesDir.parent}/snet", "snet.jar") }
private val EXT_DEX by lazy { File(EXT_APK.parent, "snet.dex") }
override fun onResponse(responseCode: Int) = viewModel.finishSafetyNetCheck(responseCode) override fun onResponse(responseCode: Int) = viewModel.finishSafetyNetCheck(responseCode)
override fun onEventDispatched(event: ViewEvent) { override fun onEventDispatched(event: ViewEvent) {
super.onEventDispatched(event) super.onEventDispatched(event)
when (event) { when (event) {
is OpenLinkEvent -> openLink(event.url) is OpenLinkEvent -> activity.openUrl(event.url)
is ManagerInstallEvent -> installManager() is ManagerInstallEvent -> installManager()
is MagiskInstallEvent -> installMagisk() is MagiskInstallEvent -> installMagisk()
is UninstallEvent -> uninstall() is UninstallEvent -> uninstall()
...@@ -62,7 +63,7 @@ class HomeFragment : MagiskFragment<HomeViewModel, FragmentMagiskBinding>(), ...@@ -62,7 +63,7 @@ class HomeFragment : MagiskFragment<HomeViewModel, FragmentMagiskBinding>(),
return return
} }
MagiskInstallDialog(requireActivity() as MagiskActivity<*, *>).show() MagiskInstallDialog(requireActivity() as BaseActivity<*, *>).show()
} }
private fun installManager() = ManagerInstallDialog(requireActivity()).show() private fun installManager() = ManagerInstallDialog(requireActivity()).show()
...@@ -94,7 +95,7 @@ class HomeFragment : MagiskFragment<HomeViewModel, FragmentMagiskBinding>(), ...@@ -94,7 +95,7 @@ class HomeFragment : MagiskFragment<HomeViewModel, FragmentMagiskBinding>(),
private fun updateSafetyNet(dieOnError: Boolean) { private fun updateSafetyNet(dieOnError: Boolean) {
Completable.fromAction { Completable.fromAction {
val loader = DynamicClassLoader(EXT_APK) val loader = DynamicClassLoader(EXT_APK)
val dex = DexFile.loadDex(EXT_APK.path, EXT_APK.parent, 0) val dex = DexFile.loadDex(EXT_APK.path, EXT_DEX.path, 0)
// Scan through the dex and find our helper class // Scan through the dex and find our helper class
var helperClass: Class<*>? = null var helperClass: Class<*>? = null
......
package com.topjohnwu.magisk.ui.home package com.topjohnwu.magisk.ui.home
import android.content.pm.PackageManager import android.content.pm.PackageManager
import com.skoumal.teanity.extensions.addOnPropertyChangedCallback
import com.skoumal.teanity.extensions.doOnSubscribeUi
import com.skoumal.teanity.extensions.subscribeK
import com.skoumal.teanity.util.KObservableField
import com.topjohnwu.magisk.* import com.topjohnwu.magisk.*
import com.topjohnwu.magisk.base.viewmodel.BaseViewModel
import com.topjohnwu.magisk.data.repository.MagiskRepository import com.topjohnwu.magisk.data.repository.MagiskRepository
import com.topjohnwu.magisk.extensions.get import com.topjohnwu.magisk.extensions.*
import com.topjohnwu.magisk.extensions.packageName
import com.topjohnwu.magisk.extensions.res
import com.topjohnwu.magisk.extensions.toggle
import com.topjohnwu.magisk.model.events.* import com.topjohnwu.magisk.model.events.*
import com.topjohnwu.magisk.model.observer.Observer import com.topjohnwu.magisk.model.observer.Observer
import com.topjohnwu.magisk.ui.base.MagiskViewModel import com.topjohnwu.magisk.utils.KObservableField
import com.topjohnwu.magisk.utils.SafetyNetHelper import com.topjohnwu.magisk.utils.SafetyNetHelper
import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.Shell
import io.reactivex.Completable
enum class SafetyNetState { enum class SafetyNetState {
LOADING, PASS, FAILED, IDLE LOADING, PASS, FAILED, IDLE
...@@ -31,7 +26,7 @@ enum class MagiskItem { ...@@ -31,7 +26,7 @@ enum class MagiskItem {
class HomeViewModel( class HomeViewModel(
private val magiskRepo: MagiskRepository private val magiskRepo: MagiskRepository
) : MagiskViewModel(State.LOADED) { ) : BaseViewModel(State.LOADED) {
val hasGMS = runCatching { val hasGMS = runCatching {
get<PackageManager>().getPackageInfo("com.google.android.gms", 0); true get<PackageManager>().getPackageInfo("com.google.android.gms", 0); true
...@@ -43,10 +38,7 @@ class HomeViewModel( ...@@ -43,10 +38,7 @@ class HomeViewModel(
val isKeepVerity = KObservableField(Info.keepVerity) val isKeepVerity = KObservableField(Info.keepVerity)
val isRecovery = KObservableField(Info.recovery) val isRecovery = KObservableField(Info.recovery)
private val _magiskState = KObservableField(MagiskState.LOADING) val magiskState = KObservableField(MagiskState.LOADING)
val magiskState = Observer(_magiskState, isConnected) {
if (isConnected.value) _magiskState.value else MagiskState.UP_TO_DATE
}
val magiskStateText = Observer(magiskState) { val magiskStateText = Observer(magiskState) {
when (magiskState.value) { when (magiskState.value) {
MagiskState.NOT_INSTALLED -> R.string.magisk_version_error.res() MagiskState.NOT_INSTALLED -> R.string.magisk_version_error.res()
...@@ -179,24 +171,28 @@ class HomeViewModel( ...@@ -179,24 +171,28 @@ class HomeViewModel(
} }
fun refresh() { 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) .applyViewModel(this)
.doOnSubscribeUi { .doOnSubscribeUi {
_magiskState.value = MagiskState.LOADING magiskState.value = MagiskState.LOADING
_managerState.value = MagiskState.LOADING _managerState.value = MagiskState.LOADING
ctsState.value = SafetyNetState.IDLE ctsState.value = SafetyNetState.IDLE
basicIntegrityState.value = SafetyNetState.IDLE basicIntegrityState.value = SafetyNetState.IDLE
safetyNetTitle.value = R.string.safetyNet_check_text safetyNetTitle.value = R.string.safetyNet_check_text
} }.subscribeK {
.subscribeK {
updateSelf() updateSelf()
ensureEnv() ensureEnv()
refreshVersions() refreshVersions()
} }
hasRoot.value = Shell.rootAccess()
} }
private fun refreshVersions() { private fun refreshVersions() {
...@@ -211,7 +207,7 @@ class HomeViewModel( ...@@ -211,7 +207,7 @@ class HomeViewModel(
} }
private fun updateSelf() { private fun updateSelf() {
_magiskState.value = when (Info.magiskVersionCode) { magiskState.value = when (Info.magiskVersionCode) {
in Int.MIN_VALUE until 0 -> MagiskState.NOT_INSTALLED in Int.MIN_VALUE until 0 -> MagiskState.NOT_INSTALLED
!in Info.remote.magisk.versionCode..Int.MAX_VALUE -> MagiskState.OBSOLETE !in Info.remote.magisk.versionCode..Int.MAX_VALUE -> MagiskState.OBSOLETE
else -> MagiskState.UP_TO_DATE else -> MagiskState.UP_TO_DATE
......
...@@ -6,14 +6,14 @@ import android.view.Menu ...@@ -6,14 +6,14 @@ import android.view.Menu
import android.view.MenuInflater import android.view.MenuInflater
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import com.skoumal.teanity.viewevents.ViewEvent
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.base.BaseFragment
import com.topjohnwu.magisk.databinding.FragmentLogBinding import com.topjohnwu.magisk.databinding.FragmentLogBinding
import com.topjohnwu.magisk.model.events.PageChangedEvent import com.topjohnwu.magisk.model.events.PageChangedEvent
import com.topjohnwu.magisk.ui.base.MagiskFragment import com.topjohnwu.magisk.model.events.ViewEvent
import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.androidx.viewmodel.ext.android.viewModel
class LogFragment : MagiskFragment<LogViewModel, FragmentLogBinding>() { class LogFragment : BaseFragment<LogViewModel, FragmentLogBinding>() {
override val layoutRes: Int = R.layout.fragment_log override val layoutRes: Int = R.layout.fragment_log
override val viewModel: LogViewModel by viewModel() override val viewModel: LogViewModel by viewModel()
......
package com.topjohnwu.magisk.ui.log package com.topjohnwu.magisk.ui.log
import android.content.res.Resources import android.content.res.Resources
import com.skoumal.teanity.databinding.ComparableRvItem
import com.skoumal.teanity.extensions.addOnPropertyChangedCallback
import com.skoumal.teanity.extensions.doOnSubscribeUi
import com.skoumal.teanity.extensions.subscribeK
import com.skoumal.teanity.util.DiffObservableList
import com.skoumal.teanity.util.KObservableField
import com.skoumal.teanity.viewevents.SnackbarEvent
import com.topjohnwu.magisk.BR import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.Config import com.topjohnwu.magisk.Config
import com.topjohnwu.magisk.Const import com.topjohnwu.magisk.Const
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.base.viewmodel.BaseViewModel
import com.topjohnwu.magisk.data.repository.LogRepository import com.topjohnwu.magisk.data.repository.LogRepository
import com.topjohnwu.magisk.databinding.ComparableRvItem
import com.topjohnwu.magisk.extensions.addOnPropertyChangedCallback
import com.topjohnwu.magisk.extensions.doOnSubscribeUi
import com.topjohnwu.magisk.extensions.subscribeK
import com.topjohnwu.magisk.model.binding.BindingAdapter import com.topjohnwu.magisk.model.binding.BindingAdapter
import com.topjohnwu.magisk.model.entity.recycler.ConsoleRvItem import com.topjohnwu.magisk.model.entity.recycler.ConsoleRvItem
import com.topjohnwu.magisk.model.entity.recycler.LogItemRvItem import com.topjohnwu.magisk.model.entity.recycler.LogItemRvItem
import com.topjohnwu.magisk.model.entity.recycler.LogRvItem import com.topjohnwu.magisk.model.entity.recycler.LogRvItem
import com.topjohnwu.magisk.model.entity.recycler.MagiskLogRvItem import com.topjohnwu.magisk.model.entity.recycler.MagiskLogRvItem
import com.topjohnwu.magisk.model.events.PageChangedEvent import com.topjohnwu.magisk.model.events.PageChangedEvent
import com.topjohnwu.magisk.ui.base.MagiskViewModel import com.topjohnwu.magisk.model.events.SnackbarEvent
import com.topjohnwu.magisk.utils.DiffObservableList
import com.topjohnwu.magisk.utils.KObservableField
import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.Shell
import me.tatarka.bindingcollectionadapter2.BindingViewPagerAdapter import me.tatarka.bindingcollectionadapter2.BindingViewPagerAdapter
import me.tatarka.bindingcollectionadapter2.OnItemBind import me.tatarka.bindingcollectionadapter2.OnItemBind
...@@ -30,7 +30,7 @@ import java.util.* ...@@ -30,7 +30,7 @@ import java.util.*
class LogViewModel( class LogViewModel(
private val resources: Resources, private val resources: Resources,
private val logRepo: LogRepository private val logRepo: LogRepository
) : MagiskViewModel(), BindingViewPagerAdapter.PageTitles<ComparableRvItem<*>> { ) : BaseViewModel(), BindingViewPagerAdapter.PageTitles<ComparableRvItem<*>> {
val itemsAdapter = BindingAdapter() val itemsAdapter = BindingAdapter()
val items = DiffObservableList(ComparableRvItem.callback) val items = DiffObservableList(ComparableRvItem.callback)
......
package com.topjohnwu.magisk.ui.module package com.topjohnwu.magisk.ui.module
import android.content.res.Resources import android.content.res.Resources
import com.skoumal.teanity.databinding.ComparableRvItem
import com.skoumal.teanity.extensions.addOnPropertyChangedCallback
import com.skoumal.teanity.extensions.doOnSuccessUi
import com.skoumal.teanity.extensions.subscribeK
import com.skoumal.teanity.util.DiffObservableList
import com.skoumal.teanity.util.KObservableField
import com.topjohnwu.magisk.BR import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.base.viewmodel.BaseViewModel
import com.topjohnwu.magisk.data.database.RepoDao import com.topjohnwu.magisk.data.database.RepoDao
import com.topjohnwu.magisk.extensions.toSingle import com.topjohnwu.magisk.databinding.ComparableRvItem
import com.topjohnwu.magisk.extensions.update import com.topjohnwu.magisk.extensions.*
import com.topjohnwu.magisk.model.entity.module.Module import com.topjohnwu.magisk.model.entity.module.Module
import com.topjohnwu.magisk.model.entity.recycler.ModuleRvItem import com.topjohnwu.magisk.model.entity.recycler.ModuleRvItem
import com.topjohnwu.magisk.model.entity.recycler.RepoRvItem import com.topjohnwu.magisk.model.entity.recycler.RepoRvItem
...@@ -20,7 +15,8 @@ import com.topjohnwu.magisk.model.events.InstallModuleEvent ...@@ -20,7 +15,8 @@ import com.topjohnwu.magisk.model.events.InstallModuleEvent
import com.topjohnwu.magisk.model.events.OpenChangelogEvent import com.topjohnwu.magisk.model.events.OpenChangelogEvent
import com.topjohnwu.magisk.model.events.OpenFilePickerEvent import com.topjohnwu.magisk.model.events.OpenFilePickerEvent
import com.topjohnwu.magisk.tasks.RepoUpdater import com.topjohnwu.magisk.tasks.RepoUpdater
import com.topjohnwu.magisk.ui.base.MagiskViewModel import com.topjohnwu.magisk.utils.DiffObservableList
import com.topjohnwu.magisk.utils.KObservableField
import io.reactivex.Single import io.reactivex.Single
import io.reactivex.disposables.Disposable import io.reactivex.disposables.Disposable
import me.tatarka.bindingcollectionadapter2.OnItemBind import me.tatarka.bindingcollectionadapter2.OnItemBind
...@@ -29,7 +25,7 @@ class ModuleViewModel( ...@@ -29,7 +25,7 @@ class ModuleViewModel(
private val resources: Resources, private val resources: Resources,
private val repoUpdater: RepoUpdater, private val repoUpdater: RepoUpdater,
private val repoDB: RepoDao private val repoDB: RepoDao
) : MagiskViewModel() { ) : BaseViewModel() {
val query = KObservableField("") val query = KObservableField("")
......
...@@ -8,19 +8,19 @@ import android.view.MenuInflater ...@@ -8,19 +8,19 @@ import android.view.MenuInflater
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.skoumal.teanity.viewevents.ViewEvent
import com.topjohnwu.magisk.ClassMap import com.topjohnwu.magisk.ClassMap
import com.topjohnwu.magisk.Const import com.topjohnwu.magisk.Const
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.base.BaseFragment
import com.topjohnwu.magisk.databinding.FragmentModulesBinding import com.topjohnwu.magisk.databinding.FragmentModulesBinding
import com.topjohnwu.magisk.extensions.reboot import com.topjohnwu.magisk.extensions.reboot
import com.topjohnwu.magisk.model.events.OpenFilePickerEvent import com.topjohnwu.magisk.model.events.OpenFilePickerEvent
import com.topjohnwu.magisk.ui.base.MagiskFragment import com.topjohnwu.magisk.model.events.ViewEvent
import com.topjohnwu.magisk.ui.flash.FlashActivity import com.topjohnwu.magisk.ui.flash.FlashActivity
import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.Shell
import org.koin.androidx.viewmodel.ext.android.sharedViewModel import org.koin.androidx.viewmodel.ext.android.sharedViewModel
class ModulesFragment : MagiskFragment<ModuleViewModel, FragmentModulesBinding>() { class ModulesFragment : BaseFragment<ModuleViewModel, FragmentModulesBinding>() {
override val layoutRes: Int = R.layout.fragment_modules override val layoutRes: Int = R.layout.fragment_modules
override val viewModel: ModuleViewModel by sharedViewModel() override val viewModel: ModuleViewModel by sharedViewModel()
......
...@@ -6,9 +6,9 @@ import android.view.Menu ...@@ -6,9 +6,9 @@ import android.view.Menu
import android.view.MenuInflater import android.view.MenuInflater
import android.view.MenuItem import android.view.MenuItem
import android.widget.SearchView import android.widget.SearchView
import com.skoumal.teanity.viewevents.ViewEvent
import com.topjohnwu.magisk.Config import com.topjohnwu.magisk.Config
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.base.BaseFragment
import com.topjohnwu.magisk.databinding.FragmentReposBinding import com.topjohnwu.magisk.databinding.FragmentReposBinding
import com.topjohnwu.magisk.model.download.DownloadService import com.topjohnwu.magisk.model.download.DownloadService
import com.topjohnwu.magisk.model.entity.internal.Configuration import com.topjohnwu.magisk.model.entity.internal.Configuration
...@@ -16,12 +16,12 @@ import com.topjohnwu.magisk.model.entity.internal.DownloadSubject ...@@ -16,12 +16,12 @@ import com.topjohnwu.magisk.model.entity.internal.DownloadSubject
import com.topjohnwu.magisk.model.entity.module.Repo import com.topjohnwu.magisk.model.entity.module.Repo
import com.topjohnwu.magisk.model.events.InstallModuleEvent import com.topjohnwu.magisk.model.events.InstallModuleEvent
import com.topjohnwu.magisk.model.events.OpenChangelogEvent import com.topjohnwu.magisk.model.events.OpenChangelogEvent
import com.topjohnwu.magisk.ui.base.MagiskFragment import com.topjohnwu.magisk.model.events.ViewEvent
import com.topjohnwu.magisk.view.MarkDownWindow import com.topjohnwu.magisk.view.MarkDownWindow
import com.topjohnwu.magisk.view.dialogs.CustomAlertDialog import com.topjohnwu.magisk.view.dialogs.CustomAlertDialog
import org.koin.androidx.viewmodel.ext.android.sharedViewModel import org.koin.androidx.viewmodel.ext.android.sharedViewModel
class ReposFragment : MagiskFragment<ModuleViewModel, FragmentReposBinding>(), class ReposFragment : BaseFragment<ModuleViewModel, FragmentReposBinding>(),
SearchView.OnQueryTextListener { SearchView.OnQueryTextListener {
override val layoutRes: Int = R.layout.fragment_repos override val layoutRes: Int = R.layout.fragment_repos
......
...@@ -13,21 +13,20 @@ import androidx.preference.ListPreference ...@@ -13,21 +13,20 @@ import androidx.preference.ListPreference
import androidx.preference.Preference import androidx.preference.Preference
import androidx.preference.PreferenceCategory import androidx.preference.PreferenceCategory
import androidx.preference.SwitchPreferenceCompat import androidx.preference.SwitchPreferenceCompat
import com.skoumal.teanity.extensions.subscribeK
import com.skoumal.teanity.util.KObservableField
import com.topjohnwu.magisk.BuildConfig import com.topjohnwu.magisk.BuildConfig
import com.topjohnwu.magisk.Config import com.topjohnwu.magisk.Config
import com.topjohnwu.magisk.Const import com.topjohnwu.magisk.Const
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.base.BasePreferenceFragment
import com.topjohnwu.magisk.data.database.RepoDao import com.topjohnwu.magisk.data.database.RepoDao
import com.topjohnwu.magisk.databinding.CustomDownloadDialogBinding import com.topjohnwu.magisk.databinding.CustomDownloadDialogBinding
import com.topjohnwu.magisk.extensions.subscribeK
import com.topjohnwu.magisk.extensions.toLangTag import com.topjohnwu.magisk.extensions.toLangTag
import com.topjohnwu.magisk.model.download.DownloadService import com.topjohnwu.magisk.model.download.DownloadService
import com.topjohnwu.magisk.model.entity.internal.Configuration import com.topjohnwu.magisk.model.entity.internal.Configuration
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject import com.topjohnwu.magisk.model.entity.internal.DownloadSubject
import com.topjohnwu.magisk.model.observer.Observer import com.topjohnwu.magisk.model.observer.Observer
import com.topjohnwu.magisk.net.Networking import com.topjohnwu.magisk.net.Networking
import com.topjohnwu.magisk.ui.base.BasePreferenceFragment
import com.topjohnwu.magisk.utils.* import com.topjohnwu.magisk.utils.*
import com.topjohnwu.magisk.view.dialogs.FingerprintAuthDialog import com.topjohnwu.magisk.view.dialogs.FingerprintAuthDialog
import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.Shell
...@@ -204,7 +203,7 @@ class SettingsFragment : BasePreferenceFragment() { ...@@ -204,7 +203,7 @@ class SettingsFragment : BasePreferenceFragment() {
Shell.su("magiskhide --disable").submit() Shell.su("magiskhide --disable").submit()
} }
Config.Key.LOCALE -> { Config.Key.LOCALE -> {
LocaleManager.setLocale(activity.application) ResourceMgr.reload()
activity.recreate() activity.recreate()
} }
Config.Key.CHECK_UPDATES -> Utils.scheduleUpdateCheck(activity) Config.Key.CHECK_UPDATES -> Utils.scheduleUpdateCheck(activity)
...@@ -233,7 +232,7 @@ class SettingsFragment : BasePreferenceFragment() { ...@@ -233,7 +232,7 @@ class SettingsFragment : BasePreferenceFragment() {
val values = mutableListOf<String>() val values = mutableListOf<String>()
names.add( names.add(
LocaleManager.getString(defaultLocale, R.string.system_default) ResourceMgr.getString(defaultLocale, R.string.system_default)
) )
values.add("") values.add("")
......
package com.topjohnwu.magisk.ui.superuser package com.topjohnwu.magisk.ui.superuser
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.base.BaseFragment
import com.topjohnwu.magisk.databinding.FragmentSuperuserBinding import com.topjohnwu.magisk.databinding.FragmentSuperuserBinding
import com.topjohnwu.magisk.ui.base.MagiskFragment
import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.androidx.viewmodel.ext.android.viewModel
class SuperuserFragment : class SuperuserFragment :
MagiskFragment<SuperuserViewModel, FragmentSuperuserBinding>() { BaseFragment<SuperuserViewModel, FragmentSuperuserBinding>() {
override val layoutRes: Int = R.layout.fragment_superuser override val layoutRes: Int = R.layout.fragment_superuser
override val viewModel: SuperuserViewModel by viewModel() override val viewModel: SuperuserViewModel by viewModel()
......
...@@ -2,22 +2,22 @@ package com.topjohnwu.magisk.ui.superuser ...@@ -2,22 +2,22 @@ package com.topjohnwu.magisk.ui.superuser
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.content.res.Resources import android.content.res.Resources
import com.skoumal.teanity.databinding.ComparableRvItem
import com.skoumal.teanity.extensions.applySchedulers
import com.skoumal.teanity.extensions.subscribeK
import com.skoumal.teanity.rxbus.RxBus
import com.skoumal.teanity.util.DiffObservableList
import com.skoumal.teanity.viewevents.SnackbarEvent
import com.topjohnwu.magisk.BR import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.base.viewmodel.BaseViewModel
import com.topjohnwu.magisk.data.database.PolicyDao import com.topjohnwu.magisk.data.database.PolicyDao
import com.topjohnwu.magisk.databinding.ComparableRvItem
import com.topjohnwu.magisk.extensions.applySchedulers
import com.topjohnwu.magisk.extensions.subscribeK
import com.topjohnwu.magisk.extensions.toggle import com.topjohnwu.magisk.extensions.toggle
import com.topjohnwu.magisk.model.entity.MagiskPolicy import com.topjohnwu.magisk.model.entity.MagiskPolicy
import com.topjohnwu.magisk.model.entity.recycler.PolicyRvItem import com.topjohnwu.magisk.model.entity.recycler.PolicyRvItem
import com.topjohnwu.magisk.model.events.PolicyEnableEvent import com.topjohnwu.magisk.model.events.PolicyEnableEvent
import com.topjohnwu.magisk.model.events.PolicyUpdateEvent import com.topjohnwu.magisk.model.events.PolicyUpdateEvent
import com.topjohnwu.magisk.ui.base.MagiskViewModel import com.topjohnwu.magisk.model.events.SnackbarEvent
import com.topjohnwu.magisk.utils.DiffObservableList
import com.topjohnwu.magisk.utils.FingerprintHelper import com.topjohnwu.magisk.utils.FingerprintHelper
import com.topjohnwu.magisk.utils.RxBus
import com.topjohnwu.magisk.view.dialogs.CustomAlertDialog import com.topjohnwu.magisk.view.dialogs.CustomAlertDialog
import com.topjohnwu.magisk.view.dialogs.FingerprintAuthDialog import com.topjohnwu.magisk.view.dialogs.FingerprintAuthDialog
import io.reactivex.Single import io.reactivex.Single
...@@ -29,7 +29,7 @@ class SuperuserViewModel( ...@@ -29,7 +29,7 @@ class SuperuserViewModel(
private val packageManager: PackageManager, private val packageManager: PackageManager,
private val resources: Resources, private val resources: Resources,
rxBus: RxBus rxBus: RxBus
) : MagiskViewModel() { ) : BaseViewModel() {
val items = DiffObservableList(ComparableRvItem.callback) val items = DiffObservableList(ComparableRvItem.callback)
val itemBinding = ItemBinding.of<ComparableRvItem<*>> { itemBinding, _, item -> val itemBinding = ItemBinding.of<ComparableRvItem<*>> { itemBinding, _, item ->
......
...@@ -5,19 +5,20 @@ import android.os.Build ...@@ -5,19 +5,20 @@ import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.text.TextUtils import android.text.TextUtils
import android.view.Window import android.view.Window
import com.skoumal.teanity.viewevents.ViewEvent
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.base.BaseActivity
import com.topjohnwu.magisk.databinding.ActivityRequestBinding import com.topjohnwu.magisk.databinding.ActivityRequestBinding
import com.topjohnwu.magisk.model.entity.MagiskPolicy import com.topjohnwu.magisk.model.entity.MagiskPolicy
import com.topjohnwu.magisk.model.events.DieEvent import com.topjohnwu.magisk.model.events.DieEvent
import com.topjohnwu.magisk.model.events.ViewEvent
import com.topjohnwu.magisk.model.receiver.GeneralReceiver import com.topjohnwu.magisk.model.receiver.GeneralReceiver
import com.topjohnwu.magisk.ui.base.MagiskActivity
import com.topjohnwu.magisk.utils.SuLogger import com.topjohnwu.magisk.utils.SuLogger
import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.androidx.viewmodel.ext.android.viewModel
open class SuRequestActivity : MagiskActivity<SuRequestViewModel, ActivityRequestBinding>() { open class SuRequestActivity : BaseActivity<SuRequestViewModel, ActivityRequestBinding>() {
override val layoutRes: Int = R.layout.activity_request override val layoutRes: Int = R.layout.activity_request
override val themeRes: Int = R.style.MagiskTheme_SU
override val viewModel: SuRequestViewModel by viewModel() override val viewModel: SuRequestViewModel by viewModel()
override fun onBackPressed() { override fun onBackPressed() {
......
...@@ -9,21 +9,21 @@ import android.graphics.drawable.Drawable ...@@ -9,21 +9,21 @@ import android.graphics.drawable.Drawable
import android.hardware.fingerprint.FingerprintManager import android.hardware.fingerprint.FingerprintManager
import android.os.CountDownTimer import android.os.CountDownTimer
import android.text.TextUtils import android.text.TextUtils
import com.skoumal.teanity.databinding.ComparableRvItem
import com.skoumal.teanity.extensions.addOnPropertyChangedCallback
import com.skoumal.teanity.util.DiffObservableList
import com.skoumal.teanity.util.KObservableField
import com.topjohnwu.magisk.BuildConfig import com.topjohnwu.magisk.BuildConfig
import com.topjohnwu.magisk.Config import com.topjohnwu.magisk.Config
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.base.viewmodel.BaseViewModel
import com.topjohnwu.magisk.data.database.PolicyDao import com.topjohnwu.magisk.data.database.PolicyDao
import com.topjohnwu.magisk.databinding.ComparableRvItem
import com.topjohnwu.magisk.extensions.addOnPropertyChangedCallback
import com.topjohnwu.magisk.extensions.now import com.topjohnwu.magisk.extensions.now
import com.topjohnwu.magisk.model.entity.MagiskPolicy import com.topjohnwu.magisk.model.entity.MagiskPolicy
import com.topjohnwu.magisk.model.entity.recycler.SpinnerRvItem import com.topjohnwu.magisk.model.entity.recycler.SpinnerRvItem
import com.topjohnwu.magisk.model.entity.toPolicy import com.topjohnwu.magisk.model.entity.toPolicy
import com.topjohnwu.magisk.model.events.DieEvent import com.topjohnwu.magisk.model.events.DieEvent
import com.topjohnwu.magisk.ui.base.MagiskViewModel import com.topjohnwu.magisk.utils.DiffObservableList
import com.topjohnwu.magisk.utils.FingerprintHelper import com.topjohnwu.magisk.utils.FingerprintHelper
import com.topjohnwu.magisk.utils.KObservableField
import com.topjohnwu.magisk.utils.SuConnector import com.topjohnwu.magisk.utils.SuConnector
import me.tatarka.bindingcollectionadapter2.BindingListViewAdapter import me.tatarka.bindingcollectionadapter2.BindingListViewAdapter
import me.tatarka.bindingcollectionadapter2.ItemBinding import me.tatarka.bindingcollectionadapter2.ItemBinding
...@@ -36,7 +36,7 @@ class SuRequestViewModel( ...@@ -36,7 +36,7 @@ class SuRequestViewModel(
private val policyDB: PolicyDao, private val policyDB: PolicyDao,
private val timeoutPrefs: SharedPreferences, private val timeoutPrefs: SharedPreferences,
private val resources: Resources private val resources: Resources
) : MagiskViewModel() { ) : BaseViewModel() {
val icon = KObservableField<Drawable?>(null) val icon = KObservableField<Drawable?>(null)
val title = KObservableField("") val title = KObservableField("")
......
...@@ -21,9 +21,9 @@ import androidx.recyclerview.widget.RecyclerView ...@@ -21,9 +21,9 @@ import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager.widget.ViewPager import androidx.viewpager.widget.ViewPager
import com.google.android.material.floatingactionbutton.FloatingActionButton import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.google.android.material.navigation.NavigationView import com.google.android.material.navigation.NavigationView
import com.skoumal.teanity.extensions.subscribeK
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.extensions.replaceRandomWithSpecial import com.topjohnwu.magisk.extensions.replaceRandomWithSpecial
import com.topjohnwu.magisk.extensions.subscribeK
import com.topjohnwu.magisk.model.entity.state.IndeterminateState import com.topjohnwu.magisk.model.entity.state.IndeterminateState
import io.reactivex.Observable import io.reactivex.Observable
import io.reactivex.disposables.Disposable import io.reactivex.disposables.Disposable
......
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 ...@@ -3,8 +3,8 @@ package com.topjohnwu.magisk.utils
import android.content.ComponentName import android.content.ComponentName
import android.content.Context import android.content.Context
import android.widget.Toast import android.widget.Toast
import com.skoumal.teanity.extensions.subscribeK
import com.topjohnwu.magisk.* import com.topjohnwu.magisk.*
import com.topjohnwu.magisk.extensions.subscribeK
import com.topjohnwu.magisk.ui.SplashActivity import com.topjohnwu.magisk.ui.SplashActivity
import com.topjohnwu.magisk.view.Notifications import com.topjohnwu.magisk.view.Notifications
import com.topjohnwu.signing.JarMap import com.topjohnwu.signing.JarMap
...@@ -102,7 +102,7 @@ object PatchAPK { ...@@ -102,7 +102,7 @@ object PatchAPK {
Config.suManager = pkg Config.suManager = pkg
Config.export() Config.export()
RootUtils.rmAndLaunch(BuildConfig.APPLICATION_ID, Utils.rmAndLaunch(BuildConfig.APPLICATION_ID,
ComponentName(pkg, ClassMap.get<Class<*>>(SplashActivity::class.java).name)) ComponentName(pkg, ClassMap.get<Class<*>>(SplashActivity::class.java).name))
return true return true
......
@file:Suppress("DEPRECATION")
package com.topjohnwu.magisk.utils package com.topjohnwu.magisk.utils
import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.content.ContextWrapper import android.content.ContextWrapper
import android.content.res.AssetManager
import android.content.res.Configuration import android.content.res.Configuration
import android.content.res.Resources import android.content.res.Resources
import androidx.annotation.StringRes import androidx.annotation.StringRes
import com.topjohnwu.magisk.Config import com.topjohnwu.magisk.Config
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.extensions.get
import com.topjohnwu.magisk.extensions.inject
import com.topjohnwu.magisk.extensions.langTagToLocale import com.topjohnwu.magisk.extensions.langTagToLocale
import com.topjohnwu.superuser.internal.InternalUtils
import io.reactivex.Single import io.reactivex.Single
import java.util.* import java.util.*
var currentLocale = Locale.getDefault()!! var isRunningAsStub = false
var currentLocale: Locale = Locale.getDefault()
private set private set
val defaultLocale = Locale.getDefault()!! @SuppressLint("ConstantLocale")
val defaultLocale: Locale = Locale.getDefault()
val availableLocales = Single.fromCallable { val availableLocales = Single.fromCallable {
val compareId = R.string.app_changelog val compareId = R.string.app_changelog
val res: Resources by inject()
mutableListOf<Locale>().apply { mutableListOf<Locale>().apply {
// Add default locale // Add default locale
add(Locale.ENGLISH) add(Locale.ENGLISH)
...@@ -30,10 +33,18 @@ val availableLocales = Single.fromCallable { ...@@ -30,10 +33,18 @@ val availableLocales = Single.fromCallable {
add(Locale.TAIWAN) add(Locale.TAIWAN)
add(Locale("pt", "BR")) add(Locale("pt", "BR"))
val config = Configuration()
val metrics = ResourceMgr.resource.displayMetrics
val res = Resources(ResourceMgr.resource.assets, metrics, config)
// Other locales // Other locales
val otherLocales = res.assets.locales val otherLocales = ResourceMgr.resource.assets.locales
.map { it.langTagToLocale() } .map { it.langTagToLocale() }
.distinctBy { LocaleManager.getString(it, compareId) } .distinctBy {
config.setLocale(it)
res.updateConfiguration(config, metrics)
res.getString(compareId)
}
listOf("", "").toTypedArray() listOf("", "").toTypedArray()
...@@ -44,25 +55,72 @@ val availableLocales = Single.fromCallable { ...@@ -44,25 +55,72 @@ val availableLocales = Single.fromCallable {
}) })
}.cache()!! }.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 val localeConfig = Config.locale
currentLocale = when { currentLocale = when {
localeConfig.isEmpty() -> defaultLocale localeConfig.isEmpty() -> defaultLocale
else -> localeConfig.langTagToLocale() else -> localeConfig.langTagToLocale()
} }
Locale.setDefault(currentLocale) Locale.setDefault(currentLocale)
InternalUtils.replaceBaseContext(wrapper, getLocaleContext(wrapper, currentLocale)) resource.patch(config)
} }
fun getLocaleContext(context: Context, locale: Locale = currentLocale): Context { fun getString(locale: Locale, @StringRes id: Int): String {
val config = Configuration(context.resources.configuration) val config = Configuration()
config.setLocale(locale) config.setLocale(locale)
return context.createConfigurationContext(config) return Resources(resource.assets, resource.displayMetrics, config).getString(id)
} }
fun getString(locale: Locale, @StringRes id: Int): String { open class GlobalResContext(base: Context) : ContextWrapper(base) {
return getLocaleContext(get(), locale).getString(id) 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 package com.topjohnwu.magisk.utils
import android.content.ComponentName
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.res.Resources import android.content.res.Resources
...@@ -72,4 +73,8 @@ object Utils { ...@@ -72,4 +73,8 @@ object Utils {
if ((exists() && isDirectory) || mkdirs()) this else null if ((exists() && isDirectory) || mkdirs()) this else null
} }
fun rmAndLaunch(rm: String, component: ComponentName) {
Shell.su("(rm_launch $rm ${component.flattenToString()})").exec()
}
} }
...@@ -4,23 +4,22 @@ import android.content.Context ...@@ -4,23 +4,22 @@ import android.content.Context
import android.view.LayoutInflater import android.view.LayoutInflater
import android.widget.TextView import android.widget.TextView
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import com.skoumal.teanity.extensions.subscribeK
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.data.repository.StringRepository import com.topjohnwu.magisk.data.repository.StringRepository
import com.topjohnwu.magisk.extensions.inject import com.topjohnwu.magisk.extensions.subscribeK
import io.noties.markwon.Markwon
import io.reactivex.Completable import io.reactivex.Completable
import io.reactivex.Single import io.reactivex.Single
import ru.noties.markwon.Markwon import org.koin.core.KoinComponent
import ru.noties.markwon.html.HtmlPlugin import org.koin.core.inject
import ru.noties.markwon.image.ImagesPlugin
import ru.noties.markwon.image.svg.SvgPlugin
import timber.log.Timber import timber.log.Timber
import java.io.InputStream import java.io.InputStream
import java.util.* import java.util.*
object MarkDownWindow { object MarkDownWindow : KoinComponent {
private val stringRepo: StringRepository by inject() private val stringRepo: StringRepository by inject()
private val markwon: Markwon by inject()
fun show(activity: Context, title: String?, url: String) { fun show(activity: Context, title: String?, url: String) {
show(activity, title, stringRepo.getString(url)) show(activity, title, stringRepo.getString(url))
...@@ -35,25 +34,14 @@ object MarkDownWindow { ...@@ -35,25 +34,14 @@ object MarkDownWindow {
} }
fun show(activity: Context, title: String?, content: Single<String>) { fun show(activity: Context, title: String?, content: Single<String>) {
val markwon = Markwon.builder(activity)
.usePlugin(HtmlPlugin.create())
.usePlugin(ImagesPlugin.create(activity))
.usePlugin(SvgPlugin.create(activity.resources))
.build()
val mv = LayoutInflater.from(activity).inflate(R.layout.markdown_window, null) val mv = LayoutInflater.from(activity).inflate(R.layout.markdown_window, null)
val tv = mv.findViewById<TextView>(R.id.md_txt) val tv = mv.findViewById<TextView>(R.id.md_txt)
content.map { content.map {
runCatching {
markwon.setMarkdown(tv, it) markwon.setMarkdown(tv, it)
}.onFailure {
Timber.e(it)
// Always wrap the actual exception as it could be ExceptionInInitializerError,
// which is a fatal error and RxJava will send it to the global handler and crash
throw MarkwonException(it)
}
}.ignoreElement().onErrorResumeNext { }.ignoreElement().onErrorResumeNext {
// Nothing we can actually do other than show error message // Nothing we can actually do other than show error message
Timber.e(it)
tv.setText(R.string.download_file_error) tv.setText(R.string.download_file_error)
Completable.complete() Completable.complete()
}.subscribeK { }.subscribeK {
...@@ -64,6 +52,4 @@ object MarkDownWindow { ...@@ -64,6 +52,4 @@ object MarkDownWindow {
.show() .show()
} }
} }
class MarkwonException(e: Throwable): Exception(e)
} }
...@@ -8,9 +8,9 @@ import com.topjohnwu.magisk.Info ...@@ -8,9 +8,9 @@ import com.topjohnwu.magisk.Info
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.extensions.cachedFile import com.topjohnwu.magisk.extensions.cachedFile
import com.topjohnwu.magisk.extensions.reboot import com.topjohnwu.magisk.extensions.reboot
import com.topjohnwu.magisk.net.Networking
import com.topjohnwu.magisk.tasks.MagiskInstaller import com.topjohnwu.magisk.tasks.MagiskInstaller
import com.topjohnwu.magisk.utils.Utils import com.topjohnwu.magisk.utils.Utils
import com.topjohnwu.magisk.net.Networking
import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.Shell
import com.topjohnwu.superuser.ShellUtils import com.topjohnwu.superuser.ShellUtils
import com.topjohnwu.superuser.internal.UiThreadHandler import com.topjohnwu.superuser.internal.UiThreadHandler
......
...@@ -7,13 +7,13 @@ import android.widget.Toast ...@@ -7,13 +7,13 @@ import android.widget.Toast
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import com.topjohnwu.magisk.Const import com.topjohnwu.magisk.Const
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.base.BaseActivity
import com.topjohnwu.magisk.model.download.DownloadService import com.topjohnwu.magisk.model.download.DownloadService
import com.topjohnwu.magisk.model.entity.internal.Configuration import com.topjohnwu.magisk.model.entity.internal.Configuration
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject import com.topjohnwu.magisk.model.entity.internal.DownloadSubject
import com.topjohnwu.magisk.ui.base.MagiskActivity
import com.topjohnwu.magisk.utils.Utils import com.topjohnwu.magisk.utils.Utils
internal class InstallMethodDialog(activity: MagiskActivity<*, *>, options: List<String>) : internal class InstallMethodDialog(activity: BaseActivity<*, *>, options: List<String>) :
AlertDialog.Builder(activity) { AlertDialog.Builder(activity) {
init { init {
...@@ -28,11 +28,11 @@ internal class InstallMethodDialog(activity: MagiskActivity<*, *>, options: List ...@@ -28,11 +28,11 @@ internal class InstallMethodDialog(activity: MagiskActivity<*, *>, options: List
} }
} }
private fun flash(activity: MagiskActivity<*, *>) = DownloadService(activity) { private fun flash(activity: BaseActivity<*, *>) = DownloadService(activity) {
subject = DownloadSubject.Magisk(Configuration.Flash.Primary) subject = DownloadSubject.Magisk(Configuration.Flash.Primary)
} }
private fun patchBoot(activity: MagiskActivity<*, *>) = activity.withExternalRW { private fun patchBoot(activity: BaseActivity<*, *>) = activity.withExternalRW {
onSuccess { onSuccess {
Utils.toast(R.string.patch_file_msg, Toast.LENGTH_LONG) Utils.toast(R.string.patch_file_msg, Toast.LENGTH_LONG)
val intent = Intent(Intent.ACTION_GET_CONTENT) val intent = Intent(Intent.ACTION_GET_CONTENT)
...@@ -49,7 +49,7 @@ internal class InstallMethodDialog(activity: MagiskActivity<*, *>, options: List ...@@ -49,7 +49,7 @@ internal class InstallMethodDialog(activity: MagiskActivity<*, *>, options: List
} }
} }
private fun downloadOnly(activity: MagiskActivity<*, *>) = activity.withExternalRW { private fun downloadOnly(activity: BaseActivity<*, *>) = activity.withExternalRW {
onSuccess { onSuccess {
DownloadService(activity) { DownloadService(activity) {
subject = DownloadSubject.Magisk(Configuration.Download) subject = DownloadSubject.Magisk(Configuration.Download)
...@@ -57,7 +57,7 @@ internal class InstallMethodDialog(activity: MagiskActivity<*, *>, options: List ...@@ -57,7 +57,7 @@ internal class InstallMethodDialog(activity: MagiskActivity<*, *>, options: List
} }
} }
private fun installInactiveSlot(activity: MagiskActivity<*, *>) { private fun installInactiveSlot(activity: BaseActivity<*, *>) {
CustomAlertDialog(activity) CustomAlertDialog(activity)
.setTitle(R.string.warning) .setTitle(R.string.warning)
.setMessage(R.string.install_inactive_slot_msg) .setMessage(R.string.install_inactive_slot_msg)
......
...@@ -3,14 +3,14 @@ package com.topjohnwu.magisk.view.dialogs ...@@ -3,14 +3,14 @@ package com.topjohnwu.magisk.view.dialogs
import android.net.Uri import android.net.Uri
import com.topjohnwu.magisk.Info import com.topjohnwu.magisk.Info
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.ui.base.MagiskActivity import com.topjohnwu.magisk.base.BaseActivity
import com.topjohnwu.magisk.utils.Utils import com.topjohnwu.magisk.utils.Utils
import com.topjohnwu.magisk.view.MarkDownWindow import com.topjohnwu.magisk.view.MarkDownWindow
import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.Shell
import com.topjohnwu.superuser.ShellUtils import com.topjohnwu.superuser.ShellUtils
import java.util.* import java.util.*
class MagiskInstallDialog(a: MagiskActivity<*, *>) : CustomAlertDialog(a) { class MagiskInstallDialog(a: BaseActivity<*, *>) : CustomAlertDialog(a) {
init { init {
val filename = "Magisk v${Info.remote.magisk.version}" + val filename = "Magisk v${Info.remote.magisk.version}" +
"(${Info.remote.magisk.versionCode})" "(${Info.remote.magisk.versionCode})"
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
<data> <data>
<import type="com.skoumal.teanity.viewmodel.LoadingViewModel.State" /> <import type="com.topjohnwu.magisk.base.viewmodel.LoadingViewModel.State" />
<import type="com.topjohnwu.magisk.ui.home.SafetyNetState" /> <import type="com.topjohnwu.magisk.ui.home.SafetyNetState" />
......
# v7.3.4 # v7.3.5
- App is now fully written in Kotlin! - Sort installed modules by name
- New downloading system - Better pre-5.0 support
- Add new "Recovery Mode" to Advanced Settings - Fix potential issues when patching tar files
<?xml version='1.0' encoding='UTF-8' standalone='yes' ?> <?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
<resources> <resources>
<string name="advanced_settings_title">إعدادات متقدمة</string>
<string name="app_changelog">تغييرات التطبيق</string> <!--Welcome Activity-->
<string name="author">انشئ بواسطة %1$s</string> <string name="modules">الإضافات</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>
<string name="downloads">التنزيلات</string> <string name="downloads">التنزيلات</string>
<string name="forever">للابد</string> <string name="superuser">المستخدم المتميز</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="log">السجل</string> <string name="log">السجل</string>
<string name="logs_cleared">تم حذف السجل بنجاح</string> <string name="settings">الإعدادات</string>
<string name="magisk_update_title">تحديث Magisk جديد متوفر!</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="magisk_version_error">Magisk غير مثبت</string>
<string name="menuClearLog">حذف السجل الآن</string> <string name="checking_for_updates">البحث عن تحديثات…</string>
<string name="menuReload">إعادة تحميل</string> <string name="invalid_update_channel">قناة تحديث غير صالحة</string>
<string name="modules">الإضافات</string> <string name="safetyNet_check_text">انقر لبدء فحص SafetyNet</string>
<string name="multiuser_mode">وضع تعدد المستخدمين</string> <string name="checking_safetyNet_status">التحقق من حالة SafetyNet…</string>
<string name="no_apps_found">لا توجد تطبيقات</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_info_provided">(لم يتم توفير أي معلومات)</string>
<string name="no_modules_found">لم يعثر على الإضافات</string> <string name="no_modules_found">لم يعثر على أي إضافات</string>
<string name="none">بدون</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="not_installed">غير مثبت</string>
<string name="once">مرة</string> <string name="updated_on">حُدث في: %1$s</string>
<string name="owner_manage_summary">يمكن للمالك فقط إدارة صلاحيات الروت وتلقي مطالبات الطلب</string> <string name="sorting_order">ترتيب الفرز</string>
<string name="owner_only_summary">المالك فقط لديه صلاحيات الروت</string> <string name="sort_by_name">افرز حسب الاسم</string>
<string name="prompt">طلب</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="reboot">إعادة التشغيل</string>
<string name="settings_reboot_toast">إعادة التشغيل لتطبيق الإعدادات</string>
<string name="release_notes">ملاحظات الإصدار</string> <string name="release_notes">ملاحظات الإصدار</string>
<string name="remove_file_created">سيتم حذف الإضافة في إعادة التشغيل التالي</string> <string name="repo_cache_cleared">مُسحت الذاكرة المؤقتة للإضافة</string>
<string name="remove_file_deleted">لن يتم حذف الإضافة في إعادة التشغيل التالي</string>
<string name="repo_cache_cleared">تم مسح الذاكرة المؤقته للمستودع</string> <string name="dtbo_patched_title">رُقع DTBO!</string>
<string name="repo_install_msg">هل تريد تثبيت %1$s ?</string> <string name="dtbo_patched_reboot">رقع Magisk Manager الملف dtbo.img. يرجى إعادة التشغيل</string>
<string name="repo_install_title">تثبيت %1$s</string> <string name="flashing">…تثبيت</string>
<string name="request_timeout">مهلة الطلب</string> <string name="done">تم!</string>
<string name="request_timeout_summary">%1$d ثانية</string> <string name="failure">فشل</string>
<string name="safetyNet_check_success">نجح فحص SafetyNet</string> <string name="hide_manager_title">إخفاء Magisk Manager…</string>
<string name="safetyNet_check_text">انقر لبدء فحص SafetyNet</string> <string name="hide_manager_fail_toast">فشل إخفاء Magisk Manager…</string>
<string name="safetyNet_res_invalid">الاستجابة غير صالحه</string> <string name="open_link_failed_toast">لم يُعثر على تطبيق لفتح الرابط …</string>
<string name="settings">الإعدادات</string> <string name="warning">تحذير</string>
<string name="settings_clear_cache_summary">حذف المعلومات المخزنة مؤقتا للمستودع على الانترنت، يجبر التطبيق لتحديث عبر الانترنت</string> <string name="complete_uninstall">إلغاء التثبيت بالكامل</string>
<string name="settings_clear_cache_title">حذف الذاكرة المؤقتة للمستودع</string> <string name="restore_img">استعادة الصور</string>
<string name="settings_core_only_summary">تمكين الميزات الأساسية فقط، لن يتم تحميل جميع الإضافات. MagiskSU، MagiskHide، systemless hosts، و لا يزال ممكنا</string> <string name="restore_img_msg">استعادة…</string>
<string name="settings_core_only_title">Magisk الوضع الأساسي فقط</string> <string name="restore_done">استُعيدت!</string>
<string name="settings_dark_theme_summary">تفعيل السمة الغامقة</string> <string name="restore_fail">النسخة الاحتياطية الأصلية غير موجودة!</string>
<string name="settings_dark_theme_title">السمة الغامقة</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_general_category">عام</string>
<string name="settings_hosts_summary">Systemless يدعم تطبيقات حجب الإعلانات</string> <string name="settings_dark_theme_title">السمة الغامقة</string>
<string name="settings_hosts_title">تمكين المضيفين(الهوست) لـ systemless</string> <string name="settings_dark_theme_summary">تمكين السمة الغامقة</string>
<string name="settings_magiskhide_summary">إخفاء Magisk من مختلف الاكتشافات</string> <string name="settings_download_path_title">مسار التحميل</string>
<string name="settings_owner_manage">إدارة مالك الجهاز</string> <string name="settings_download_path_message">ستحمل الملفات إلى %1$s</string>
<string name="settings_owner_only">مالك الجهاز فقط</string> <string name="settings_clear_cache_title">حذف الذاكرة المؤقتة للإضافات</string>
<string name="settings_reboot_toast">إعادة التشغيل لتطبيق الإعدادات</string> <string name="settings_clear_cache_summary">حذف المعلومات المخزنة مؤقتا للإضافات المباشرة، هذا سيجبر التطبيق على التحديث عبر الانترنت</string>
<string name="settings_su_adb">ADB فقط</string> <string name="settings_hide_manager_title">إخفاء Magisk Manager</string>
<string name="settings_su_app">التطبيقات فقط</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_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_disable">معطل</string>
<string name="settings_su_request_10">10 ثواني</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_20">20 ثانية</string>
<string name="settings_su_request_30">30 ثانية</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_su_request_60">60 ثانية</string>
<string name="settings_user_independent">مستخدم مستقل</string> <string name="superuser_access">صلاحيات المستخدم المتميز</string>
<string name="sixtymin">60 دقائق</string> <string name="auto_response">رد تلقائي</string>
<string name="su_allow_toast">%1$s يتم منح صلاحيات Superuser</string> <string name="request_timeout">مهلة الطلب</string>
<string name="su_deny_toast">%1$s يتم رفض صلاحيات Superuser</string> <string name="superuser_notification">إشعارات المستخدم المتميز</string>
<string name="su_request_title">Superuser طلبات</string> <string name="request_timeout_summary">%1$d ثانية</string>
<string name="su_revoke_msg">تأكيد لسحب صلاحيات %1$s ?</string> <string name="settings_su_reauth_title">إعادة المصادقة بعد الترقية</string>
<string name="su_revoke_title">سحب؟</string> <string name="settings_su_reauth_summary">أعد المصادقة على صلاحيات المستخدم المتميز بعد إجراء ترقيات للتطبيق</string>
<string name="su_snack_deny">Superuser الصلاحيات لـ %1$s تم رفضها</string> <string name="settings_su_fingerprint_summary">استخدام قارئ بصمات الأصابع للسماح بطلبات المستخدم المتميز</string>
<string name="su_snack_grant">Superuser الصلاحيات لـ %1$s تم منحها</string> <string name="settings_su_fingerprint_title">تمكين مصادقة البصمة</string>
<string name="su_snack_log_off">السجلات لـ %1$s تم تعطيلها</string> <string name="auth_fingerprint">مصادقة البصمة</string>
<string name="su_snack_log_on">السجلات لـ %1$s تم تفعيلها</string>
<string name="su_snack_notif_off">الإشعارات لـ %1$s تم تعطيلها</string> <string name="multiuser_mode">نمط تعدد المستخدمين</string>
<string name="su_snack_notif_on">الإشعارات لـ %1$s تم تفعيلها</string> <string name="settings_owner_only">مالك الجهاز فقط</string>
<string name="su_warning">"منح حق الوصول الكامل إلى جهازك. <string name="settings_owner_manage">إدارة مالك الجهاز</string>
رفض إذا كنت غير متأكد!"</string> <string name="settings_user_independent">مستقل عن المستخدم</string>
<string name="superuser">Superuser</string> <string name="owner_only_summary">للمالك فقط صلاحيات الروت</string>
<string name="superuser_access">Superuser صلاحيات</string> <string name="owner_manage_summary">يمكن للمالك فقط إدارة صلاحيات الروت وتلقي مطالبات الطلب</string>
<string name="superuser_notification">Superuser إشعارات</string> <string name="user_indepenent_summary">لكل مستخدم قواعد روت منفصلة خاصة به</string>
<string name="target_uid">الهدف UID: %1$d</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="tenmin">10 دقائق</string>
<string name="twentymin">20 دقائق</string>
<string name="thirtymin">30 دقائق</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="toast">ملاحظة منبثقة</string>
<string name="twentymin">20 دقائق</string> <string name="none">بدون</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="auth_fail">فشل المصادقة</string> <string name="auth_fail">فشل المصادقة</string>
<string name="auth_fingerprint">مصادقة البصمة</string>
<string name="complete_uninstall">إلغاء التثبيت بالكامل</string> <!--Superuser logs-->
<string name="direct_install">تثبيت مباشر (موصى به)</string> <string name="pid">PID: %1$d</string>
<string name="disable_fingerprint">لم يتم تعيين بصمات الأصابع أو لا يوجد جهاز مدعوم</string> <string name="target_uid">Target UID: %1$d</string>
<string name="download_zip_only">تحميل ملف zip فقط</string> <string name="command">Command: %1$s</string>
<string name="dtbo_patched_reboot">قام مدير Magisk بتصحيح dtbo.img ، يرجى إعادة التشغيل</string>
<string name="dtbo_patched_title">تم تصحيح DTBO!</string> <!-- MagiskHide -->
<string name="env_fix_msg">يحتاج جهازك إلى إعداد إضافي لـ Magisk للعمل بشكل صحيح. سيتم تنزيل ملف zip لتثبيت Magisk ، هل تريد المتابعة الآن؟</string> <string name="show_system_app">اظهار برامج النظام</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>
</resources> </resources>
...@@ -24,6 +24,7 @@ ...@@ -24,6 +24,7 @@
<string name="advanced_settings_title">Ajustes avanzados</string> <string name="advanced_settings_title">Ajustes avanzados</string>
<string name="keep_force_encryption">Mantener cifrado forzado</string> <string name="keep_force_encryption">Mantener cifrado forzado</string>
<string name="keep_dm_verity">Mantener AVB 2.0/dm-verity</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="current_installed">Instalada: %1$s</string>
<string name="latest_version">Última: %1$s</string> <string name="latest_version">Última: %1$s</string>
<string name="uninstall">Desinstalar</string> <string name="uninstall">Desinstalar</string>
...@@ -44,6 +45,7 @@ ...@@ -44,6 +45,7 @@
<string name="reboot_recovery">Reiniciar en Modo Recovery</string> <string name="reboot_recovery">Reiniciar en Modo Recovery</string>
<string name="reboot_bootloader">Reiniciar en Modo Bootloader</string> <string name="reboot_bootloader">Reiniciar en Modo Bootloader</string>
<string name="reboot_download">Reiniciar en Modo Download</string> <string name="reboot_download">Reiniciar en Modo Download</string>
<string name="reboot_edl">Reiniciar en Modo EDL</string>
<!--Repo Fragment--> <!--Repo Fragment-->
<string name="update_available">Actualización Disponible</string> <string name="update_available">Actualización Disponible</string>
...@@ -118,6 +120,8 @@ ...@@ -118,6 +120,8 @@
<string name="settings_general_category">General</string> <string name="settings_general_category">General</string>
<string name="settings_dark_theme_title">Tema oscuro</string> <string name="settings_dark_theme_title">Tema oscuro</string>
<string name="settings_dark_theme_summary">Habilitar el 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_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_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> <string name="settings_hide_manager_title">Ocultar Magisk Manager</string>
...@@ -178,6 +182,7 @@ ...@@ -178,6 +182,7 @@
<string name="isolate_summary">Cada sesión root tendrá su propia Namespace</string> <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="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="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--> <!--Superuser-->
<string name="su_request_title">Petición de superusuario</string> <string name="su_request_title">Petición de superusuario</string>
......
...@@ -8,10 +8,10 @@ ...@@ -8,10 +8,10 @@
<string name="settings">Seaded</string> <string name="settings">Seaded</string>
<string name="install">Installi</string> <string name="install">Installi</string>
<string name="unsupport_magisk_title">Mittetoetatud Magisk\'i versioon</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--> <!--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="checking_for_updates">Kontrollin uuendusi…</string>
<string name="invalid_update_channel">Sobimatu uuenduste kanal</string> <string name="invalid_update_channel">Sobimatu uuenduste kanal</string>
<string name="safetyNet_check_text">Koputa SafetyNet\'i kontrolliks</string> <string name="safetyNet_check_text">Koputa SafetyNet\'i kontrolliks</string>
...@@ -24,6 +24,7 @@ ...@@ -24,6 +24,7 @@
<string name="advanced_settings_title">Täpsemad seaded</string> <string name="advanced_settings_title">Täpsemad seaded</string>
<string name="keep_force_encryption">Säilita sunnitud krüpteering</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="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="current_installed">Installitud: %1$s</string>
<string name="latest_version">Viimatine: %1$s</string> <string name="latest_version">Viimatine: %1$s</string>
<string name="uninstall">Eemalda</string> <string name="uninstall">Eemalda</string>
...@@ -44,6 +45,7 @@ ...@@ -44,6 +45,7 @@
<string name="reboot_recovery">Taaskäivita taastusesse</string> <string name="reboot_recovery">Taaskäivita taastusesse</string>
<string name="reboot_bootloader">Taaskäivita käivitushaldurisse</string> <string name="reboot_bootloader">Taaskäivita käivitushaldurisse</string>
<string name="reboot_download">Taaskäivita allalaadimisrežiimi</string> <string name="reboot_download">Taaskäivita allalaadimisrežiimi</string>
<string name="reboot_edl">Taaskäivita EDL\'i</string>
<!--Repo Fragment--> <!--Repo Fragment-->
<string name="update_available">Uuendus saadaval</string> <string name="update_available">Uuendus saadaval</string>
...@@ -68,9 +70,23 @@ ...@@ -68,9 +70,23 @@
<string name="progress_channel">Edenemise teated</string> <string name="progress_channel">Edenemise teated</string>
<string name="download_complete">Allalaadimine valmis</string> <string name="download_complete">Allalaadimine valmis</string>
<string name="download_file_error">Faili allalaadimisel esines viga</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="magisk_update_title">Magisk\'ile on uuendus saadaval!</string>
<string name="manager_update_title">Magisk Manager\'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--> <!--Toasts, Dialogs-->
<string name="close">Sulge</string> <string name="close">Sulge</string>
<string name="repo_install_title">Installi %1$s</string> <string name="repo_install_title">Installi %1$s</string>
...@@ -80,19 +96,16 @@ ...@@ -80,19 +96,16 @@
<string name="settings_reboot_toast">Taaskäivita seadete rakendamiseks.</string> <string name="settings_reboot_toast">Taaskäivita seadete rakendamiseks.</string>
<string name="release_notes">Väljalaske märkmed</string> <string name="release_notes">Väljalaske märkmed</string>
<string name="repo_cache_cleared">Hoidla vahemälu tühjendatud</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_title">DTBO sai paigatud!</string>
<string name="dtbo_patched_reboot">Magisk Manager on paiganud dtbo.img. Palun taaskäivita.</string> <string name="dtbo_patched_reboot">Magisk Manager on paiganud dtbo.img. Palun taaskäivita.</string>
<string name="flashing">Välgutamine</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_title">Peidan Magisk Manager\'i…</string>
<string name="hide_manager_fail_toast">Magisk Manager\'i peitmine ebaõnnestus.</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="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="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="complete_uninstall">Täielik eemaldus</string>
<string name="restore_img">Taasta tõmmised</string> <string name="restore_img">Taasta tõmmised</string>
<string name="restore_img_msg">Taastamine…</string> <string name="restore_img_msg">Taastamine…</string>
...@@ -103,13 +116,14 @@ ...@@ -103,13 +116,14 @@
<string name="setup_fail">Seadistus ebaõnnnestus.</string> <string name="setup_fail">Seadistus ebaõnnnestus.</string>
<string name="env_fix_title">Vajab lisaseadistust</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="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> <string name="setup_msg">Käivitan keskkonnaseadistust…</string>
<!--Settings Activity --> <!--Settings Activity -->
<string name="settings_general_category">Üldine</string> <string name="settings_general_category">Üldine</string>
<string name="settings_dark_theme_title">Tume teema</string> <string name="settings_dark_theme_title">Tume teema</string>
<string name="settings_dark_theme_summary">Luba 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_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_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> <string name="settings_hide_manager_title">Peida Magisk Manager</string>
...@@ -171,6 +185,7 @@ ...@@ -171,6 +185,7 @@
<string name="isolate_summary">Iga juurkasutaja sessioon saab oma isoleeritud nimeruumi.</string> <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="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="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--> <!--Superuser-->
<string name="su_request_title">Superkasutaja taotlus</string> <string name="su_request_title">Superkasutaja taotlus</string>
......
...@@ -24,6 +24,7 @@ ...@@ -24,6 +24,7 @@
<string name="advanced_settings_title">Gelişmiş Ayarlar</string> <string name="advanced_settings_title">Gelişmiş Ayarlar</string>
<string name="keep_force_encryption">Şifrelemeyi zorlamayı sürdür</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="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="current_installed">Yüklü: %1$s</string>
<string name="latest_version">Güncel: %1$s</string> <string name="latest_version">Güncel: %1$s</string>
<string name="uninstall">Kaldır</string> <string name="uninstall">Kaldır</string>
......
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<style name="SplashTheme" parent="SplashThemeBase.V19"/>
<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>
</resources> </resources>
\ No newline at end of file
...@@ -32,10 +32,6 @@ ...@@ -32,10 +32,6 @@
<color name="dark_secondary_text">#dedede</color> <color name="dark_secondary_text">#dedede</color>
<color name="su_request_background">#e0e0e0</color> <color name="su_request_background">#e0e0e0</color>
<!-- Flashing colors -->
<color name="ic_launcher_background">#00AF9C</color>
<!-- Card colors --> <!-- Card colors -->
<color name="card_background_color_dark">#ff424242</color> <color name="card_background_color_dark">#ff424242</color>
<color name="card_background_color_light">#ffffffff</color> <color name="card_background_color_light">#ffffffff</color>
......
...@@ -60,8 +60,6 @@ ...@@ -60,8 +60,6 @@
<item name="cardBackgroundColor">@color/card_background_color_light</item> <item name="cardBackgroundColor">@color/card_background_color_light</item>
</style> </style>
<style name="SplashTheme" parent="Theme.AppCompat.NoActionBar"> <style name="SplashTheme" parent="SplashThemeBase"/>
<item name="android:windowBackground">@drawable/ic_splash_activity</item>
</style>
</resources> </resources>
...@@ -7,7 +7,7 @@ if (configPath.exists()) ...@@ -7,7 +7,7 @@ if (configPath.exists())
configPath.withInputStream { is -> props.load(is) } configPath.withInputStream { is -> props.load(is) }
buildscript { buildscript {
ext.kotlinVer = '1.3.50' ext.vKotlin = '1.3.50'
repositories { repositories {
google() google()
...@@ -16,8 +16,8 @@ buildscript { ...@@ -16,8 +16,8 @@ buildscript {
maven { url 'https://kotlin.bintray.com/kotlinx' } maven { url 'https://kotlin.bintray.com/kotlinx' }
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:3.5.0' classpath 'com.android.tools.build:gradle:3.5.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlinVer}" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${vKotlin}"
// NOTE: Do not place your application dependencies here; they belong // NOTE: Do not place your application dependencies here; they belong
...@@ -38,7 +38,7 @@ subprojects { ...@@ -38,7 +38,7 @@ subprojects {
maven { url "https://jitpack.io" } maven { url "https://jitpack.io" }
maven { url "http://oss.sonatype.org/content/repositories/snapshots" } maven { url "http://oss.sonatype.org/content/repositories/snapshots" }
} }
afterEvaluate { afterEvaluate { module ->
if (getPlugins().hasPlugin('com.android.library') || if (getPlugins().hasPlugin('com.android.library') ||
getPlugins().hasPlugin('com.android.application')) { getPlugins().hasPlugin('com.android.application')) {
android { android {
...@@ -58,7 +58,7 @@ subprojects { ...@@ -58,7 +58,7 @@ subprojects {
} }
} }
if (it.name == 'app' || it.name == 'stub') { if (module.name == 'app' || module.name == 'stub') {
android { android {
signingConfigs { signingConfigs {
config { config {
...@@ -76,8 +76,6 @@ subprojects { ...@@ -76,8 +76,6 @@ subprojects {
signingConfig signingConfigs.config signingConfig signingConfigs.config
} }
release { release {
minifyEnabled true
shrinkResources true
signingConfig signingConfigs.config signingConfig signingConfigs.config
} }
} }
...@@ -85,6 +83,16 @@ subprojects { ...@@ -85,6 +83,16 @@ subprojects {
lintOptions { lintOptions {
disable 'MissingTranslation' 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 @@ ...@@ -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. 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 ## 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 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 own section](#samsung-system-as-root). - 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 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!** - 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: Other notes:
...@@ -33,7 +33,7 @@ If the result is `true`, then your device is using system-as-root. ...@@ -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)) (P.S. If you are interested more regarding system-as-root, please check [this Twitter thread](https://twitter.com/topjohnwu/status/1174392824625676288))
## Custom Recovery ## 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 - Download the Magisk installer zip
- Reboot to custom recovery - Reboot to custom recovery
...@@ -41,9 +41,9 @@ If your device have custom recovery support, the easiest way is to install it th ...@@ -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 - Check whether Magisk Manager is installed. If for some reason it isn't installed automatically, manually install the APK
## Boot Image Patching ## 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 - Copy the boot/recovery image to your device
- Download and install the latest Magisk Manager - 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 ...@@ -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 `fastboot flash recovery /path/to/magisk_patched.img` if you are patching a recovery image
## Magisk in Recovery ## 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. 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 ...@@ -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. 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. 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. 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. 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. 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 :) 16. Voila! Enjoy Magisk :)
...@@ -116,17 +116,17 @@ Press *Power + Volume Down* to exit download mode. As soon as the screen turns o ...@@ -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 - `boot`: remove the signature of the image to prevent soft bricks
- `recovery`: this is where Magisk is actually installed - `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. - **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. - 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
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 ### 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!) 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 ### 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 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. - 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() { ...@@ -347,7 +347,7 @@ static bool magisk_env() {
unlink("/data/magisk_debug.log"); unlink("/data/magisk_debug.log");
// Backwards compatibility // Backwards compatibility
symlink("./magisk", "/sbin/.core"); symlink("./.magisk", "/sbin/.core");
symlink("./modules", MAGISKTMP "/img"); symlink("./modules", MAGISKTMP "/img");
// Directories in tmpfs overlay // Directories in tmpfs overlay
......
...@@ -74,22 +74,24 @@ void exec_module_script(const char *stage, const vector<string> &module_list) { ...@@ -74,22 +74,24 @@ void exec_module_script(const char *stage, const vector<string> &module_list) {
} }
} }
static const char migrate_script[] = constexpr char migrate_script[] =
"IMG=%s;" "MODULEROOT=" MODULEROOT R"EOF(
"MNT=/dev/img_mnt;" IMG=%s
"e2fsck -yf $IMG;" MNT=/dev/img_mnt
"mkdir -p $MNT;" e2fsck -yf $IMG
"for num in 0 1 2 3 4 5 6 7; do" mkdir -p $MNT
" losetup /dev/block/loop${num} $IMG || continue;" for num in 0 1 2 3 4 5 6 7; do
" mount -t ext4 /dev/block/loop${num} $MNT;" losetup /dev/block/loop${num} $IMG || continue
" rm -rf $MNT/lost+found $MNT/.core;" mount -t ext4 /dev/block/loop${num} $MNT
" magisk --clone $MNT " MODULEROOT ";" rm -rf $MNT/lost+found $MNT/.core
" umount $MNT;" magisk --clone $MNT $MODULEROOT
" rm -rf $MNT;" umount $MNT
" losetup -d /dev/block/loop${num};" rm -rf $MNT
" break;" losetup -d /dev/block/loop${num}
"done;" break
"rm -rf $IMG;"; done
rm -rf $IMG
)EOF";
void migrate_img(const char *img) { void migrate_img(const char *img) {
LOGI("* Migrating %s\n", img); LOGI("* Migrating %s\n", img);
...@@ -99,11 +101,12 @@ void migrate_img(const char *img) { ...@@ -99,11 +101,12 @@ void migrate_img(const char *img) {
exec_command_sync(exec, "/system/bin/sh", "-c", cmds); exec_command_sync(exec, "/system/bin/sh", "-c", cmds);
} }
static const char install_script[] = constexpr char install_script[] = R"EOF(
"APK=%s;" APK=%s
"log -t Magisk \"apk_install: $APK\";" log -t Magisk "apk_install: $APK"
"log -t Magisk \"apk_install: `pm install -r $APK 2>&1`\";" log -t Magisk "apk_install: `pm install -r $APK 2>&1`"
"rm -f $APK;"; rm -f $APK
)EOF";
void install_apk(const char *apk) { void install_apk(const char *apk) {
setfilecon(apk, "u:object_r:" SEPOL_FILE_DOMAIN ":s0"); setfilecon(apk, "u:object_r:" SEPOL_FILE_DOMAIN ":s0");
......
...@@ -67,7 +67,7 @@ static bool check_key_combo() { ...@@ -67,7 +67,7 @@ static bool check_key_combo() {
if (events.empty()) if (events.empty())
return false; return false;
RunFinally fin([&]() -> void { run_finally fin([&]() -> void {
for (const int &fd : events) for (const int &fd : events)
close(fd); close(fd);
}); });
......
...@@ -22,7 +22,7 @@ uint32_t dyn_img_hdr::j32 = 0; ...@@ -22,7 +22,7 @@ uint32_t dyn_img_hdr::j32 = 0;
uint64_t dyn_img_hdr::j64 = 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) { 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)) if (!ptr->write(in, size))
return -1; return -1;
return ptr->finalize(); return ptr->finalize();
...@@ -44,7 +44,7 @@ static void dump(void *buf, size_t size, const char *filename) { ...@@ -44,7 +44,7 @@ static void dump(void *buf, size_t size, const char *filename) {
close(fd); 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); int ifd = xopen(filename, O_RDONLY);
size_t size = lseek(ifd, 0, SEEK_END); size_t size = lseek(ifd, 0, SEEK_END);
lseek(ifd, 0, SEEK_SET); lseek(ifd, 0, SEEK_SET);
...@@ -57,53 +57,147 @@ static void restore_buf(int fd, const void *buf, size_t size) { ...@@ -57,53 +57,147 @@ static void restore_buf(int fd, const void *buf, size_t size) {
xwrite(fd, buf, size); xwrite(fd, buf, size);
} }
boot_img::~boot_img() { void dyn_img_hdr::print() {
munmap(map_addr, map_size); uint32_t ver = header_version();
delete hdr; fprintf(stderr, "HEADER_VER [%u]\n", ver);
delete k_hdr; fprintf(stderr, "KERNEL_SZ [%u]\n", kernel_size());
delete r_hdr; fprintf(stderr, "RAMDISK_SZ [%u]\n", ramdisk_size());
delete b_hdr; 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 boot_img::boot_img(const char *image) {
#define CHROME_RET 2
int boot_img::parse_file(const char *image) {
mmap_ro(image, map_addr, map_size); mmap_ro(image, map_addr, map_size);
fprintf(stderr, "Parsing boot image: [%s]\n", image); fprintf(stderr, "Parsing boot image: [%s]\n", image);
for (uint8_t *head = map_addr; head < map_addr + map_size; ++head) { for (uint8_t *addr = map_addr; addr < map_addr + map_size; ++addr) {
switch (check_fmt(head, map_size)) { switch (check_fmt(addr, map_size)) {
case CHROMEOS: case CHROMEOS:
// The caller should know it's chromeos, as it needs additional signing // chromeos require external signing
flags |= CHROMEOS_FLAG; flags |= CHROMEOS_FLAG;
addr += 65535;
break; break;
case DHTB: case DHTB:
flags |= DHTB_FLAG; flags |= (DHTB_FLAG | SEANDROID_FLAG);
flags |= SEANDROID_FLAG;
fprintf(stderr, "DHTB_HDR\n"); fprintf(stderr, "DHTB_HDR\n");
addr += sizeof(dhtb_hdr) - 1;
break; break;
case BLOB: case BLOB:
flags |= BLOB_FLAG; flags |= BLOB_FLAG;
fprintf(stderr, "TEGRA_BLOB\n"); fprintf(stderr, "TEGRA_BLOB\n");
b_hdr = new blob_hdr(); addr += sizeof(blob_hdr) - 1;
memcpy(b_hdr, head, sizeof(blob_hdr));
head += sizeof(blob_hdr) - 1;
break; break;
case AOSP: case AOSP:
return parse_image(head); parse_image(addr);
return;
default: default:
break; 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()) #define get_block(name) {\
int boot_img::parse_image(uint8_t *head) { name = addr + off; \
auto hp = reinterpret_cast<boot_img_hdr*>(head); 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) { if (hp->page_size >= 0x02000000) {
fprintf(stderr, "PXA_BOOT_HDR\n"); fprintf(stderr, "PXA_BOOT_HDR\n");
hdr = new dyn_img_pxa(head); hdr = new dyn_img_pxa(addr);
} else { } else {
if (memcmp(hp->cmdline, NOOKHD_RL_MAGIC, 10) == 0 || if (memcmp(hp->cmdline, NOOKHD_RL_MAGIC, 10) == 0 ||
memcmp(hp->cmdline, NOOKHD_GL_MAGIC, 12) == 0 || memcmp(hp->cmdline, NOOKHD_GL_MAGIC, 12) == 0 ||
...@@ -112,53 +206,41 @@ int boot_img::parse_image(uint8_t *head) { ...@@ -112,53 +206,41 @@ int boot_img::parse_image(uint8_t *head) {
memcmp(hp->cmdline, NOOKHD_ER_MAGIC, 30) == 0) { memcmp(hp->cmdline, NOOKHD_ER_MAGIC, 30) == 0) {
flags |= NOOKHD_FLAG; flags |= NOOKHD_FLAG;
fprintf(stderr, "NOOKHD_LOADER\n"); fprintf(stderr, "NOOKHD_LOADER\n");
head += NOOKHD_PRE_HEADER_SZ; addr += NOOKHD_PRE_HEADER_SZ;
} else if (memcmp(hp->name, ACCLAIM_MAGIC, 10) == 0) { } else if (memcmp(hp->name, ACCLAIM_MAGIC, 10) == 0) {
flags |= ACCLAIM_FLAG; flags |= ACCLAIM_FLAG;
fprintf(stderr, "ACCLAIM_LOADER\n"); fprintf(stderr, "ACCLAIM_LOADER\n");
head += ACCLAIM_PRE_HEADER_SZ; addr += ACCLAIM_PRE_HEADER_SZ;
} }
if (hp->header_version == 1) if (hp->header_version == 1)
hdr = new dyn_img_v1(head); hdr = new dyn_img_v1(addr);
else if (hp->header_version == 2) else if (hp->header_version == 2)
hdr = new dyn_img_v2(head); hdr = new dyn_img_v2(addr);
else else
hdr = new dyn_img_v0(head); hdr = new dyn_img_v0(addr);
} }
size_t pos = hdr->page_size(); for (int i = SHA_DIGEST_SIZE; i < SHA256_DIGEST_SIZE; ++i) {
if (hdr->id()[i]) {
flags |= hdr->id()[SHA_DIGEST_SIZE] ? SHA256_FLAG : 0; flags |= SHA256_FLAG;
break;
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();
recov_dtbo = head + pos; hdr->print();
pos += hdr->recovery_dtbo_size();
pos_align();
dtb = head + pos; size_t off = hdr->page_size();
pos += hdr->dtb_size(); hdr_addr = addr;
pos_align(); 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) { if (addr + off < map_addr + map_size) {
tail = head + pos; tail = addr + off;
tail_size = map_size - (tail - map_addr); tail_size = map_size - (tail - map_addr);
} }
...@@ -169,7 +251,7 @@ int boot_img::parse_image(uint8_t *head) { ...@@ -169,7 +251,7 @@ int boot_img::parse_image(uint8_t *head) {
flags |= LG_BUMP_FLAG; flags |= LG_BUMP_FLAG;
} }
find_dtb(); find_kernel_dtb();
k_fmt = check_fmt(kernel, hdr->kernel_size()); k_fmt = check_fmt(kernel, hdr->kernel_size());
r_fmt = check_fmt(ramdisk, hdr->ramdisk_size()); r_fmt = check_fmt(ramdisk, hdr->ramdisk_size());
...@@ -178,159 +260,82 @@ int boot_img::parse_image(uint8_t *head) { ...@@ -178,159 +260,82 @@ int boot_img::parse_image(uint8_t *head) {
if (k_fmt == MTK) { if (k_fmt == MTK) {
fprintf(stderr, "MTK_KERNEL_HDR\n"); fprintf(stderr, "MTK_KERNEL_HDR\n");
flags |= MTK_KERNEL; flags |= MTK_KERNEL;
k_hdr = new mtk_hdr(); k_hdr = reinterpret_cast<mtk_hdr *>(kernel);
memcpy(k_hdr, kernel, sizeof(mtk_hdr));
fprintf(stderr, "KERNEL [%u]\n", k_hdr->size); fprintf(stderr, "KERNEL [%u]\n", k_hdr->size);
fprintf(stderr, "NAME [%s]\n", k_hdr->name); fprintf(stderr, "NAME [%s]\n", k_hdr->name);
kernel += 512; kernel += sizeof(mtk_hdr);
hdr->kernel_size() -= 512; hdr->kernel_size() -= sizeof(mtk_hdr);
k_fmt = check_fmt(kernel, hdr->kernel_size()); k_fmt = check_fmt(kernel, hdr->kernel_size());
} }
if (r_fmt == MTK) { if (r_fmt == MTK) {
fprintf(stderr, "MTK_RAMDISK_HDR\n"); fprintf(stderr, "MTK_RAMDISK_HDR\n");
flags |= MTK_RAMDISK; flags |= MTK_RAMDISK;
r_hdr = new mtk_hdr(); r_hdr = reinterpret_cast<mtk_hdr *>(ramdisk);
memcpy(r_hdr, ramdisk, sizeof(mtk_hdr));
fprintf(stderr, "RAMDISK [%u]\n", r_hdr->size); fprintf(stderr, "RAMDISK [%u]\n", r_hdr->size);
fprintf(stderr, "NAME [%s]\n", r_hdr->name); fprintf(stderr, "NAME [%s]\n", r_hdr->name);
ramdisk += 512; ramdisk += sizeof(mtk_hdr);
hdr->ramdisk_size() -= 512; hdr->ramdisk_size() -= sizeof(mtk_hdr);
r_fmt = check_fmt(ramdisk, hdr->ramdisk_size()); r_fmt = check_fmt(ramdisk, hdr->ramdisk_size());
} }
fprintf(stderr, "KERNEL_FMT [%s]\n", fmt2name[k_fmt]); fprintf(stderr, "KERNEL_FMT [%s]\n", fmt2name[k_fmt]);
fprintf(stderr, "RAMDISK_FMT [%s]\n", fmt2name[r_fmt]); fprintf(stderr, "RAMDISK_FMT [%s]\n", fmt2name[r_fmt]);
return (flags & CHROMEOS_FLAG) ? CHROME_RET : 0;
} }
void boot_img::find_dtb() { void boot_img::find_kernel_dtb() {
for (uint32_t i = 0; i < hdr->kernel_size(); ++i) { 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); auto fdt_hdr = reinterpret_cast<fdt_header *>(kernel + i);
if (fdt32_to_cpu(fdt_hdr->magic) != FDT_MAGIC) if (fdt32_to_cpu(fdt_hdr->magic) != FDT_MAGIC)
continue; continue;
// Check that fdt_header.totalsize does not overflow kernel image size // Check that fdt_header.totalsize does not overflow kernel image size
uint32_t totalsize = fdt32_to_cpu(fdt_hdr->totalsize); uint32_t totalsize = fdt32_to_cpu(fdt_hdr->totalsize);
if (totalsize > hdr->kernel_size() - i) { if (totalsize + i > eof)
fprintf(stderr, "Invalid DTB detection at 0x%x: size (%u) > remaining (%u)\n",
i, totalsize, hdr->kernel_size() - i);
continue; continue;
}
// Check that fdt_header.off_dt_struct does not overflow kernel image size // 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); uint32_t off_dt_struct = fdt32_to_cpu(fdt_hdr->off_dt_struct);
if (off_dt_struct > hdr->kernel_size() - i) { if (off_dt_struct + i > eof)
fprintf(stderr, "Invalid DTB detection at 0x%x: "
"struct offset (%u) > remaining (%u)\n",
i, off_dt_struct, hdr->kernel_size() - i);
continue; continue;
}
// Check that fdt_node_header.tag of first node is FDT_BEGIN_NODE // 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); 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) { 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);
continue; continue;
}
kernel_dtb = kernel + i; kernel_dtb = kernel + i;
kernel_dt_size = hdr->kernel_size() - i; kernel_dt_size = eof - i;
hdr->kernel_size() = i; hdr->kernel_size() = i;
fprintf(stderr, "KERNEL_DTB [%u]\n", kernel_dt_size); fprintf(stderr, "KERNEL_DTB [%u]\n", kernel_dt_size);
break; 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) { int unpack(const char *image, bool hdr) {
boot_img boot {}; boot_img boot(image);
int ret = boot.parse_file(image);
int fd;
if (hdr) { if (hdr)
FILE *fp = xfopen(HEADER_FILE, "w"); boot.hdr->dump_hdr_file();
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);
}
// Dump kernel // Dump kernel
if (COMPRESSED(boot.k_fmt)) { 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()); decompress(boot.k_fmt, fd, boot.kernel, boot.hdr->kernel_size());
close(fd); close(fd);
} else { } else {
fprintf(stderr, "Kernel is uncompressed or not a supported compressed type!\n");
dump(boot.kernel, boot.hdr->kernel_size(), KERNEL_FILE); 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(boot.kernel_dtb, boot.kernel_dt_size, KER_DTB_FILE);
// Dump ramdisk // Dump ramdisk
if (COMPRESSED(boot.r_fmt)) { 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()); decompress(boot.r_fmt, fd, boot.ramdisk, boot.hdr->ramdisk_size());
close(fd); close(fd);
} else { } else {
fprintf(stderr, "Ramdisk is uncompressed or not a supported compressed type!\n");
dump(boot.ramdisk, boot.hdr->ramdisk_size(), RAMDISK_FILE); dump(boot.ramdisk, boot.hdr->ramdisk_size(), RAMDISK_FILE);
} }
...@@ -341,23 +346,30 @@ int unpack(const char *image, bool hdr) { ...@@ -341,23 +346,30 @@ int unpack(const char *image, bool hdr) {
dump(boot.extra, boot.hdr->extra_size(), EXTRA_FILE); dump(boot.extra, boot.hdr->extra_size(), EXTRA_FILE);
// Dump recovery_dtbo // 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 dtb
dump(boot.dtb, boot.hdr->dtb_size(), DTB_FILE); dump(boot.dtb, boot.hdr->dtb_size(), DTB_FILE);
return ret;
return (boot.flags & CHROMEOS_FLAG) ? 2 : 0;
} }
#define file_align() \ #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) { void repack(const char* src_img, const char* out_img, bool nocomp) {
boot_img boot {}; 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 fprintf(stderr, "Repack to boot image: [%s]\n", out_img);
boot.parse_file(orig_image);
// Reset sizes // Reset sizes
boot.hdr->kernel_size() = 0; boot.hdr->kernel_size() = 0;
...@@ -366,65 +378,36 @@ void repack(const char* orig_image, const char* out_image, bool force_nocomp) { ...@@ -366,65 +378,36 @@ void repack(const char* orig_image, const char* out_image, bool force_nocomp) {
boot.hdr->dtb_size() = 0; boot.hdr->dtb_size() = 0;
boot.kernel_dt_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 // Create new image
int fd = creat(out_image, 0644); int fd = creat(out_img, 0644);
if (boot.flags & DHTB_FLAG) { if (boot.flags & DHTB_FLAG) {
// Skip DHTB header // Skip DHTB header
write_zero(fd, 512); write_zero(fd, sizeof(dhtb_hdr));
} else if (boot.flags & BLOB_FLAG) { } else if (boot.flags & BLOB_FLAG) {
// Skip blob header restore_buf(fd, boot.map_addr, sizeof(blob_hdr));
write_zero(fd, sizeof(blob_hdr));
} else if (boot.flags & NOOKHD_FLAG) { } else if (boot.flags & NOOKHD_FLAG) {
restore_buf(fd, boot.map_addr, NOOKHD_PRE_HEADER_SZ); restore_buf(fd, boot.map_addr, NOOKHD_PRE_HEADER_SZ);
} else if (boot.flags & ACCLAIM_FLAG) { } else if (boot.flags & ACCLAIM_FLAG) {
restore_buf(fd, boot.map_addr, ACCLAIM_PRE_HEADER_SZ); restore_buf(fd, boot.map_addr, ACCLAIM_PRE_HEADER_SZ);
} }
// header // Copy a page for header
if (access(HEADER_FILE, R_OK) == 0) { off.header = lseek(fd, 0, SEEK_CUR);
parse_prop_file(HEADER_FILE, [&](string_view key, string_view value) -> bool { restore_buf(fd, boot.hdr_addr, boot.hdr->page_size());
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());
// kernel // kernel
kernel_off = lseek(fd, 0, SEEK_CUR); off.kernel = lseek(fd, 0, SEEK_CUR);
if (boot.flags & MTK_KERNEL) { if (boot.flags & MTK_KERNEL) {
// Skip MTK header // Copy MTK headers
write_zero(fd, 512); restore_buf(fd, boot.k_hdr, sizeof(mtk_hdr));
} }
if (access(KERNEL_FILE, R_OK) == 0) { if (access(KERNEL_FILE, R_OK) == 0) {
size_t raw_size; size_t raw_size;
...@@ -433,60 +416,60 @@ void repack(const char* orig_image, const char* out_image, bool force_nocomp) { ...@@ -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)) { 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); boot.hdr->kernel_size() = compress(boot.k_fmt, fd, raw_buf, raw_size);
} else { } 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); munmap(raw_buf, raw_size);
} }
// kernel dtb // kernel dtb
if (access(KER_DTB_FILE, R_OK) == 0) 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(); file_align();
// ramdisk // ramdisk
ramdisk_off = lseek(fd, 0, SEEK_CUR); off.ramdisk = lseek(fd, 0, SEEK_CUR);
if (boot.flags & MTK_RAMDISK) { if (boot.flags & MTK_RAMDISK) {
// Skip MTK header // Copy MTK headers
write_zero(fd, 512); restore_buf(fd, boot.r_hdr, sizeof(mtk_hdr));
} }
if (access(RAMDISK_FILE, R_OK) == 0) { if (access(RAMDISK_FILE, R_OK) == 0) {
size_t raw_size; size_t raw_size;
void *raw_buf; void *raw_buf;
mmap_ro(RAMDISK_FILE, raw_buf, raw_size); 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); boot.hdr->ramdisk_size() = compress(boot.r_fmt, fd, raw_buf, raw_size);
} else { } 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); munmap(raw_buf, raw_size);
file_align(); file_align();
} }
// second // second
second_off = lseek(fd, 0, SEEK_CUR); off.second = lseek(fd, 0, SEEK_CUR);
if (access(SECOND_FILE, R_OK) == 0) { 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(); file_align();
} }
// extra // extra
extra_off = lseek(fd, 0, SEEK_CUR); off.extra = lseek(fd, 0, SEEK_CUR);
if (access(EXTRA_FILE, R_OK) == 0) { 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(); file_align();
} }
// recovery_dtbo // recovery_dtbo
if (access(RECV_DTBO_FILE, R_OK) == 0) { if (access(RECV_DTBO_FILE, R_OK) == 0) {
boot.hdr->recovery_dtbo_offset() = lseek(fd, 0, SEEK_CUR); 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(); file_align();
} }
// dtb // dtb
dtb_off = lseek(fd, 0, SEEK_CUR); off.dtb = lseek(fd, 0, SEEK_CUR);
if (access(DTB_FILE, R_OK) == 0) { 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(); file_align();
} }
...@@ -500,72 +483,73 @@ void repack(const char* orig_image, const char* out_image, bool force_nocomp) { ...@@ -500,72 +483,73 @@ void repack(const char* orig_image, const char* out_image, bool force_nocomp) {
close(fd); close(fd);
/*********************
* Patching the image
*********************/
// Map output image as rw // Map output image as rw
munmap(boot.map_addr, boot.map_size); 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 // MTK headers
if (boot.flags & MTK_KERNEL) { if (boot.flags & MTK_KERNEL) {
boot.k_hdr->size = boot.hdr->kernel_size(); auto hdr = reinterpret_cast<mtk_hdr *>(boot.map_addr + off.kernel);
boot.hdr->kernel_size() += 512; hdr->size = boot.hdr->kernel_size();
memcpy(boot.map_addr + kernel_off, boot.k_hdr, sizeof(mtk_hdr)); boot.hdr->kernel_size() += sizeof(*hdr);
} }
if (boot.flags & MTK_RAMDISK) { if (boot.flags & MTK_RAMDISK) {
boot.r_hdr->size = boot.hdr->ramdisk_size(); auto hdr = reinterpret_cast<mtk_hdr *>(boot.map_addr + off.ramdisk);
boot.hdr->ramdisk_size() += 512; hdr->size = boot.hdr->ramdisk_size();
memcpy(boot.map_addr + ramdisk_off, boot.r_hdr, sizeof(mtk_hdr)); boot.hdr->ramdisk_size() += sizeof(*hdr);
} }
// Update checksum // Update checksum
HASH_CTX ctx; HASH_CTX ctx;
(boot.flags & SHA256_FLAG) ? SHA256_init(&ctx) : SHA_init(&ctx); (boot.flags & SHA256_FLAG) ? SHA256_init(&ctx) : SHA_init(&ctx);
uint32_t size = boot.hdr->kernel_size(); 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)); HASH_update(&ctx, &size, sizeof(size));
size = boot.hdr->ramdisk_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)); HASH_update(&ctx, &size, sizeof(size));
size = boot.hdr->second_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)); HASH_update(&ctx, &size, sizeof(size));
size = boot.hdr->extra_size(); size = boot.hdr->extra_size();
if (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)); HASH_update(&ctx, &size, sizeof(size));
} }
if (boot.hdr->header_version()) { if (boot.hdr->header_version() >= 1) {
size = boot.hdr->recovery_dtbo_size(); size = boot.hdr->recovery_dtbo_size();
HASH_update(&ctx, boot.map_addr + boot.hdr->recovery_dtbo_offset(), size); HASH_update(&ctx, boot.map_addr + boot.hdr->recovery_dtbo_offset(), size);
HASH_update(&ctx, &size, sizeof(size)); HASH_update(&ctx, &size, sizeof(size));
}
if (boot.hdr->header_version() >= 2) {
size = boot.hdr->dtb_size(); size = boot.hdr->dtb_size();
if (size) { HASH_update(&ctx, boot.map_addr + off.dtb, size);
HASH_update(&ctx, boot.map_addr + dtb_off, size);
HASH_update(&ctx, &size, sizeof(size)); HASH_update(&ctx, &size, sizeof(size));
} }
}
memset(boot.hdr->id(), 0, 32); memset(boot.hdr->id(), 0, 32);
memcpy(boot.hdr->id(), HASH_final(&ctx), memcpy(boot.hdr->id(), HASH_final(&ctx),
(boot.flags & SHA256_FLAG) ? SHA256_DIGEST_SIZE : SHA_DIGEST_SIZE); (boot.flags & SHA256_FLAG) ? SHA256_DIGEST_SIZE : SHA_DIGEST_SIZE);
boot.hdr->header_size() = boot.hdr->hdr_size();
// Print new image info // Print new image info
boot.print_hdr(); boot.hdr->print();
// Try to fix the header
if (boot.hdr->header_version() && boot.hdr->header_size() == 0)
boot.hdr->header_size() = sizeof(boot_img_hdr);
// Main header // 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) { if (boot.flags & DHTB_FLAG) {
// DHTB header // 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); memcpy(hdr, DHTB_MAGIC, 8);
hdr->size = boot.map_size - 512; hdr->size = boot.map_size - sizeof(dhtb_hdr);
SHA256_hash(boot.map_addr + 512, hdr->size, hdr->checksum); SHA256_hash(boot.map_addr + sizeof(dhtb_hdr), hdr->size, hdr->checksum);
} else if (boot.flags & BLOB_FLAG) { } else if (boot.flags & BLOB_FLAG) {
// Blob headers // Blob header
boot.b_hdr->size = boot.map_size - sizeof(blob_hdr); auto hdr = reinterpret_cast<blob_hdr *>(boot.map_addr);
memcpy(boot.map_addr, boot.b_hdr, sizeof(blob_hdr)); hdr->size = boot.map_size - sizeof(blob_hdr);
} }
} }
...@@ -4,6 +4,47 @@ ...@@ -4,6 +4,47 @@
#include <utility> #include <utility>
#include "format.h" #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 { struct boot_img_hdr_base {
char magic[8]; char magic[8];
...@@ -57,12 +98,12 @@ struct boot_img_hdr_v2 : public boot_img_hdr_v1 { ...@@ -57,12 +98,12 @@ struct boot_img_hdr_v2 : public boot_img_hdr_v1 {
} __attribute__((packed)); } __attribute__((packed));
// Default to hdr v2 // Default to hdr v2
typedef boot_img_hdr_v2 boot_img_hdr; using boot_img_hdr = boot_img_hdr_v2;
// Special Samsung header // Special Samsung header
struct boot_img_hdr_pxa : public boot_img_hdr_base { struct boot_img_hdr_pxa : public boot_img_hdr_base {
uint32_t extra_size; /* extra blob size in bytes */ 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 tags_addr; /* physical addr for kernel tags */
uint32_t page_size; /* flash page size we assume */ uint32_t page_size; /* flash page size we assume */
...@@ -111,34 +152,6 @@ struct boot_img_hdr_pxa : public boot_img_hdr_base { ...@@ -111,34 +152,6 @@ struct boot_img_hdr_pxa : public boot_img_hdr_base {
** else: jump to kernel_addr ** 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) \ #define drct_var(name) \
auto &name() { return img_hdr->name; } auto &name() { return img_hdr->name; }
#define decl_var(name, len) \ #define decl_var(name, len) \
...@@ -179,6 +192,10 @@ struct dyn_img_hdr { ...@@ -179,6 +192,10 @@ struct dyn_img_hdr {
return img_hdr; return img_hdr;
} }
void print();
void dump_hdr_file();
void load_hdr_file();
protected: protected:
union { union {
/* Main header could be either AOSP or PXA, /* Main header could be either AOSP or PXA,
...@@ -199,10 +216,22 @@ private: ...@@ -199,10 +216,22 @@ private:
#undef decl_var #undef decl_var
#undef decl_val #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) \ #define impl_val(name) \
decltype(std::declval<dyn_img_hdr>().name()) name() override { return hdr_pxa->name; } decltype(std::declval<dyn_img_hdr>().name()) name() override { return hdr_pxa->name; }
struct dyn_img_pxa : public dyn_img_hdr { struct dyn_img_pxa : public dyn_img_hdr {
impl_cls(pxa)
impl_val(extra_size) impl_val(extra_size)
impl_val(page_size) impl_val(page_size)
...@@ -210,15 +239,6 @@ struct dyn_img_pxa : public dyn_img_hdr { ...@@ -210,15 +239,6 @@ struct dyn_img_pxa : public dyn_img_hdr {
impl_val(cmdline) impl_val(cmdline)
impl_val(id) impl_val(id)
impl_val(extra_cmdline) 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 #undef impl_val
...@@ -226,6 +246,7 @@ struct dyn_img_pxa : public dyn_img_hdr { ...@@ -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; } decltype(std::declval<dyn_img_hdr>().name()) name() override { return v2_hdr->name; }
struct dyn_img_v0 : public dyn_img_hdr { struct dyn_img_v0 : public dyn_img_hdr {
impl_cls(v0)
impl_val(page_size) impl_val(page_size)
impl_val(extra_size) impl_val(extra_size)
...@@ -234,90 +255,86 @@ struct dyn_img_v0 : public dyn_img_hdr { ...@@ -234,90 +255,86 @@ struct dyn_img_v0 : public dyn_img_hdr {
impl_val(cmdline) impl_val(cmdline)
impl_val(id) impl_val(id)
impl_val(extra_cmdline) 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 { struct dyn_img_v1 : public dyn_img_v0 {
impl_cls(v1)
impl_val(header_version) impl_val(header_version)
impl_val(recovery_dtbo_size) impl_val(recovery_dtbo_size)
impl_val(recovery_dtbo_offset) impl_val(recovery_dtbo_offset)
impl_val(header_size) impl_val(header_size)
dyn_img_v1(void *ptr) : dyn_img_v0(ptr) {}
uint32_t &extra_size() override { uint32_t &extra_size() override {
return dyn_img_hdr::extra_size(); return dyn_img_hdr::extra_size();
} }
}; };
struct dyn_img_v2 : public dyn_img_v1 { struct dyn_img_v2 : public dyn_img_v1 {
impl_cls(v2)
impl_val(dtb_size) impl_val(dtb_size)
dyn_img_v2(void *ptr) : dyn_img_v1(ptr) {}
}; };
#undef __impl_cls
#undef impl_cls
#undef impl_val #undef impl_val
// Flags // Flags
#define MTK_KERNEL 1 << 1 #define MTK_KERNEL (1 << 0)
#define MTK_RAMDISK 1 << 2 #define MTK_RAMDISK (1 << 1)
#define CHROMEOS_FLAG 1 << 3 #define CHROMEOS_FLAG (1 << 2)
#define DHTB_FLAG 1 << 4 #define DHTB_FLAG (1 << 3)
#define SEANDROID_FLAG 1 << 5 #define SEANDROID_FLAG (1 << 4)
#define LG_BUMP_FLAG 1 << 6 #define LG_BUMP_FLAG (1 << 5)
#define SHA256_FLAG 1 << 7 #define SHA256_FLAG (1 << 6)
#define BLOB_FLAG 1 << 8 #define BLOB_FLAG (1 << 7)
#define NOOKHD_FLAG 1 << 9 #define NOOKHD_FLAG (1 << 8)
#define ACCLAIM_FLAG 1 << 10 #define ACCLAIM_FLAG (1 << 9)
struct boot_img { struct boot_img {
// Memory map of the whole image // Memory map of the whole image
uint8_t *map_addr; uint8_t *map_addr;
size_t map_size; size_t map_size;
// Headers // Android image header
dyn_img_hdr *hdr; /* Android image header */ dyn_img_hdr *hdr;
mtk_hdr *k_hdr; /* MTK kernel header */
mtk_hdr *r_hdr; /* MTK ramdisk header */
blob_hdr *b_hdr; /* Tegra blob header */
// Flags to indicate the state of current boot image // Flags to indicate the state of current boot image
uint16_t flags; uint16_t flags = 0;
// The format of kernel and ramdisk // The format of kernel and ramdisk
format_t k_fmt; format_t k_fmt;
format_t r_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 // Pointer to dtb that is appended after kernel
uint8_t *kernel_dtb; uint8_t *kernel_dtb;
uint32_t kernel_dt_size; uint32_t kernel_dt_size = 0;
// Pointer to end of image // Pointer to end of image
uint8_t *tail; uint8_t *tail;
size_t tail_size; size_t tail_size = 0;
// Pointers to blocks defined in header // Pointers to blocks defined in header
uint8_t *hdr_addr;
uint8_t *kernel; uint8_t *kernel;
uint8_t *ramdisk; uint8_t *ramdisk;
uint8_t *second; uint8_t *second;
uint8_t *extra; uint8_t *extra;
uint8_t *recov_dtbo; uint8_t *recovery_dtbo;
uint8_t *dtb; uint8_t *dtb;
boot_img(const char *);
~boot_img(); ~boot_img();
int parse_file(const char *); void parse_image(uint8_t *addr);
int parse_image(uint8_t *); void find_kernel_dtb();
void find_dtb();
void print_hdr();
}; };
...@@ -62,7 +62,7 @@ void decompress(char *infile, const char *outfile) { ...@@ -62,7 +62,7 @@ void decompress(char *infile, const char *outfile) {
out_fd = strcmp(outfile, "-") == 0 ? out_fd = strcmp(outfile, "-") == 0 ?
STDOUT_FILENO : xopen(outfile, O_WRONLY | O_CREAT | O_TRUNC, 0644); 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 (ext) *ext = '.';
} }
if (!cmp->write(buf, len)) if (!cmp->write(buf, len))
...@@ -108,7 +108,7 @@ void compress(const char *method, const char *infile, const char *outfile) { ...@@ -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); 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 { read_file(in_file, [&](void *buf, size_t len) -> void {
if (!cmp->write(buf, len)) if (!cmp->write(buf, len))
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
#include <lz4.h> #include <lz4.h>
#include <lz4frame.h> #include <lz4frame.h>
#include <lz4hc.h> #include <lz4hc.h>
#include <OutStream.h> #include <stream.h>
#include "format.h" #include "format.h"
......
...@@ -7,12 +7,102 @@ extern "C" { ...@@ -7,12 +7,102 @@ extern "C" {
#include <utils.h> #include <utils.h>
#include <bitset> #include <bitset>
#include <vector> #include <vector>
#include <map>
#include "magiskboot.h" #include "magiskboot.h"
#include "format.h"
using namespace std; 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; constexpr int MAX_DEPTH = 32;
static bitset<MAX_DEPTH> depth_set; static bitset<MAX_DEPTH> depth_set;
...@@ -116,35 +206,27 @@ static void dtb_print(const char *file, bool fstab) { ...@@ -116,35 +206,27 @@ static void dtb_print(const char *file, bool fstab) {
print_node(fdt); print_node(fdt);
} }
++dtb_num; ++dtb_num;
i += fdt_totalsize(fdt) - 1;
} }
} }
fprintf(stderr, "\n"); fprintf(stderr, "\n");
munmap(dtb, size); 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 keepverity = check_env("KEEPVERITY");
bool redirect = check_env("TWOSTAGEINIT"); 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; 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); int fstab = find_fstab(fdt);
if (fstab < 0) if (fstab < 0)
continue; continue;
fprintf(stderr, "Found fstab in dtb.%04d\n", fdt_list.size()); fprintf(stderr, "Found fstab in dtb.%04d\n", idx - 1);
int block; int block;
fdt_for_each_subnode(block, fdt, fstab) { fdt_for_each_subnode(block, fdt, fstab) {
const char *name = fdt_get_name(fdt, block, nullptr); const char *name = fdt_get_name(fdt, block, nullptr);
...@@ -165,21 +247,153 @@ static void dtb_patch(const char *in, const char *out) { ...@@ -165,21 +247,153 @@ static void dtb_patch(const char *in, const char *out) {
fdt_setprop_string(fdt, block, "mnt_point", "/system_root"); 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) if (!out)
out = in; 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) { for (auto fdt : fdt_list) {
fdt_pack(fdt); fdt_pack(fdt);
xwrite(fd, fdt, fdt_totalsize(fdt)); xwrite(fd, fdt, fdt_totalsize(fdt));
free(fdt);
} }
close(fd); close(fd);
} }
exit(!modified); return 0;
} }
int dtb_commands(int argc, char *argv[]) { int dtb_commands(int argc, char *argv[]) {
...@@ -189,11 +403,12 @@ int dtb_commands(int argc, char *argv[]) { ...@@ -189,11 +403,12 @@ int dtb_commands(int argc, char *argv[]) {
if (argv[0] == "print"sv) { if (argv[0] == "print"sv) {
dtb_print(dtb, argc > 1 && argv[1] == "-f"sv); dtb_print(dtb, argc > 1 && argv[1] == "-f"sv);
return 0;
} else if (argv[0] == "patch"sv) { } else if (argv[0] == "patch"sv) {
dtb_patch(dtb, argv[1]); if (dtb_patch(dtb, argv[1]))
exit(1);
return 0;
} else { } else {
return 1; return 1;
} }
return 0;
} }
...@@ -44,8 +44,6 @@ format_t check_fmt(const void *buf, size_t len) { ...@@ -44,8 +44,6 @@ format_t check_fmt(const void *buf, size_t len) {
return LZ4_LEGACY; return LZ4_LEGACY;
} else if (MATCH(MTK_MAGIC)) { } else if (MATCH(MTK_MAGIC)) {
return MTK; return MTK;
} else if (MATCH(DTB_MAGIC)) {
return DTB;
} else if (MATCH(DHTB_MAGIC)) { } else if (MATCH(DHTB_MAGIC)) {
return DHTB; return DHTB;
} else if (MATCH(TEGRABLOB_MAGIC)) { } else if (MATCH(TEGRABLOB_MAGIC)) {
...@@ -77,8 +75,6 @@ const char *Fmt2Name::operator[](format_t fmt) { ...@@ -77,8 +75,6 @@ const char *Fmt2Name::operator[](format_t fmt) {
return "lz4_legacy"; return "lz4_legacy";
case MTK: case MTK:
return "mtk"; return "mtk";
case DTB:
return "dtb";
default: default:
return "raw"; return "raw";
} }
......
...@@ -38,7 +38,6 @@ typedef enum { ...@@ -38,7 +38,6 @@ typedef enum {
#define LZ41_MAGIC "\x03\x21\x4c\x18" #define LZ41_MAGIC "\x03\x21\x4c\x18"
#define LZ42_MAGIC "\x04\x22\x4d\x18" #define LZ42_MAGIC "\x04\x22\x4d\x18"
#define MTK_MAGIC "\x88\x16\x88\x58" #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 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 DHTB_MAGIC "\x44\x48\x54\x42\x01\x00\x00\x00"
#define SEANDROID_MAGIC "SEANDROIDENFORCE" #define SEANDROID_MAGIC "SEANDROIDENFORCE"
......
...@@ -13,11 +13,11 @@ ...@@ -13,11 +13,11 @@
#define NEW_BOOT "new-boot.img" #define NEW_BOOT "new-boot.img"
int unpack(const char *image, bool hdr = false); 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 hexpatch(const char *image, const char *from, const char *to);
int cpio_commands(int argc, char *argv[]); int cpio_commands(int argc, char *argv[]);
int dtb_commands(int argc, char *argv[]); int dtb_commands(int argc, char *argv[]);
char *patch_verity(const 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); void patch_encryption(void *buf, uint32_t &size);
bool check_env(const char *name); bool check_env(const char *name);
...@@ -22,20 +22,20 @@ static int check_verity_pattern(const char *s) { ...@@ -22,20 +22,20 @@ static int check_verity_pattern(const char *s) {
} }
static int check_encryption_pattern(const char *s) { static int check_encryption_pattern(const char *s) {
const char *encrypt_list[] = { "forceencrypt", "forcefdeorfbe", nullptr }; static const char *encrypt_list[] = { "forceencrypt", "forcefdeorfbe" };
for (int i = 0 ; encrypt_list[i]; ++i) { for (auto enc : encrypt_list) {
int len = strlen(encrypt_list[i]); int len = strlen(enc);
if (strncmp(s, encrypt_list[i], len) == 0) if (strncmp(s, enc, len) == 0)
return len; return len;
} }
return -1; 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); auto src = static_cast<const char *>(buf);
int src_size = size; int src_size = size;
bool found = false; bool found = false;
char patched[4096]; auto patched = (char *)(inplace ? buf : xmalloc(size));
int write = 0; int write = 0;
for (int read = 0; read < src_size; ++read, ++write) { for (int read = 0; read < src_size; ++read, ++write) {
if (int skip; (skip = check_verity_pattern(src + read)) > 0) { if (int skip; (skip = check_verity_pattern(src + read)) > 0) {
...@@ -47,24 +47,25 @@ char *patch_verity(const void *buf, uint32_t &size) { ...@@ -47,24 +47,25 @@ char *patch_verity(const void *buf, uint32_t &size) {
patched[write] = src[read]; patched[write] = src[read];
} }
patched[write] = '\0'; 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) { void patch_encryption(void *buf, uint32_t &size) {
int skip, src_size = *size; auto src = static_cast<char *>(buf);
char *src = (char *) *buf, *patched = (char *) xcalloc(src_size, 1); int src_size = size;
for (int read = 0, write = 0; read < src_size; ++read, ++write) { int write = 0;
if ((skip = check_encryption_pattern(src + read)) > 0) { for (int read = 0; read < src_size; ++read, ++write) {
fprintf(stderr, "Replace pattern [%.*s] with [encryptable]\n", skip, src + read); if (int skip; (skip = check_encryption_pattern(src + read)) > 0) {
memcpy(patched + read, "encryptable", 11); fprintf(stderr, "Found pattern [%.*s]\n", skip, src + read);
size -= skip;
read += skip; read += skip;
write += 11;
*size -= (skip - 11);
} }
patched[write] = src[read]; src[write] = src[read];
} }
free(*buf); src[write] = '\0';
*buf = patched;
} }
...@@ -52,11 +52,7 @@ void magisk_cpio::patch() { ...@@ -52,11 +52,7 @@ void magisk_cpio::patch() {
if (!keepverity) { if (!keepverity) {
if (fstab) { if (fstab) {
fprintf(stderr, "Found fstab file [%s]\n", cur->first.data()); fprintf(stderr, "Found fstab file [%s]\n", cur->first.data());
auto buf = patch_verity(cur->second->data, cur->second->filesize); patch_verity(cur->second->data, cur->second->filesize, true);
if (buf) {
free(cur->second->data);
cur->second->data = buf;
}
} else if (cur->first == "verity_key") { } else if (cur->first == "verity_key") {
rm(cur); rm(cur);
continue; continue;
...@@ -64,7 +60,7 @@ void magisk_cpio::patch() { ...@@ -64,7 +60,7 @@ void magisk_cpio::patch() {
} }
if (!keepforceencrypt) { if (!keepforceencrypt) {
if (fstab) { 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() { ...@@ -246,13 +242,13 @@ void magisk_cpio::compress() {
fprintf(stderr, "Compressing cpio -> [%s]\n", RAMDISK_XZ); fprintf(stderr, "Compressing cpio -> [%s]\n", RAMDISK_XZ);
auto init = entries.extract("init"); auto init = entries.extract("init");
XZEncoder encoder; XZEncoder encoder;
encoder.set_out(make_unique<BufOutStream>()); encoder.setOut(make_unique<BufOutStream>());
output(encoder); output(encoder);
encoder.finalize(); encoder.finalize();
entries.clear(); entries.clear();
entries.insert(std::move(init)); entries.insert(std::move(init));
auto xz = new cpio_entry(RAMDISK_XZ, S_IFREG); 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); insert(xz);
} }
...@@ -262,13 +258,13 @@ void magisk_cpio::decompress() { ...@@ -262,13 +258,13 @@ void magisk_cpio::decompress() {
return; return;
fprintf(stderr, "Decompressing cpio [%s]\n", RAMDISK_XZ); fprintf(stderr, "Decompressing cpio [%s]\n", RAMDISK_XZ);
LZMADecoder decoder; LZMADecoder decoder;
decoder.set_out(make_unique<BufOutStream>()); decoder.setOut(make_unique<BufOutStream>());
decoder.write(it->second->data, it->second->filesize); decoder.write(it->second->data, it->second->filesize);
decoder.finalize(); decoder.finalize();
entries.erase(it); entries.erase(it);
char *buf; char *buf;
size_t sz; size_t sz;
static_cast<BufOutStream *>(decoder.get_out())->getbuf(buf, sz); static_cast<BufOutStream *>(decoder.getOut())->getbuf(buf, sz);
load_cpio(buf, sz); load_cpio(buf, sz);
} }
......
...@@ -36,7 +36,7 @@ static inline void lazy_unmount(const char* mountpoint) { ...@@ -36,7 +36,7 @@ static inline void lazy_unmount(const char* mountpoint) {
} }
void hide_daemon(int pid) { void hide_daemon(int pid) {
RunFinally fin([=]() -> void { run_finally fin([=]() -> void {
// Send resume signal // Send resume signal
kill(pid, SIGCONT); kill(pid, SIGCONT);
_exit(0); _exit(0);
......
...@@ -98,7 +98,7 @@ static int add_list(const char *pkg, const char *proc = "") { ...@@ -98,7 +98,7 @@ static int add_list(const char *pkg, const char *proc = "") {
// Critical region // Critical region
{ {
MutexGuard lock(monitor_lock); mutex_guard lock(monitor_lock);
hide_set.emplace(pkg, proc); hide_set.emplace(pkg, proc);
} }
...@@ -119,7 +119,7 @@ int add_list(int client) { ...@@ -119,7 +119,7 @@ int add_list(int client) {
static int rm_list(const char *pkg, const char *proc = "") { static int rm_list(const char *pkg, const char *proc = "") {
{ {
// Critical region // Critical region
MutexGuard lock(monitor_lock); mutex_guard lock(monitor_lock);
bool remove = false; bool remove = false;
auto next = hide_set.begin(); auto next = hide_set.begin();
decltype(next) cur; decltype(next) cur;
......
...@@ -76,7 +76,7 @@ static inline long xptrace(int request, pid_t pid, void *addr = nullptr, intptr_ ...@@ -76,7 +76,7 @@ static inline long xptrace(int request, pid_t pid, void *addr = nullptr, intptr_
} }
void update_uid_map() { void update_uid_map() {
MutexGuard lock(monitor_lock); mutex_guard lock(monitor_lock);
uid_proc_map.clear(); uid_proc_map.clear();
string data_path(APP_DATA_DIR); string data_path(APP_DATA_DIR);
data_path += "/0/"; data_path += "/0/";
...@@ -336,7 +336,7 @@ void proc_monitor() { ...@@ -336,7 +336,7 @@ void proc_monitor() {
continue; continue;
} }
bool detach = false; bool detach = false;
RunFinally detach_task([&] { run_finally f([&] {
if (detach) if (detach)
// Non of our business now // Non of our business now
detach_pid(pid); detach_pid(pid);
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
#include <memory> #include <memory>
#include <db.h> #include <db.h>
#include <utils.h>
#define DEFAULT_SHELL "/system/bin/sh" #define DEFAULT_SHELL "/system/bin/sh"
...@@ -29,8 +30,7 @@ public: ...@@ -29,8 +30,7 @@ public:
su_info(unsigned uid = 0); su_info(unsigned uid = 0);
~su_info(); ~su_info();
void lock(); mutex_guard lock();
void unlock();
bool is_fresh(); bool is_fresh();
void refresh(); void refresh();
......
...@@ -33,12 +33,8 @@ su_info::~su_info() { ...@@ -33,12 +33,8 @@ su_info::~su_info() {
pthread_mutex_destroy(&_lock); pthread_mutex_destroy(&_lock);
} }
void su_info::lock() { mutex_guard su_info::lock() {
pthread_mutex_lock(&_lock); return mutex_guard(_lock);
}
void su_info::unlock() {
pthread_mutex_unlock(&_lock);
} }
bool su_info::is_fresh() { bool su_info::is_fresh() {
...@@ -89,17 +85,14 @@ static shared_ptr<su_info> get_su_info(unsigned uid) { ...@@ -89,17 +85,14 @@ static shared_ptr<su_info> get_su_info(unsigned uid) {
shared_ptr<su_info> info; shared_ptr<su_info> info;
{ {
MutexGuard lock(cache_lock); mutex_guard lock(cache_lock);
if (!cached || cached->uid != uid || !cached->is_fresh()) if (!cached || cached->uid != uid || !cached->is_fresh())
cached = make_shared<su_info>(uid); cached = make_shared<su_info>(uid);
cached->refresh(); cached->refresh();
info = cached; info = cached;
} }
info->lock(); auto g = info->lock();
RunFinally unlock([&] {
info->unlock();
});
if (info->access.policy == QUERY) { if (info->access.policy == QUERY) {
// Not cached, get data from database // Not cached, get data from database
......
...@@ -2,41 +2,41 @@ ...@@ -2,41 +2,41 @@
#include <pthread.h> #include <pthread.h>
#include <deque> #include <deque>
#include <misc.h>
template<typename T> template<typename T>
class BlockingQueue { class blocking_queue {
std::deque<T> deque{}; std::deque<T> deque{};
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER; pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
bool cancelled = false; bool cancelled = false;
public: public:
~BlockingQueue(); ~blocking_queue();
T take(); T take();
T &front(); T &front() const;
T &back(); T &back() const;
void put(const T&); void push(const T&);
void put(T&&); void push(T&&);
template< class... Args > template< class... Args >
void emplace_back(Args&&... args); void emplace(Args &&... args);
void clear(); void clear();
void cancel(); void cancel();
}; };
#define run_and_notify(block) \ #define run_and_notify(block) \
pthread_mutex_lock(&this->lock); \ mutex_guard g(this->lock); \
block \ block \
pthread_cond_signal(&this->cond); \ pthread_cond_signal(&this->cond);
pthread_mutex_unlock(&this->lock);
template<typename T> template<typename T>
BlockingQueue<T>::~BlockingQueue() { blocking_queue<T>::~blocking_queue() {
pthread_mutex_destroy(&lock); pthread_mutex_destroy(&lock);
pthread_cond_destroy(&cond); pthread_cond_destroy(&cond);
} }
template<typename T> template<typename T>
T BlockingQueue<T>::take() { T blocking_queue<T>::take() {
pthread_mutex_lock(&lock); mutex_guard g(lock);
cancelled = false; cancelled = false;
while (deque.empty() && !cancelled) while (deque.empty() && !cancelled)
pthread_cond_wait(&cond, &lock); pthread_cond_wait(&cond, &lock);
...@@ -44,55 +44,51 @@ T BlockingQueue<T>::take() { ...@@ -44,55 +44,51 @@ T BlockingQueue<T>::take() {
pthread_exit(nullptr); pthread_exit(nullptr);
T ret(std::move(deque.front())); T ret(std::move(deque.front()));
deque.pop_front(); deque.pop_front();
pthread_mutex_unlock(&lock);
return ret; return ret;
} }
template<typename T> 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); }) run_and_notify({ deque.push_back(s); })
} }
template<typename T> 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)); }) run_and_notify({ deque.push_back(std::move(s)); })
} }
template<typename T> template<typename T>
T &BlockingQueue<T>::front() { T &blocking_queue<T>::front() const {
pthread_mutex_lock(&lock); mutex_guard g(lock);
auto &ret = deque.front(); return deque.front();
pthread_mutex_unlock(&lock);
return ret;
} }
template<typename T> template<typename T>
T &BlockingQueue<T>::back() { T &blocking_queue<T>::back() const {
pthread_mutex_lock(&lock); mutex_guard g(lock);
auto &ret = deque.back(); return deque.back();
pthread_mutex_unlock(&lock);
return ret;
} }
template<typename T> template<typename T>
template<class... Args> 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)...); }) run_and_notify({ deque.emplace_back(std::forward<Args>(args)...); })
} }
template<typename T> template<typename T>
void BlockingQueue<T>::clear() { void blocking_queue<T>::clear() {
pthread_mutex_lock(&lock); mutex_guard g(lock);
std::deque<T> t; std::deque<T> t;
deque.swap(t); deque.swap(t);
pthread_mutex_unlock(&lock);
} }
template<typename T> template<typename T>
void BlockingQueue<T>::cancel() { void blocking_queue<T>::cancel() {
run_and_notify({ run_and_notify({
cancelled = true; cancelled = true;
std::deque<T> t; std::deque<T> t;
deque.swap(t); deque.swap(t);
}) })
} }
#undef run_and_notify
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
#include <map> #include <map>
#include <string_view> #include <string_view>
#include <OutStream.h> #include <stream.h>
struct cpio_newc_header; struct cpio_newc_header;
......
...@@ -19,9 +19,9 @@ public: ...@@ -19,9 +19,9 @@ public:
FilterOutStream(strm_ptr &&ptr) : out(std::move(ptr)) {} 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 { bool write(const void *buf, size_t len) override {
return out ? out->write(buf, len) : false; return out ? out->write(buf, len) : false;
......
...@@ -29,7 +29,7 @@ void no_logging() { ...@@ -29,7 +29,7 @@ void no_logging() {
#define LOG_TAG "Magisk" #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); return __android_log_vprint(ANDROID_LOG_DEBUG, LOG_TAG, fmt, ap);
} }
......
...@@ -181,3 +181,20 @@ int parse_int(const char *s) { ...@@ -181,3 +181,20 @@ int parse_int(const char *s) {
} }
return val; 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); ...@@ -14,6 +14,7 @@ char *rtrim(char *str);
void init_argv0(int argc, char **argv); void init_argv0(int argc, char **argv);
void set_nice_name(const char *name); void set_nice_name(const char *name);
int parse_int(const char *s); int parse_int(const char *s);
uint32_t binary_gcd(uint32_t u, uint32_t v);
#ifdef __cplusplus #ifdef __cplusplus
} }
...@@ -27,17 +28,17 @@ void gen_rand_str(char *buf, int len, bool varlen = true); ...@@ -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_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) #define str_starts(s, ss) ((ss) != nullptr && (s).compare(0, strlen(ss), ss) == 0)
class MutexGuard { class mutex_guard {
public: public:
explicit MutexGuard(pthread_mutex_t &m): mutex(&m) { explicit mutex_guard(pthread_mutex_t &m): mutex(&m) {
pthread_mutex_lock(mutex); 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); pthread_mutex_lock(mutex);
} }
~MutexGuard() { ~mutex_guard() {
pthread_mutex_unlock(mutex); pthread_mutex_unlock(mutex);
} }
...@@ -45,13 +46,13 @@ private: ...@@ -45,13 +46,13 @@ private:
pthread_mutex_t *mutex; pthread_mutex_t *mutex;
}; };
class RunFinally { class run_finally {
public: 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; } void disable() { fn = nullptr; }
~RunFinally() { if (fn) fn(); } ~run_finally() { if (fn) fn(); }
private: private:
std::function<void ()> fn; std::function<void ()> fn;
......
...@@ -31,7 +31,7 @@ if [ ! -f /system/build.prop ]; then ...@@ -31,7 +31,7 @@ if [ ! -f /system/build.prop ]; then
cd "`dirname "$0"`/.." cd "`dirname "$0"`/.."
adb push native/out/x86/busybox scripts/emulator.sh /data/local/tmp adb push native/out/x86/busybox scripts/emulator.sh /data/local/tmp
emu_arch=`adb shell uname -m` 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 adb push native/out/x86/magiskinit64 /data/local/tmp/magiskinit
else else
adb push native/out/x86/magiskinit /data/local/tmp adb push native/out/x86/magiskinit /data/local/tmp
......
...@@ -3,6 +3,7 @@ apply plugin: 'com.android.library' ...@@ -3,6 +3,7 @@ apply plugin: 'com.android.library'
android { android {
defaultConfig { defaultConfig {
vectorDrawables.useSupportLibrary = true vectorDrawables.useSupportLibrary = true
consumerProguardFiles 'proguard-rules.pro'
} }
} }
......
...@@ -19,3 +19,7 @@ ...@@ -19,3 +19,7 @@
# If you keep the line number information, uncomment this to # If you keep the line number information, uncomment this to
# hide the original source file name. # hide the original source file name.
#-renamesourcefileattribute SourceFile #-renamesourcefileattribute SourceFile
-keepclassmembers class * implements javax.net.ssl.SSLSocketFactory {
** delegate;
}
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.topjohnwu.shared"> 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.REQUEST_INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
...@@ -9,6 +10,12 @@ ...@@ -9,6 +10,12 @@
android:installLocation="internalOnly" android:installLocation="internalOnly"
android:label="@string/app_name" android:label="@string/app_name"
android:supportsRtl="true"> android:supportsRtl="true">
<activity
android:name="a.r"
android:theme="@android:style/Theme.Translucent.NoTitleBar"
android:process=":remote" />
<provider <provider
android:name="a.p" android:name="a.p"
android:authorities="${applicationId}.provider" 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 { ...@@ -35,7 +35,7 @@ public class Networking {
return request(url, "GET"); return request(url, "GET");
} }
public static void init(Context context) { public static boolean init(Context context) {
try { try {
// Try installing new SSL provider from Google Play Service // Try installing new SSL provider from Google Play Service
Context gms = context.createPackageContext("com.google.android.gms", Context gms = context.createPackageContext("com.google.android.gms",
...@@ -45,11 +45,15 @@ public class Networking { ...@@ -45,11 +45,15 @@ public class Networking {
.getMethod("insertProvider", Context.class) .getMethod("insertProvider", Context.class)
.invoke(null, gms); .invoke(null, gms);
} catch (Exception e) { } catch (Exception e) {
if (Build.VERSION.SDK_INT < 21) {
// Failed to update SSL provider, use NoSSLv3SocketFactory on SDK < 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()); HttpsURLConnection.setDefaultSSLSocketFactory(new NoSSLv3SocketFactory());
return false;
} }
} }
return true;
}
public static boolean checkNetworkStatus(Context context) { public static boolean checkNetworkStatus(Context context) {
ConnectivityManager manager = (ConnectivityManager) ConnectivityManager manager = (ConnectivityManager)
......
...@@ -11,18 +11,18 @@ import javax.net.ssl.HttpsURLConnection; ...@@ -11,18 +11,18 @@ import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory; 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 @Override
public String[] getDefaultCipherSuites() { public String[] getDefaultCipherSuites() {
return base.getDefaultCipherSuites(); return delegate.getDefaultCipherSuites();
} }
@Override @Override
public String[] getSupportedCipherSuites() { public String[] getSupportedCipherSuites() {
return base.getSupportedCipherSuites(); return delegate.getSupportedCipherSuites();
} }
private Socket createSafeSocket(Socket socket) { private Socket createSafeSocket(Socket socket) {
...@@ -40,31 +40,31 @@ class NoSSLv3SocketFactory extends SSLSocketFactory { ...@@ -40,31 +40,31 @@ class NoSSLv3SocketFactory extends SSLSocketFactory {
@Override @Override
public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException { 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 @Override
public Socket createSocket() throws IOException { public Socket createSocket() throws IOException {
return createSafeSocket(base.createSocket()); return createSafeSocket(delegate.createSocket());
} }
@Override @Override
public Socket createSocket(String host, int port) throws IOException { public Socket createSocket(String host, int port) throws IOException {
return createSafeSocket(base.createSocket(host, port)); return createSafeSocket(delegate.createSocket(host, port));
} }
@Override @Override
public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException { 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 @Override
public Socket createSocket(InetAddress host, int port) throws IOException { public Socket createSocket(InetAddress host, int port) throws IOException {
return createSafeSocket(base.createSocket(host, port)); return createSafeSocket(delegate.createSocket(host, port));
} }
@Override @Override
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException { 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> <resources>
<!--Not translatable--> <string name="upgrade_msg">Qurmanı sonlandırmaq üçün full Magisk Manager`ə yüksəldin. Yüklənib qurulsun?</string>
<string name="app_name" translatable="false">Magisk Manager</string> <string name="no_internet_msg">Lütfən internetə qoşulun! Full Magisk Manager\'ə yüksəltmə lazımidir.</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">Yox, sağolun</string> <string name="no_thanks">Yox, sağolun</string>
<string name="yes">Bəli</string> <string name="yes">Bəli</string>
<string name="ok">OK</string> <string name="ok">OK</string>
......
...@@ -2,4 +2,6 @@ ...@@ -2,4 +2,6 @@
<string name="no_thanks">Не, благодаря.</string> <string name="no_thanks">Не, благодаря.</string>
<string name="yes">Да</string> <string name="yes">Да</string>
<string name="ok">OK</string> <string name="ok">OK</string>
<string name="upgrade_msg">Надградете до пълната версия на Magisk Manager, за да довършите първоначалната настройка. Изтегляне и инсталиране сега?</string>
<string name="no_internet_msg">Моля да се свържете към работеща интернет мрежа, защото надграждането до пълната версия на Magisk Manager е задължително.</string>
</resources> </resources>
...@@ -3,5 +3,7 @@ ...@@ -3,5 +3,7 @@
<string name="no_thanks">No, gràcies</string> <string name="no_thanks">No, gràcies</string>
<string name="yes"></string> <string name="yes"></string>
<string name="ok">Ok</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> </resources>
...@@ -2,4 +2,6 @@ ...@@ -2,4 +2,6 @@
<string name="no_thanks">Nein danke</string> <string name="no_thanks">Nein danke</string>
<string name="yes">Ja</string> <string name="yes">Ja</string>
<string name="ok">OK</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> </resources>
...@@ -2,4 +2,6 @@ ...@@ -2,4 +2,6 @@
<string name="no_thanks">No gracias</string> <string name="no_thanks">No gracias</string>
<string name="yes"></string> <string name="yes"></string>
<string name="ok">Aceptar</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> </resources>
<?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<!--Used in both stub and full app--> <!--Used in both stub and full app-->
<string name="no_thanks">Tänan ei</string> <string name="no_thanks">Tänan ei</string>
<string name="yes">Jah</string> <string name="yes">Jah</string>
<string name="ok">OK</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> </resources>
\ No newline at end of file
...@@ -4,4 +4,6 @@ ...@@ -4,4 +4,6 @@
<string name="no_thanks">Non merci</string> <string name="no_thanks">Non merci</string>
<string name="yes">Oui</string> <string name="yes">Oui</string>
<string name="ok">OK</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> </resources>
...@@ -2,4 +2,6 @@ ...@@ -2,4 +2,6 @@
<string name="no_thanks">Tidak, terima kasih</string> <string name="no_thanks">Tidak, terima kasih</string>
<string name="yes">Ya</string> <string name="yes">Ya</string>
<string name="ok">OK</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> </resources>
...@@ -2,4 +2,6 @@ ...@@ -2,4 +2,6 @@
<string name="no_thanks">No, grazie</string> <string name="no_thanks">No, grazie</string>
<string name="yes"></string> <string name="yes"></string>
<string name="ok">OK</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> </resources>
...@@ -2,4 +2,6 @@ ...@@ -2,4 +2,6 @@
<string name="no_thanks">아니오, 괜찮습니다</string> <string name="no_thanks">아니오, 괜찮습니다</string>
<string name="yes"></string> <string name="yes"></string>
<string name="ok">확인</string> <string name="ok">확인</string>
<string name="upgrade_msg">완전한 Magisk Manager로 업데이트하여 설치를 마치십시오. 다운로드하고 설치하시겠습니까?</string>
<string name="no_internet_msg">인터넷에 연결해 주시기 바랍니다! 완전한 Magisk Manager로 업데이트 해야 합니다.</string>
</resources> </resources>
...@@ -2,4 +2,6 @@ ...@@ -2,4 +2,6 @@
<string name="no_thanks">Ačiū, nereikia</string> <string name="no_thanks">Ačiū, nereikia</string>
<string name="yes">Taip</string> <string name="yes">Taip</string>
<string name="ok">Gerai</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>
<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--> <!--Used in both stub and full app-->
<string name="no_thanks">Не, благодарам</string> <string name="no_thanks">Не, благодарам</string>
<string name="yes">Да</string> <string name="yes">Да</string>
<string name="ok">ОК</string> <string name="ok">ОК</string>
<string name="upgrade_msg">Надградете до целосната верзија на Magisk Manager за да го завршите поставувањето. Преземете и инсталирајте?</string>
<string name="no_internet_msg">Ве молиме поврзете се на интернет бидејќи е потребна надградба на целосната верзија на Magisk Manager.</string>
</resources> </resources>
...@@ -4,4 +4,6 @@ ...@@ -4,4 +4,6 @@
<string name="no_thanks">Nei takk</string> <string name="no_thanks">Nei takk</string>
<string name="yes">Ja</string> <string name="yes">Ja</string>
<string name="ok">OK</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> </resources>
...@@ -2,4 +2,6 @@ ...@@ -2,4 +2,6 @@
<string name="no_thanks">Nie dziękuję</string> <string name="no_thanks">Nie dziękuję</string>
<string name="yes">Tak</string> <string name="yes">Tak</string>
<string name="ok">OK</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> </resources>
...@@ -2,4 +2,6 @@ ...@@ -2,4 +2,6 @@
<string name="no_thanks">Nu, mulțumesc</string> <string name="no_thanks">Nu, mulțumesc</string>
<string name="yes">Da</string> <string name="yes">Da</string>
<string name="ok">Ok</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> </resources>
...@@ -2,4 +2,6 @@ ...@@ -2,4 +2,6 @@
<string name="no_thanks">Нет</string> <string name="no_thanks">Нет</string>
<string name="yes">Да</string> <string name="yes">Да</string>
<string name="ok">OK</string> <string name="ok">OK</string>
<string name="upgrade_msg">Обновите Magisk Manager для завершения установки. Загрузить и установить?</string>
<string name="no_internet_msg">Пожалуйста, подключитесь к интернету! Требуется обновление Magisk Manager.</string>
</resources> </resources>
...@@ -3,4 +3,6 @@ ...@@ -3,4 +3,6 @@
<string name="no_thanks">Nie, ďakujem</string> <string name="no_thanks">Nie, ďakujem</string>
<string name="yes">Áno</string> <string name="yes">Áno</string>
<string name="ok">OK</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> </resources>
...@@ -2,4 +2,6 @@ ...@@ -2,4 +2,6 @@
<string name="no_thanks">Hayır, teşekkürler</string> <string name="no_thanks">Hayır, teşekkürler</string>
<string name="yes">Evet</string> <string name="yes">Evet</string>
<string name="ok">Tamam</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> </resources>
...@@ -2,4 +2,6 @@ ...@@ -2,4 +2,6 @@
<string name="no_thanks">Ні, дякую</string> <string name="no_thanks">Ні, дякую</string>
<string name="yes">Так</string> <string name="yes">Так</string>
<string name="ok">OK</string> <string name="ok">OK</string>
<string name="upgrade_msg">Оновіть Magisk Manager для завершення встановлення. Завантажити і встановити?</string>
<string name="no_internet_msg">Будь ласка, підключіться до Інтернету! Потрібно оновити Magisk Manager.</string>
</resources> </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 @@ ...@@ -2,4 +2,6 @@
<string name="no_thanks">不,谢谢</string> <string name="no_thanks">不,谢谢</string>
<string name="yes"></string> <string name="yes"></string>
<string name="ok"></string> <string name="ok"></string>
<string name="upgrade_msg">需要升级到完整的 Magisk Manager 来完成安装。 现在下载?</string>
<string name="no_internet_msg">请连接到互联网! 这是升级到完整 Magisk Manager 所必需的。</string>
</resources> </resources>
...@@ -2,4 +2,6 @@ ...@@ -2,4 +2,6 @@
<string name="no_thanks">不,謝謝</string> <string name="no_thanks">不,謝謝</string>
<string name="yes"></string> <string name="yes"></string>
<string name="ok"></string> <string name="ok"></string>
<string name="upgrade_msg">需要升級到完整版 Magisk Manager。是否下載並安裝?</string>
<string name="no_internet_msg">請連上網路!升級到完整版 Magisk Manager 是必須的。</string>
</resources> </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 @@ ...@@ -6,8 +6,9 @@
<string name="magiskhide" translatable="false">Magisk Hide</string> <string name="magiskhide" translatable="false">Magisk Hide</string>
<string name="empty" translatable="false"/> <string name="empty" translatable="false"/>
<!--Used in both stub and full app-->
<string name="no_thanks">No thanks</string> <string name="no_thanks">No thanks</string>
<string name="yes">Yes</string> <string name="yes">Yes</string>
<string name="ok">OK</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> </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 { ...@@ -9,7 +9,10 @@ android {
buildTypes { buildTypes {
release { 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" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
package="com.topjohnwu.magisk"> package="com.topjohnwu.magisk">
<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.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" <!-- Download Activity -->
android:allowBackup="true">
<activity <activity
android:name=".MainActivity" android:name="a.c"
android:theme="@android:style/Theme.Translucent.NoTitleBar"> android:configChanges="orientation|screenSize"
android:theme="@style/SplashTheme">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </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> <intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" /> <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> </intent-filter>
</receiver> </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> </application>
</manifest> </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; ...@@ -3,30 +3,55 @@ package com.topjohnwu.magisk;
import android.app.Activity; import android.app.Activity;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.app.Application; import android.app.Application;
import android.app.ProgressDialog;
import android.os.Build;
import android.os.Bundle; 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.Networking;
import com.topjohnwu.magisk.net.ResponseListener; import com.topjohnwu.magisk.net.ResponseListener;
import com.topjohnwu.magisk.utils.APKInstall;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; 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 = private static final String URL =
"https://raw.githubusercontent.com/topjohnwu/magisk_files/master/" + "https://raw.githubusercontent.com/topjohnwu/magisk_files/" +
(BuildConfig.VERSION_NAME.contains("-") ? "canary_builds/release.json" : "stable.json"); (IS_CANARY ? "canary/release.json" : "master/stable.json");
private String apkLink; 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() { 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(); Application app = getApplication();
Networking.get(apkLink) Networking.get(apkLink)
.getAsFile(new File(getFilesDir(), "manager.apk"), apk -> APKInstall.install(app, apk)); .setErrorHandler(err)
finish(); .getAsFile(MANAGER_APK, apk -> APKInstall.install(app, apk));
}
} }
@Override @Override
...@@ -35,10 +60,10 @@ public class MainActivity extends Activity { ...@@ -35,10 +60,10 @@ public class MainActivity extends Activity {
Networking.init(this); Networking.init(this);
if (Networking.checkNetworkStatus(this)) { if (Networking.checkNetworkStatus(this)) {
Networking.get(URL) Networking.get(URL)
.setErrorHandler(((conn, e) -> finish())) .setErrorHandler(err)
.getAsJSONObject(new JSONLoader()); .getAsJSONObject(new JSONLoader());
} else { } else {
new AlertDialog.Builder(this, AlertDialog.THEME_DEVICE_DEFAULT_LIGHT) new AlertDialog.Builder(this)
.setCancelable(false) .setCancelable(false)
.setTitle(R.string.app_name) .setTitle(R.string.app_name)
.setMessage(R.string.no_internet_msg) .setMessage(R.string.no_internet_msg)
...@@ -54,7 +79,7 @@ public class MainActivity extends Activity { ...@@ -54,7 +79,7 @@ public class MainActivity extends Activity {
try { try {
JSONObject manager = json.getJSONObject("app"); JSONObject manager = json.getJSONObject("app");
apkLink = manager.getString("link"); apkLink = manager.getString("link");
new AlertDialog.Builder(MainActivity.this, AlertDialog.THEME_DEVICE_DEFAULT_LIGHT) new AlertDialog.Builder(DownloadActivity.this)
.setCancelable(false) .setCancelable(false)
.setTitle(R.string.app_name) .setTitle(R.string.app_name)
.setMessage(R.string.upgrade_msg) .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