diff --git a/.gitignore b/.gitignore index 47cd35844..83911abb4 100644 --- a/.gitignore +++ b/.gitignore @@ -2,8 +2,8 @@ out *.zip *.jks *.apk -config.prop -update.sh +/config.prop +/update.sh # Built binaries native/out diff --git a/README.MD b/README.MD index 2a5be0cb5..0f950d7d5 100644 --- a/README.MD +++ b/README.MD @@ -30,12 +30,12 @@ Furthermore, Magisk provides a **Systemless Interface** to alter the system (or ## Translations -Default string resources for Magisk Manager are scattered throughout +Default string resources for Magisk Manager and its stub APK are located here: - `app/src/main/res/values/strings.xml` -- `shared/src/main/res/values/strings.xml` +- `stub/src/main/res/values/strings.xml` -Translate each and place them in the respective locations (`/src/main/res/values-/strings.xml`). +Translate each and place them in the respective locations (`[module]/src/main/res/values-[lang]/strings.xml`). ## Signature Verification diff --git a/app/build.gradle b/app/build.gradle index b9d07c8ee..e522687cc 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -75,7 +75,7 @@ dependencies { implementation "${bindingAdapter}:${vBAdapt}" implementation "${bindingAdapter}-recyclerview:${vBAdapt}" - def vMarkwon = '4.1.1' + def vMarkwon = '4.1.2' implementation "io.noties.markwon:core:${vMarkwon}" implementation "io.noties.markwon:html:${vMarkwon}" implementation "io.noties.markwon:image:${vMarkwon}" @@ -112,7 +112,7 @@ dependencies { replacedBy('com.github.topjohnwu:room-runtime') } } - def vRoom = "2.2.0" + def vRoom = "2.2.1" implementation "com.github.topjohnwu:room-runtime:${vRoom}" kapt "androidx.room:room-compiler:${vRoom}" @@ -124,9 +124,10 @@ dependencies { implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0-alpha03' implementation 'androidx.browser:browser:1.0.0' implementation 'androidx.preference:preference:1.1.0' - implementation 'androidx.recyclerview:recyclerview:1.0.0' + implementation 'androidx.recyclerview:recyclerview:1.1.0-rc01' + implementation 'androidx.fragment:fragment-ktx:1.2.0-rc01' implementation 'androidx.work:work-runtime:2.2.0' - implementation 'androidx.transition:transition:1.2.0' + implementation 'androidx.transition:transition:1.3.0-rc01' 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' diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index c95d10abb..accd5efc2 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -32,7 +32,11 @@ -keep,allowobfuscation class * extends com.topjohnwu.magisk.base.DelegateWorker # BootSigner --keepclassmembers class com.topjohnwu.signing.BootSigner { *; } +-keep class a.a { *; } + +# Workaround R8 bug +-keep,allowobfuscation class com.topjohnwu.magisk.model.receiver.GeneralReceiver +-keepclassmembers class a.e { *; } # Strip logging -assumenosideeffects class timber.log.Timber.Tree { *; } diff --git a/app/res-ids.txt b/app/res-ids.txt new file mode 100644 index 000000000..3a47acfe6 --- /dev/null +++ b/app/res-ids.txt @@ -0,0 +1,5 @@ +com.topjohnwu.magisk:color/xxxxxxxx = 0x7f010000 +com.topjohnwu.magisk:drawable/xxxxxxxx = 0x7f020000 +com.topjohnwu.magisk:string/xxxxxxxx = 0x7f030000 +com.topjohnwu.magisk:style/xxxxxxxx = 0x7f040000 +com.topjohnwu.magisk:xml/xxxxxxxx = 0x7f050000 diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 2a2189477..7c5ba0f2e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -78,6 +78,7 @@ @@ -88,6 +89,7 @@ android:name="a.h" android:directBootAware="true"> + @@ -110,6 +112,12 @@ android:name="com.google.android.gms.version" android:value="12451000" /> + + + diff --git a/app/src/main/java/a/a.java b/app/src/main/java/a/a.java index 5ccb6738b..23927a021 100644 --- a/app/src/main/java/a/a.java +++ b/app/src/main/java/a/a.java @@ -1,18 +1,21 @@ package a; -import androidx.annotation.Keep; import androidx.core.app.AppComponentFactory; import com.topjohnwu.magisk.utils.PatchAPK; import com.topjohnwu.signing.BootSigner; -@Keep public class a extends AppComponentFactory { + @Deprecated public static boolean patchAPK(String in, String out, String pkg) { return PatchAPK.patch(in, out, pkg); } + public static boolean patchAPK(String in, String out, String pkg, String label) { + return PatchAPK.patch(in, out, pkg, label); + } + public static void main(String[] args) throws Exception { BootSigner.main(args); } diff --git a/app/src/main/java/a/e.java b/app/src/main/java/a/e.java index da51b8f55..e2f243e94 100644 --- a/app/src/main/java/a/e.java +++ b/app/src/main/java/a/e.java @@ -3,5 +3,11 @@ package a; import com.topjohnwu.magisk.App; public class e extends App { - /* stub */ + public e() { + super(); + } + + public e(Object o) { + super(o); + } } diff --git a/app/src/main/java/a/w.java b/app/src/main/java/a/w.java index 6fa3e50f6..f852a8968 100644 --- a/app/src/main/java/a/w.java +++ b/app/src/main/java/a/w.java @@ -7,7 +7,6 @@ import androidx.work.Worker; import androidx.work.WorkerParameters; import com.topjohnwu.magisk.base.DelegateWorker; -import com.topjohnwu.magisk.utils.ResourceMgrKt; import java.lang.reflect.ParameterizedType; @@ -19,7 +18,7 @@ public abstract class w extends Worker { @SuppressWarnings("unchecked") w(@NonNull Context context, @NonNull WorkerParameters workerParams) { - super(ResourceMgrKt.wrap(context, false), workerParams); + super(context, workerParams); try { base = ((Class) ((ParameterizedType) getClass().getGenericSuperclass()) .getActualTypeArguments()[0]).newInstance(); diff --git a/app/src/main/java/com/topjohnwu/magisk/App.kt b/app/src/main/java/com/topjohnwu/magisk/App.kt index 3a3f6961c..0569ac57a 100644 --- a/app/src/main/java/com/topjohnwu/magisk/App.kt +++ b/app/src/main/java/com/topjohnwu/magisk/App.kt @@ -6,6 +6,7 @@ import android.content.res.Configuration import androidx.appcompat.app.AppCompatDelegate import androidx.multidex.MultiDex import androidx.room.Room +import androidx.work.WorkManager import androidx.work.impl.WorkDatabase import androidx.work.impl.WorkDatabase_Impl import com.topjohnwu.magisk.data.database.RepoDatabase @@ -14,16 +15,17 @@ import com.topjohnwu.magisk.di.ActivityTracker import com.topjohnwu.magisk.di.koinModules import com.topjohnwu.magisk.extensions.get import com.topjohnwu.magisk.extensions.unwrap -import com.topjohnwu.magisk.utils.ResourceMgr import com.topjohnwu.magisk.utils.RootInit -import com.topjohnwu.magisk.utils.isRunningAsStub -import com.topjohnwu.magisk.utils.wrap import com.topjohnwu.superuser.Shell import org.koin.android.ext.koin.androidContext import org.koin.core.context.startKoin import timber.log.Timber -open class App : Application() { +open class App() : Application() { + + constructor(o: Any) : this() { + Info.stub = DynAPK.load(o) + } init { AppCompatDelegate.setCompatVectorFromResourcesEnabled(true) @@ -50,7 +52,6 @@ open class App : Application() { val app: Application val impl: Context if (base is Application) { - isRunningAsStub = true app = base impl = base.baseContext } else { @@ -67,6 +68,7 @@ open class App : Application() { } ResourceMgr.reload() app.registerActivityLifecycleCallbacks(get()) + WorkManager.initialize(impl.wrapJob(), androidx.work.Configuration.Builder().build()) } // This is required as some platforms expect ContextImpl diff --git a/app/src/main/java/com/topjohnwu/magisk/ClassMap.kt b/app/src/main/java/com/topjohnwu/magisk/ClassMap.kt deleted file mode 100644 index 010a9ccf7..000000000 --- a/app/src/main/java/com/topjohnwu/magisk/ClassMap.kt +++ /dev/null @@ -1,29 +0,0 @@ -package com.topjohnwu.magisk - -import com.topjohnwu.magisk.model.download.DownloadService -import com.topjohnwu.magisk.model.receiver.GeneralReceiver -import com.topjohnwu.magisk.model.update.UpdateCheckService -import com.topjohnwu.magisk.ui.MainActivity -import com.topjohnwu.magisk.ui.SplashActivity -import com.topjohnwu.magisk.ui.flash.FlashActivity -import com.topjohnwu.magisk.ui.surequest.SuRequestActivity -import com.topjohnwu.magisk.redesign.MainActivity as RedesignActivity - -object ClassMap { - private val map = mapOf( - App::class.java to a.e::class.java, - MainActivity::class.java to a.b::class.java, - SplashActivity::class.java to a.c::class.java, - FlashActivity::class.java to a.f::class.java, - UpdateCheckService::class.java to a.g::class.java, - GeneralReceiver::class.java to a.h::class.java, - DownloadService::class.java to a.j::class.java, - SuRequestActivity::class.java to a.m::class.java, - //redesign - RedesignActivity::class.java to a.i::class.java - ) - - operator fun >get(c: Class<*>): T { - return map.getOrElse(c) { throw IllegalArgumentException() } as T - } -} diff --git a/app/src/main/java/com/topjohnwu/magisk/Config.kt b/app/src/main/java/com/topjohnwu/magisk/Config.kt index 27dce273d..a988c7284 100644 --- a/app/src/main/java/com/topjohnwu/magisk/Config.kt +++ b/app/src/main/java/com/topjohnwu/magisk/Config.kt @@ -33,8 +33,9 @@ object Config : PreferenceModel, DBConfig { const val ROOT_ACCESS = "root_access" const val SU_MULTIUSER_MODE = "multiuser_mode" const val SU_MNT_NS = "mnt_ns" - const val SU_MANAGER = "requester" const val SU_FINGERPRINT = "su_fingerprint" + const val SU_MANAGER = "requester" + const val KEYSTORE = "keystore" // prefs const val SU_REQUEST_TIMEOUT = "su_request_timeout" @@ -100,7 +101,12 @@ object Config : PreferenceModel, DBConfig { } private val defaultChannel = - if (Utils.isCanary) Value.CANARY_DEBUG_CHANNEL + if (Utils.isCanary) { + if (BuildConfig.DEBUG) + Value.CANARY_DEBUG_CHANNEL + else + Value.CANARY_CHANNEL + } else Value.DEFAULT_CHANNEL var downloadPath by preference(Key.DOWNLOAD_PATH, Environment.DIRECTORY_DOWNLOADS) @@ -133,6 +139,7 @@ object Config : PreferenceModel, DBConfig { var suMultiuserMode by dbSettings(Key.SU_MULTIUSER_MODE, Value.MULTIUSER_MODE_OWNER_ONLY) var suFingerprint by dbSettings(Key.SU_FINGERPRINT, false) var suManager by dbStrings(Key.SU_MANAGER, "", true) + var keyStoreRaw by dbStrings(Key.KEYSTORE, "", true) // Always return a path in external storage where we can write val downloadDirectory get() = @@ -141,9 +148,6 @@ object Config : PreferenceModel, DBConfig { fun initialize() = prefs.edit { parsePrefs(this) - if (!prefs.contains(Key.UPDATE_CHANNEL)) - putString(Key.UPDATE_CHANNEL, defaultChannel.toString()) - // Get actual state putBoolean(Key.COREONLY, Const.MAGISK_DISABLE_FILE.exists()) @@ -152,6 +156,9 @@ object Config : PreferenceModel, DBConfig { putString(Key.SU_MNT_NS, suMntNamespaceMode.toString()) putString(Key.SU_MULTIUSER_MODE, suMultiuserMode.toString()) putBoolean(Key.SU_FINGERPRINT, FingerprintHelper.useFingerprint()) + }.also { + if (!prefs.contains(Key.UPDATE_CHANNEL)) + prefs.edit().putString(Key.UPDATE_CHANNEL, defaultChannel.toString()).apply() } private fun parsePrefs(editor: SharedPreferences.Editor) = editor.apply { @@ -215,4 +222,4 @@ object Config : PreferenceModel, DBConfig { Shell.su("cat $xml > /data/adb/${Const.MANAGER_CONFIGS}").exec() } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/topjohnwu/magisk/Const.kt b/app/src/main/java/com/topjohnwu/magisk/Const.kt index 2f29cfb96..3ea6121b9 100644 --- a/app/src/main/java/com/topjohnwu/magisk/Const.kt +++ b/app/src/main/java/com/topjohnwu/magisk/Const.kt @@ -22,8 +22,9 @@ object Const { const val MANAGER_CONFIGS = ".tmp.magisk.config" val USER_ID = Process.myUid() / 100000 - object MagiskVersion { + object Version { const val MIN_SUPPORT = 18000 + const val CONNECT_MODE = 20002 } object ID { diff --git a/app/src/main/java/com/topjohnwu/magisk/Hacks.kt b/app/src/main/java/com/topjohnwu/magisk/Hacks.kt new file mode 100644 index 000000000..0245f9591 --- /dev/null +++ b/app/src/main/java/com/topjohnwu/magisk/Hacks.kt @@ -0,0 +1,224 @@ +@file:Suppress("DEPRECATION") + +package com.topjohnwu.magisk + +import android.annotation.SuppressLint +import android.app.job.JobInfo +import android.app.job.JobScheduler +import android.app.job.JobWorkItem +import android.content.ComponentName +import android.content.Context +import android.content.ContextWrapper +import android.content.Intent +import android.content.res.AssetManager +import android.content.res.Configuration +import android.content.res.Resources +import androidx.annotation.RequiresApi +import androidx.annotation.StringRes +import com.topjohnwu.magisk.extensions.langTagToLocale +import com.topjohnwu.magisk.model.download.DownloadService +import com.topjohnwu.magisk.model.receiver.GeneralReceiver +import com.topjohnwu.magisk.model.update.UpdateCheckService +import com.topjohnwu.magisk.ui.MainActivity +import com.topjohnwu.magisk.ui.SplashActivity +import com.topjohnwu.magisk.ui.flash.FlashActivity +import com.topjohnwu.magisk.ui.surequest.SuRequestActivity +import com.topjohnwu.magisk.utils.currentLocale +import com.topjohnwu.magisk.utils.defaultLocale +import java.util.* + +fun AssetManager.addAssetPath(path: String) { + DynAPK.addAssetPath(this, path) +} + +fun Context.wrap(global: Boolean = true): Context + = if (global) GlobalResContext(this) else ResContext(this) + +fun Context.wrapJob(): Context = object : GlobalResContext(this) { + + override fun getApplicationContext(): Context { + return this + } + + @SuppressLint("NewApi") + override fun getSystemService(name: String): Any? { + return if (!isRunningAsStub) super.getSystemService(name) else + when (name) { + Context.JOB_SCHEDULER_SERVICE -> + JobSchedulerWrapper(super.getSystemService(name) as JobScheduler) + else -> super.getSystemService(name) + } + } +} + +// 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(ResourceMgr.resApk) + return this +} + +fun Class<*>.cmp(pkg: String = BuildConfig.APPLICATION_ID): ComponentName { + val name = ClassMap[this].name + return ComponentName(pkg, Info.stub?.componentMap?.get(name) ?: name) +} + +fun Context.intent(c: Class<*>): Intent { + val cls = ClassMap[c] + return Info.stub?.let { + val className = it.componentMap.getOrElse(cls.name) { cls.name } + Intent().setComponent(ComponentName(this, className)) + } ?: Intent(this, cls) +} + +fun resolveRes(idx: Int): Int { + return Info.stub?.resourceMap?.get(idx) ?: when(idx) { + DynAPK.NOTIFICATION -> R.drawable.ic_magisk_outline + DynAPK.DOWNLOAD -> R.drawable.sc_cloud_download + DynAPK.SUPERUSER -> R.drawable.sc_superuser + DynAPK.MODULES -> R.drawable.sc_extension + DynAPK.MAGISKHIDE -> R.drawable.sc_magiskhide + else -> -1 + } +} + +private open class GlobalResContext(base: Context) : ContextWrapper(base) { + open val mRes: Resources get() = ResourceMgr.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)) + } +} + +private class ResContext(base: Context) : GlobalResContext(base) { + override val mRes by lazy { base.resources.patch() } +} + +object ResourceMgr { + + internal lateinit var resource: Resources + internal lateinit var resApk: String + + fun init(context: Context) { + resource = context.resources + if (isRunningAsStub) + resApk = DynAPK.current(context).path + } + + fun reload(config: Configuration = Configuration(resource.configuration)) { + val localeConfig = Config.locale + currentLocale = when { + localeConfig.isEmpty() -> defaultLocale + else -> localeConfig.langTagToLocale() + } + Locale.setDefault(currentLocale) + resource.patch(config) + } + + fun getString(locale: Locale, @StringRes id: Int): String { + val config = Configuration() + config.setLocale(locale) + return Resources(resource.assets, resource.displayMetrics, config).getString(id) + } + +} + +@RequiresApi(api = 28) +private class JobSchedulerWrapper(private val base: JobScheduler) : JobScheduler() { + + override fun schedule(job: JobInfo): Int { + return base.schedule(job.patch()) + } + + override fun enqueue(job: JobInfo, work: JobWorkItem): Int { + return base.enqueue(job.patch(), work) + } + + override fun cancel(jobId: Int) { + base.cancel(jobId) + } + + override fun cancelAll() { + base.cancelAll() + } + + override fun getAllPendingJobs(): List { + return base.allPendingJobs + } + + override fun getPendingJob(jobId: Int): JobInfo? { + return base.getPendingJob(jobId) + } + + private fun JobInfo.patch(): JobInfo { + // We need to patch the component of JobInfo to access WorkManager SystemJobService + + val name = service.className + val component = ComponentName( + service.packageName, + Info.stub!!.componentMap[name] ?: name) + + // Clone the JobInfo except component + val builder = JobInfo.Builder(id, component) + .setExtras(extras) + .setTransientExtras(transientExtras) + .setClipData(clipData, clipGrantFlags) + .setRequiredNetwork(requiredNetwork) + .setEstimatedNetworkBytes(estimatedNetworkDownloadBytes, estimatedNetworkUploadBytes) + .setRequiresCharging(isRequireCharging) + .setRequiresDeviceIdle(isRequireDeviceIdle) + .setRequiresBatteryNotLow(isRequireBatteryNotLow) + .setRequiresStorageNotLow(isRequireStorageNotLow) + .also { + triggerContentUris?.let { uris -> + for (uri in uris) + it.addTriggerContentUri(uri) + } + } + .setTriggerContentUpdateDelay(triggerContentUpdateDelay) + .setTriggerContentMaxDelay(triggerContentMaxDelay) + .setImportantWhileForeground(isImportantWhileForeground) + .setPrefetch(isPrefetch) + .setPersisted(isPersisted) + + if (isPeriodic) { + builder.setPeriodic(intervalMillis, flexMillis) + } else { + if (minLatencyMillis > 0) + builder.setMinimumLatency(minLatencyMillis) + if (maxExecutionDelayMillis > 0) + builder.setOverrideDeadline(maxExecutionDelayMillis) + } + if (!isRequireDeviceIdle) + builder.setBackoffCriteria(initialBackoffMillis, backoffPolicy) + + return builder.build() + } +} + +object ClassMap { + + private val map = mapOf( + App::class.java to a.e::class.java, + MainActivity::class.java to a.b::class.java, + SplashActivity::class.java to a.c::class.java, + FlashActivity::class.java to a.f::class.java, + UpdateCheckService::class.java to a.g::class.java, + GeneralReceiver::class.java to a.h::class.java, + DownloadService::class.java to a.j::class.java, + SuRequestActivity::class.java to a.m::class.java + ) + + operator fun get(c: Class<*>) = map.getOrElse(c) { throw IllegalArgumentException() } +} diff --git a/app/src/main/java/com/topjohnwu/magisk/Info.kt b/app/src/main/java/com/topjohnwu/magisk/Info.kt index c2f4f84c8..3fa227b99 100644 --- a/app/src/main/java/com/topjohnwu/magisk/Info.kt +++ b/app/src/main/java/com/topjohnwu/magisk/Info.kt @@ -1,18 +1,24 @@ package com.topjohnwu.magisk +import com.github.pwittchen.reactivenetwork.library.rx2.ReactiveNetwork +import com.topjohnwu.magisk.extensions.get +import com.topjohnwu.magisk.extensions.subscribeK import com.topjohnwu.magisk.model.entity.UpdateInfo +import com.topjohnwu.magisk.utils.CachedValue +import com.topjohnwu.magisk.utils.KObservableField import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.ShellUtils +val isRunningAsStub get() = Info.stub != null + object Info { - @JvmStatic - var magiskVersionCode = -1 + val envRef = CachedValue { loadState() } @JvmStatic - var magiskVersionString = "" - - var remote = UpdateInfo() + val env by envRef // Local + var remote = UpdateInfo() // Remote + var stub: DynAPK.Data? = null // Stub @JvmStatic var keepVerity = false @@ -21,11 +27,40 @@ object Info { @JvmStatic var recovery = false - fun loadMagiskInfo() { - runCatching { - magiskVersionString = ShellUtils.fastCmd("magisk -v").split(":".toRegex())[0] - magiskVersionCode = ShellUtils.fastCmd("magisk -V").toInt() - Config.magiskHide = Shell.su("magiskhide --status").exec().isSuccess + val isConnected by lazy { + KObservableField(false).also { field -> + ReactiveNetwork.observeNetworkConnectivity(get()) + .subscribeK { + field.value = it.available() + } + } + } + + private fun loadState() = runCatching { + val str = ShellUtils.fastCmd("magisk -v").split(":".toRegex())[0] + val code = ShellUtils.fastCmd("magisk -V").toInt() + val hide = Shell.su("magiskhide --status").exec().isSuccess + var mode = -1 + if (code >= Const.Version.CONNECT_MODE) { + mode = Shell.su("magisk --connect-mode").exec().code + if (mode == 0) { + // Manually trigger broadcast test + Shell.su("magisk --broadcast-test").exec() + } + } + Env(code, str, hide, mode) + }.getOrElse { Env() } + + class Env( + val magiskVersionCode: Int = -1, + val magiskVersionString: String = "", + hide: Boolean = false, + var connectionMode: Int = -1 + ) { + val magiskHide get() = Config.magiskHide + + init { + Config.magiskHide = hide } } } diff --git a/app/src/main/java/com/topjohnwu/magisk/base/BaseActivity.kt b/app/src/main/java/com/topjohnwu/magisk/base/BaseActivity.kt index 9c7b22081..2c4367bdf 100644 --- a/app/src/main/java/com/topjohnwu/magisk/base/BaseActivity.kt +++ b/app/src/main/java/com/topjohnwu/magisk/base/BaseActivity.kt @@ -21,7 +21,7 @@ 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 com.topjohnwu.magisk.wrap import kotlin.random.Random typealias RequestCallback = BaseActivity<*, *>.(Int, Intent?) -> Unit diff --git a/app/src/main/java/com/topjohnwu/magisk/base/BaseReceiver.kt b/app/src/main/java/com/topjohnwu/magisk/base/BaseReceiver.kt index 2e96a6b11..e3f07e620 100644 --- a/app/src/main/java/com/topjohnwu/magisk/base/BaseReceiver.kt +++ b/app/src/main/java/com/topjohnwu/magisk/base/BaseReceiver.kt @@ -4,7 +4,7 @@ import android.content.BroadcastReceiver import android.content.Context import android.content.ContextWrapper import android.content.Intent -import com.topjohnwu.magisk.utils.wrap +import com.topjohnwu.magisk.wrap import org.koin.core.KoinComponent abstract class BaseReceiver : BroadcastReceiver(), KoinComponent { diff --git a/app/src/main/java/com/topjohnwu/magisk/base/BaseService.kt b/app/src/main/java/com/topjohnwu/magisk/base/BaseService.kt index dc7801664..b7a65ccc2 100644 --- a/app/src/main/java/com/topjohnwu/magisk/base/BaseService.kt +++ b/app/src/main/java/com/topjohnwu/magisk/base/BaseService.kt @@ -2,7 +2,7 @@ package com.topjohnwu.magisk.base import android.app.Service import android.content.Context -import com.topjohnwu.magisk.utils.wrap +import com.topjohnwu.magisk.wrap import org.koin.core.KoinComponent abstract class BaseService : Service(), KoinComponent { diff --git a/app/src/main/java/com/topjohnwu/magisk/base/viewmodel/BaseViewModel.kt b/app/src/main/java/com/topjohnwu/magisk/base/viewmodel/BaseViewModel.kt index a9acc6ebb..987f1bf6d 100644 --- a/app/src/main/java/com/topjohnwu/magisk/base/viewmodel/BaseViewModel.kt +++ b/app/src/main/java/com/topjohnwu/magisk/base/viewmodel/BaseViewModel.kt @@ -1,28 +1,24 @@ package com.topjohnwu.magisk.base.viewmodel import android.app.Activity -import com.github.pwittchen.reactivenetwork.library.rx2.ReactiveNetwork import com.topjohnwu.magisk.extensions.doOnSubscribeUi -import com.topjohnwu.magisk.extensions.get -import com.topjohnwu.magisk.extensions.subscribeK import com.topjohnwu.magisk.model.events.BackPressEvent import com.topjohnwu.magisk.model.events.PermissionEvent import com.topjohnwu.magisk.model.events.ViewActionEvent import com.topjohnwu.magisk.utils.KObservableField import io.reactivex.Observable import io.reactivex.subjects.PublishSubject +import com.topjohnwu.magisk.Info.isConnected as gIsConnected abstract class BaseViewModel( initialState: State = State.LOADING ) : LoadingViewModel(initialState) { - val isConnected = KObservableField(false) - - init { - ReactiveNetwork.observeNetworkConnectivity(get()) - .subscribeK { isConnected.value = it.available() } - .add() + val isConnected = object : KObservableField(gIsConnected.value, gIsConnected) { + override fun get(): Boolean { + return gIsConnected.value + } } fun withView(action: Activity.() -> Unit) { diff --git a/app/src/main/java/com/topjohnwu/magisk/data/repository/MagiskRepository.kt b/app/src/main/java/com/topjohnwu/magisk/data/repository/MagiskRepository.kt index 0d73fc6c1..b9a4ae98b 100644 --- a/app/src/main/java/com/topjohnwu/magisk/data/repository/MagiskRepository.kt +++ b/app/src/main/java/com/topjohnwu/magisk/data/repository/MagiskRepository.kt @@ -29,7 +29,7 @@ class MagiskRepository( else -> throw IllegalArgumentException() }.flatMap { // If remote version is lower than current installed, try switching to beta - if (it.magisk.versionCode < Info.magiskVersionCode + if (it.magisk.versionCode < Info.env.magiskVersionCode && Config.updateChannel == Config.Value.DEFAULT_CHANNEL) { Config.updateChannel = Config.Value.BETA_CHANNEL apiRaw.fetchBetaUpdate() @@ -74,4 +74,4 @@ class MagiskRepository( ) } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/topjohnwu/magisk/extensions/XAndroid.kt b/app/src/main/java/com/topjohnwu/magisk/extensions/XAndroid.kt index 066f1835b..2fd32a7de 100644 --- a/app/src/main/java/com/topjohnwu/magisk/extensions/XAndroid.kt +++ b/app/src/main/java/com/topjohnwu/magisk/extensions/XAndroid.kt @@ -20,15 +20,18 @@ import androidx.annotation.ColorRes import androidx.annotation.DrawableRes import androidx.core.content.ContextCompat import androidx.core.net.toUri +import com.topjohnwu.magisk.Const +import com.topjohnwu.magisk.FileProvider import com.topjohnwu.magisk.utils.DynamicClassLoader -import com.topjohnwu.magisk.utils.FileProvider import com.topjohnwu.magisk.utils.Utils import com.topjohnwu.magisk.utils.currentLocale import com.topjohnwu.superuser.ShellUtils +import com.topjohnwu.superuser.Shell import java.io.File import java.io.FileNotFoundException import java.text.SimpleDateFormat import java.util.* +import java.lang.reflect.Array as JArray val packageName: String get() = get().packageName @@ -99,33 +102,38 @@ fun Context.readUri(uri: Uri) = fun Intent.startActivity(context: Context) = context.startActivity(this) -fun Intent.toCommand(args: MutableList) { - if (action != null) { +fun Intent.startActivityWithRoot() { + val args = mutableListOf("am", "start", "--user", Const.USER_ID.toString()) + val cmd = toCommand(args).joinToString(" ") + Shell.su(cmd).submit() +} + +fun Intent.toCommand(args: MutableList = mutableListOf()): MutableList { + action?.also { args.add("-a") - args.add(action!!) + args.add(it) } - if (component != null) { + component?.also { args.add("-n") - args.add(component!!.flattenToString()) + args.add(it.flattenToString()) } - if (data != null) { + data?.also { args.add("-d") - args.add(dataString!!) + args.add(it.toString()) } - if (categories != null) { - for (cat in categories) { + categories?.also { + for (cat in it) { args.add("-c") args.add(cat) } } - if (type != null) { + type?.also { args.add("-t") - args.add(type!!) + args.add(it) } - val extras = extras - if (extras != null) { - loop@ for (key in extras.keySet()) { - val v = extras.get(key) ?: continue + extras?.also { + loop@ for (key in it.keySet()) { + val v = it[key] ?: continue var value: Any = v val arg: String when { @@ -139,9 +147,8 @@ fun Intent.toCommand(args: MutableList) { arg = "--ecn" value = v.flattenToString() } - v is ArrayList<*> -> { - if (v.size <= 0) - /* Impossible to know the type due to type erasure */ + v is List<*> -> { + if (v.isEmpty()) continue@loop arg = if (v[0] is Int) @@ -177,11 +184,9 @@ fun Intent.toCommand(args: MutableList) { continue@loop /* Unsupported */ val sb = StringBuilder() - val len = java.lang.reflect.Array.getLength(v) + val len = JArray.getLength(v) for (i in 0 until len) { - sb.append( - java.lang.reflect.Array.get(v, i)!!.toString().replace(",", "\\,") - ) + sb.append(JArray.get(v, i)!!.toString().replace(",", "\\,")) sb.append(',') } // Remove trailing comma @@ -198,6 +203,7 @@ fun Intent.toCommand(args: MutableList) { } args.add("-f") args.add(flags.toString()) + return args } fun File.provide(context: Context = get()): Uri { diff --git a/app/src/main/java/com/topjohnwu/magisk/extensions/XString.kt b/app/src/main/java/com/topjohnwu/magisk/extensions/XString.kt index ad24a1a9e..0968ffd65 100644 --- a/app/src/main/java/com/topjohnwu/magisk/extensions/XString.kt +++ b/app/src/main/java/com/topjohnwu/magisk/extensions/XString.kt @@ -33,3 +33,5 @@ fun String.trimEmptyToNull(): String? = if (isBlank()) null else this fun String.legalFilename() = replace(" ", "_").replace("'", "").replace("\"", "") .replace("$", "").replace("`", "").replace("*", "").replace("/", "_") .replace("#", "").replace("@", "").replace("\\", "_") + +fun String.isEmptyInternal() = isNullOrBlank() \ No newline at end of file diff --git a/app/src/main/java/com/topjohnwu/magisk/model/download/DownloadService.kt b/app/src/main/java/com/topjohnwu/magisk/model/download/DownloadService.kt index 4fbf8c456..3f0eca392 100644 --- a/app/src/main/java/com/topjohnwu/magisk/model/download/DownloadService.kt +++ b/app/src/main/java/com/topjohnwu/magisk/model/download/DownloadService.kt @@ -7,11 +7,11 @@ import android.content.Intent import android.os.Build import android.webkit.MimeTypeMap import androidx.core.app.NotificationCompat -import com.topjohnwu.magisk.ClassMap import com.topjohnwu.magisk.R import com.topjohnwu.magisk.extensions.chooser import com.topjohnwu.magisk.extensions.exists import com.topjohnwu.magisk.extensions.provide +import com.topjohnwu.magisk.intent 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.DownloadSubject @@ -63,7 +63,7 @@ open class DownloadService : RemoteFileService() { remove(id) when (subject.configuration) { is APK.Upgrade -> APKInstall.install(this, subject.file) - else -> Unit + is APK.Restore -> Unit } } @@ -140,8 +140,7 @@ open class DownloadService : RemoteFileService() { inline operator fun invoke(context: Context, argBuilder: Builder.() -> Unit) { val app = context.applicationContext val builder = Builder().apply(argBuilder) - val intent = Intent(app, ClassMap[DownloadService::class.java]) - .putExtra(ARG_URL, builder.subject) + val intent = app.intent(DownloadService::class.java).putExtra(ARG_URL, builder.subject) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { app.startForegroundService(intent) @@ -152,4 +151,4 @@ open class DownloadService : RemoteFileService() { } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/topjohnwu/magisk/model/download/ManagerUpgrade.kt b/app/src/main/java/com/topjohnwu/magisk/model/download/ManagerUpgrade.kt index 278edafb0..9e7079bb7 100644 --- a/app/src/main/java/com/topjohnwu/magisk/model/download/ManagerUpgrade.kt +++ b/app/src/main/java/com/topjohnwu/magisk/model/download/ManagerUpgrade.kt @@ -1,43 +1,47 @@ package com.topjohnwu.magisk.model.download -import android.content.ComponentName -import com.topjohnwu.magisk.BuildConfig -import com.topjohnwu.magisk.ClassMap -import com.topjohnwu.magisk.Config -import com.topjohnwu.magisk.R -import com.topjohnwu.magisk.extensions.DynamicClassLoader +import com.topjohnwu.magisk.* +import com.topjohnwu.magisk.extensions.writeTo import com.topjohnwu.magisk.model.entity.internal.Configuration.APK.Restore import com.topjohnwu.magisk.model.entity.internal.Configuration.APK.Upgrade import com.topjohnwu.magisk.model.entity.internal.DownloadSubject -import com.topjohnwu.magisk.ui.SplashActivity import com.topjohnwu.magisk.utils.PatchAPK -import com.topjohnwu.magisk.utils.Utils import com.topjohnwu.superuser.Shell -import timber.log.Timber import java.io.File -private fun RemoteFileService.patchPackage(apk: File, id: Int) { - if (packageName != BuildConfig.APPLICATION_ID) { - update(id) { notification -> - notification.setProgress(0, 0, true) - .setProgress(0, 0, true) - .setContentTitle(getString(R.string.hide_manager_title)) - .setContentText("") - } - val patched = File(apk.parent, "patched.apk") - try { - // Try using the new APK to patch itself - val loader = DynamicClassLoader(apk) - loader.loadClass("a.a") - .getMethod("patchAPK", String::class.java, String::class.java, String::class.java) - .invoke(null, apk.path, patched.path, packageName) - } catch (e: Exception) { - Timber.e(e) - // Fallback to use the current implementation - PatchAPK.patch(apk.path, patched.path, packageName) - } +private fun RemoteFileService.patch(apk: File, id: Int) { + if (packageName == BuildConfig.APPLICATION_ID) + return + + update(id) { notification -> + notification.setProgress(0, 0, true) + .setProgress(0, 0, true) + .setContentTitle(getString(R.string.hide_manager_title)) + .setContentText("") + } + val patched = File(apk.parent, "patched.apk") + PatchAPK.patch(apk, patched, packageName, applicationInfo.nonLocalizedLabel.toString()) + apk.delete() + patched.renameTo(apk) +} + +private fun RemoteFileService.upgrade(apk: File, id: Int) { + if (isRunningAsStub) { + // Move to upgrade location + apk.copyTo(DynAPK.update(this), overwrite = true) apk.delete() - patched.renameTo(apk) + if (Info.stub!!.version < Info.remote.stub.versionCode) { + // We also want to upgrade stub + service.fetchFile(Info.remote.stub.link).blockingGet().byteStream().use { + it.writeTo(apk) + } + patch(apk, id) + } else { + // Simply relaunch the app + ProcessPhoenix.triggerRebirth(this) + } + } else { + patch(apk, id) } } @@ -51,15 +55,11 @@ private fun RemoteFileService.restore(apk: File, id: Int) { Config.export() // Make it world readable apk.setReadable(true, false) - if (Shell.su("pm install $apk").exec().isSuccess) { - val component = ComponentName(BuildConfig.APPLICATION_ID, - ClassMap.get>(SplashActivity::class.java).name) - Utils.rmAndLaunch(packageName, component) - } + Shell.su("pm install $apk && pm uninstall $packageName").exec() } fun RemoteFileService.handleAPK(subject: DownloadSubject.Manager) = when (subject.configuration) { - is Upgrade -> patchPackage(subject.file, subject.hashCode()) + is Upgrade -> upgrade(subject.file, subject.hashCode()) is Restore -> restore(subject.file, subject.hashCode()) } diff --git a/app/src/main/java/com/topjohnwu/magisk/model/download/RemoteFileService.kt b/app/src/main/java/com/topjohnwu/magisk/model/download/RemoteFileService.kt index 53e5d0ed4..181290e0f 100644 --- a/app/src/main/java/com/topjohnwu/magisk/model/download/RemoteFileService.kt +++ b/app/src/main/java/com/topjohnwu/magisk/model/download/RemoteFileService.kt @@ -25,7 +25,7 @@ import java.io.InputStream abstract class RemoteFileService : NotificationService() { - private val service: GithubRawServices by inject() + val service: GithubRawServices by inject() override val defaultNotification: NotificationCompat.Builder get() = Notifications.progress(this, "") @@ -134,4 +134,4 @@ abstract class RemoteFileService : NotificationService() { } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/topjohnwu/magisk/model/entity/UpdateInfo.kt b/app/src/main/java/com/topjohnwu/magisk/model/entity/UpdateInfo.kt index 43f6fc924..d7b75580b 100644 --- a/app/src/main/java/com/topjohnwu/magisk/model/entity/UpdateInfo.kt +++ b/app/src/main/java/com/topjohnwu/magisk/model/entity/UpdateInfo.kt @@ -8,7 +8,8 @@ import se.ansman.kotshi.JsonSerializable data class UpdateInfo( val app: ManagerJson = ManagerJson(), val uninstaller: UninstallerJson = UninstallerJson(), - val magisk: MagiskJson = MagiskJson() + val magisk: MagiskJson = MagiskJson(), + val stub: StubJson = StubJson() ) @JsonSerializable @@ -33,3 +34,9 @@ data class ManagerJson( val link: String = "", val note: String = "" ) : Parcelable + +@JsonSerializable +data class StubJson( + val versionCode: Int = -1, + val link: String = "" +) diff --git a/app/src/main/java/com/topjohnwu/magisk/model/receiver/GeneralReceiver.kt b/app/src/main/java/com/topjohnwu/magisk/model/receiver/GeneralReceiver.kt index 587712370..86ade4d75 100644 --- a/app/src/main/java/com/topjohnwu/magisk/model/receiver/GeneralReceiver.kt +++ b/app/src/main/java/com/topjohnwu/magisk/model/receiver/GeneralReceiver.kt @@ -2,14 +2,14 @@ package com.topjohnwu.magisk.model.receiver import android.content.ContextWrapper import android.content.Intent -import com.topjohnwu.magisk.ClassMap -import com.topjohnwu.magisk.Config -import com.topjohnwu.magisk.Const -import com.topjohnwu.magisk.Info +import android.os.Build.VERSION.SDK_INT +import com.topjohnwu.magisk.* import com.topjohnwu.magisk.base.BaseReceiver import com.topjohnwu.magisk.data.database.PolicyDao import com.topjohnwu.magisk.data.database.base.su import com.topjohnwu.magisk.extensions.reboot +import com.topjohnwu.magisk.extensions.startActivity +import com.topjohnwu.magisk.extensions.startActivityWithRoot import com.topjohnwu.magisk.model.download.DownloadService import com.topjohnwu.magisk.model.entity.ManagerJson import com.topjohnwu.magisk.model.entity.internal.Configuration @@ -20,6 +20,7 @@ import com.topjohnwu.magisk.view.Notifications import com.topjohnwu.magisk.view.Shortcuts import com.topjohnwu.superuser.Shell import org.koin.core.inject +import timber.log.Timber open class GeneralReceiver : BaseReceiver() { @@ -38,6 +39,17 @@ open class GeneralReceiver : BaseReceiver() { override fun onReceive(context: ContextWrapper, intent: Intent?) { intent ?: return + + // Debug messages + if (BuildConfig.DEBUG) { + Timber.d(intent.action) + intent.extras?.let { bundle -> + bundle.keySet().forEach { + Timber.d("[%s]=[%s]", it, bundle[it]) + } + } + } + when (intent.action ?: return) { Intent.ACTION_REBOOT, Intent.ACTION_BOOT_COMPLETED -> { val action = intent.getStringExtra("action") @@ -51,16 +63,26 @@ open class GeneralReceiver : BaseReceiver() { } when (action) { REQUEST -> { - val i = Intent(context, ClassMap[SuRequestActivity::class.java]) + val i = context.intent(SuRequestActivity::class.java) .setAction(action) .putExtra("socket", intent.getStringExtra("socket")) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) .addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK) - context.startActivity(i) + if (SDK_INT >= 29) { + // Android Q does not allow starting activity from background + i.startActivityWithRoot() + } else { + i.startActivity(context) + } + } + LOG -> SuLogger.handleLogs(context, intent) + NOTIFY -> SuLogger.handleNotify(context, intent) + TEST -> { + val mode = intent.getIntExtra("mode", 1 shl 1) + if (mode > Info.env.connectionMode) + Info.env.connectionMode = mode + Shell.su("magisk --connect-mode $mode").submit() } - LOG -> SuLogger.handleLogs(intent) - NOTIFY -> SuLogger.handleNotify(intent) - TEST -> Shell.su("magisk --use-broadcast").submit() } } Intent.ACTION_PACKAGE_REPLACED -> diff --git a/app/src/main/java/com/topjohnwu/magisk/model/update/UpdateCheckService.kt b/app/src/main/java/com/topjohnwu/magisk/model/update/UpdateCheckService.kt index 17841cd0c..190e05802 100644 --- a/app/src/main/java/com/topjohnwu/magisk/model/update/UpdateCheckService.kt +++ b/app/src/main/java/com/topjohnwu/magisk/model/update/UpdateCheckService.kt @@ -20,7 +20,7 @@ class UpdateCheckService : DelegateWorker() { magiskRepo.fetchUpdate().blockingGet() if (BuildConfig.VERSION_CODE < Info.remote.app.versionCode) Notifications.managerUpdate(applicationContext) - else if (Info.magiskVersionCode < Info.remote.magisk.versionCode) + else if (Info.env.magiskVersionCode < Info.remote.magisk.versionCode) Notifications.magiskUpdate(applicationContext) ListenableWorker.Result.success() }.getOrElse { diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/MainActivity.kt b/app/src/main/java/com/topjohnwu/magisk/ui/MainActivity.kt index 848192ca6..05fcaa90d 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/MainActivity.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ui/MainActivity.kt @@ -7,8 +7,6 @@ import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentTransaction import com.ncapdevi.fragnav.FragNavController import com.ncapdevi.fragnav.FragNavTransactionOptions -import com.topjohnwu.magisk.ClassMap -import com.topjohnwu.magisk.Config import com.topjohnwu.magisk.Const.Key.OPEN_SECTION import com.topjohnwu.magisk.Info import com.topjohnwu.magisk.R @@ -17,6 +15,7 @@ import com.topjohnwu.magisk.base.BaseFragment import com.topjohnwu.magisk.databinding.ActivityMainBinding import com.topjohnwu.magisk.extensions.addOnPropertyChangedCallback import com.topjohnwu.magisk.extensions.snackbar +import com.topjohnwu.magisk.intent import com.topjohnwu.magisk.model.events.* import com.topjohnwu.magisk.model.navigation.MagiskAnimBuilder import com.topjohnwu.magisk.model.navigation.MagiskNavigationEvent @@ -61,7 +60,7 @@ open class MainActivity : BaseActivity(), Na override fun onCreate(savedInstanceState: Bundle?) { if (!SplashActivity.DONE) { - startActivity(Intent(this, ClassMap[SplashActivity::class.java])) + startActivity(intent(SplashActivity::class.java)) finish() } @@ -155,11 +154,11 @@ open class MainActivity : BaseActivity(), Na private fun checkHideSection() { val menu = binding.navView.menu menu.findItem(R.id.magiskHideFragment).isVisible = - Shell.rootAccess() && Config.magiskHide + Shell.rootAccess() && Info.env.magiskHide menu.findItem(R.id.modulesFragment).isVisible = - Shell.rootAccess() && Info.magiskVersionCode >= 0 + Shell.rootAccess() && Info.env.magiskVersionCode >= 0 menu.findItem(R.id.reposFragment).isVisible = - (viewModel.isConnected.value && Shell.rootAccess() && Info.magiskVersionCode >= 0) + (viewModel.isConnected.value && Shell.rootAccess() && Info.env.magiskVersionCode >= 0) menu.findItem(R.id.logFragment).isVisible = Shell.rootAccess() menu.findItem(R.id.superuserFragment).isVisible = diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/SplashActivity.kt b/app/src/main/java/com/topjohnwu/magisk/ui/SplashActivity.kt index a2c8201b3..c52756721 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/SplashActivity.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ui/SplashActivity.kt @@ -2,14 +2,12 @@ package com.topjohnwu.magisk.ui import android.app.Activity import android.content.Context -import android.content.Intent import android.os.Bundle import android.text.TextUtils import androidx.appcompat.app.AlertDialog import com.topjohnwu.magisk.* import com.topjohnwu.magisk.model.navigation.Navigation import com.topjohnwu.magisk.utils.Utils -import com.topjohnwu.magisk.utils.wrap import com.topjohnwu.magisk.view.Notifications import com.topjohnwu.magisk.view.Shortcuts import com.topjohnwu.superuser.Shell @@ -24,11 +22,11 @@ open class SplashActivity : Activity() { super.onCreate(savedInstanceState) Shell.getShell { - if (Info.magiskVersionCode > 0 && Info.magiskVersionCode < Const.MagiskVersion.MIN_SUPPORT) { + if (Info.env.magiskVersionCode > 0 && Info.env.magiskVersionCode < Const.Version.MIN_SUPPORT) { AlertDialog.Builder(this) .setTitle(R.string.unsupport_magisk_title) .setMessage(R.string.unsupport_magisk_message) - .setNegativeButton(R.string.ok, null) + .setNegativeButton(android.R.string.ok, null) .setOnDismissListener { finish() } .show() } else { diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/flash/FlashActivity.kt b/app/src/main/java/com/topjohnwu/magisk/ui/flash/FlashActivity.kt index 143db2a3e..cade5a5f1 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/flash/FlashActivity.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ui/flash/FlashActivity.kt @@ -6,12 +6,12 @@ import android.net.Uri import android.os.Bundle import androidx.core.app.NotificationManagerCompat import androidx.core.net.toUri -import com.topjohnwu.magisk.ClassMap import com.topjohnwu.magisk.Const import com.topjohnwu.magisk.R import com.topjohnwu.magisk.base.BaseActivity import com.topjohnwu.magisk.databinding.ActivityFlashBinding import com.topjohnwu.magisk.extensions.snackbar +import com.topjohnwu.magisk.intent import com.topjohnwu.magisk.model.events.BackPressEvent import com.topjohnwu.magisk.model.events.PermissionEvent import com.topjohnwu.magisk.model.events.SnackbarEvent @@ -60,7 +60,7 @@ open class FlashActivity : BaseActivity() companion object { - private fun intent(context: Context) = Intent(context, ClassMap[FlashActivity::class.java]) + private fun intent(context: Context) = context.intent(FlashActivity::class.java) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) private fun intent(context: Context, file: File) = intent(context).setData(file.toUri()) diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/home/HomeFragment.kt b/app/src/main/java/com/topjohnwu/magisk/ui/home/HomeFragment.kt index 2d2b2161c..d9cd0df8b 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/home/HomeFragment.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ui/home/HomeFragment.kt @@ -87,8 +87,8 @@ class HomeFragment : BaseFragment(), .setTitle(R.string.proprietary_title) .setMessage(R.string.proprietary_notice) .setCancelable(false) - .setPositiveButton(R.string.yes) { _, _ -> download() } - .setNegativeButton(R.string.no_thanks) { _, _ -> viewModel.finishSafetyNetCheck(-2) } + .setPositiveButton(android.R.string.yes) { _, _ -> download() } + .setNegativeButton(android.R.string.no) { _, _ -> viewModel.finishSafetyNetCheck(-2) } .show() } diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/home/HomeViewModel.kt b/app/src/main/java/com/topjohnwu/magisk/ui/home/HomeViewModel.kt index fd5da72b0..ee86ab652 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/home/HomeViewModel.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ui/home/HomeViewModel.kt @@ -107,10 +107,10 @@ class HomeViewModel( Info.recovery = it ?: return@addOnPropertyChangedCallback } isConnected.addOnPropertyChangedCallback { - if (it == true) refresh() + if (it == true) refresh(false) } - refresh() + refresh(false) } fun paypalPressed() = OpenLinkEvent(Const.Url.PAYPAL_URL).publish() @@ -170,7 +170,11 @@ class HomeViewModel( } } - fun refresh() { + @JvmOverloads + fun refresh(invalidate: Boolean = true) { + if (invalidate) + Info.envRef.invalidate() + hasRoot.value = Shell.rootAccess() val fetchUpdate = if (isConnected.value) @@ -179,7 +183,8 @@ class HomeViewModel( Completable.complete() Completable.fromAction { - Info.loadMagiskInfo() + // Ensure value is ready + Info.env }.andThen(fetchUpdate) .applyViewModel(this) .doOnSubscribeUi { @@ -197,33 +202,40 @@ class HomeViewModel( private fun refreshVersions() { magiskCurrentVersion.value = if (magiskState.value != MagiskState.NOT_INSTALLED) { - version.format(Info.magiskVersionString, Info.magiskVersionCode) + VERSION_FMT.format(Info.env.magiskVersionString, Info.env.magiskVersionCode) } else { "" } - managerCurrentVersion.value = version - .format(BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE) + managerCurrentVersion.value = if (isRunningAsStub) MGR_VER_FMT + .format(BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE, Info.stub!!.version) + else + VERSION_FMT.format(BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE) } private fun updateSelf() { - magiskState.value = when (Info.magiskVersionCode) { + magiskState.value = when (Info.env.magiskVersionCode) { in Int.MIN_VALUE until 0 -> MagiskState.NOT_INSTALLED - !in Info.remote.magisk.versionCode..Int.MAX_VALUE -> MagiskState.OBSOLETE + in 1 until (Info.remote.magisk.versionCode - 1) -> MagiskState.OBSOLETE else -> MagiskState.UP_TO_DATE } - magiskLatestVersion.value = version - .format(Info.remote.magisk.version, Info.remote.magisk.versionCode) + magiskLatestVersion.value = + VERSION_FMT.format(Info.remote.magisk.version, Info.remote.magisk.versionCode) _managerState.value = when (Info.remote.app.versionCode) { in Int.MIN_VALUE until 0 -> MagiskState.NOT_INSTALLED //wrong update channel - in (BuildConfig.VERSION_CODE + 1)..Int.MAX_VALUE -> MagiskState.OBSOLETE - else -> MagiskState.UP_TO_DATE + in (BuildConfig.VERSION_CODE + 1) until Int.MAX_VALUE -> MagiskState.OBSOLETE + else -> { + if (isRunningAsStub && Info.stub!!.version < Info.remote.stub.versionCode) + MagiskState.OBSOLETE + else + MagiskState.UP_TO_DATE + } } - managerLatestVersion.value = version - .format(Info.remote.app.version, Info.remote.app.versionCode) + managerLatestVersion.value = MGR_VER_FMT + .format(Info.remote.app.version, Info.remote.app.versionCode, Info.remote.stub.versionCode) } private fun ensureEnv() { @@ -240,7 +252,8 @@ class HomeViewModel( } companion object { - private const val version = "%s (%d)" + private const val VERSION_FMT = "%s (%d)" + private const val MGR_VER_FMT = "%s (%d) (%d)" } } diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/module/ModulesFragment.kt b/app/src/main/java/com/topjohnwu/magisk/ui/module/ModulesFragment.kt index fa57d6605..61892804b 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/module/ModulesFragment.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ui/module/ModulesFragment.kt @@ -8,12 +8,12 @@ import android.view.MenuInflater import android.view.MenuItem import android.view.View import androidx.recyclerview.widget.RecyclerView -import com.topjohnwu.magisk.ClassMap import com.topjohnwu.magisk.Const import com.topjohnwu.magisk.R import com.topjohnwu.magisk.base.BaseFragment import com.topjohnwu.magisk.databinding.FragmentModulesBinding import com.topjohnwu.magisk.extensions.reboot +import com.topjohnwu.magisk.intent import com.topjohnwu.magisk.model.events.OpenFilePickerEvent import com.topjohnwu.magisk.model.events.ViewEvent import com.topjohnwu.magisk.ui.flash.FlashActivity @@ -28,7 +28,7 @@ class ModulesFragment : BaseFragment() override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { if (requestCode == Const.ID.FETCH_ZIP && resultCode == Activity.RESULT_OK && data != null) { // Get the URI of the selected file - val intent = Intent(activity, ClassMap[FlashActivity::class.java]) + val intent = activity.intent(FlashActivity::class.java) intent.setData(data.data).putExtra(Const.Key.FLASH_ACTION, Const.Value.FLASH_ZIP) startActivity(intent) } diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/settings/SettingsFragment.kt b/app/src/main/java/com/topjohnwu/magisk/ui/settings/SettingsFragment.kt index 67ca6f0bf..9ad8431f2 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/settings/SettingsFragment.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ui/settings/SettingsFragment.kt @@ -13,20 +13,17 @@ import androidx.preference.ListPreference import androidx.preference.Preference import androidx.preference.PreferenceCategory import androidx.preference.SwitchPreferenceCompat -import com.topjohnwu.magisk.BuildConfig -import com.topjohnwu.magisk.Config -import com.topjohnwu.magisk.Const -import com.topjohnwu.magisk.R +import com.topjohnwu.magisk.* import com.topjohnwu.magisk.base.BasePreferenceFragment import com.topjohnwu.magisk.data.database.RepoDao import com.topjohnwu.magisk.databinding.CustomDownloadDialogBinding +import com.topjohnwu.magisk.databinding.DialogCustomNameBinding import com.topjohnwu.magisk.extensions.subscribeK import com.topjohnwu.magisk.extensions.toLangTag import com.topjohnwu.magisk.model.download.DownloadService import com.topjohnwu.magisk.model.entity.internal.Configuration import com.topjohnwu.magisk.model.entity.internal.DownloadSubject import com.topjohnwu.magisk.model.observer.Observer -import com.topjohnwu.magisk.net.Networking import com.topjohnwu.magisk.utils.* import com.topjohnwu.magisk.view.dialogs.FingerprintAuthDialog import com.topjohnwu.superuser.Shell @@ -58,6 +55,7 @@ class SettingsFragment : BasePreferenceFragment() { findPreference("redesign_cat")?.isVisible = BuildConfig.DEBUG + // Get preferences updateChannel = findPreference(Config.Key.UPDATE_CHANNEL)!! rootConfig = findPreference(Config.Key.ROOT_ACCESS)!! autoRes = findPreference(Config.Key.SU_AUTO_RESPONSE)!! @@ -71,17 +69,68 @@ class SettingsFragment : BasePreferenceFragment() { val magiskCategory = findPreference("magisk")!! val suCategory = findPreference("superuser")!! val hideManager = findPreference("hide")!! - hideManager.setOnPreferenceClickListener { - PatchAPK.hideManager(requireContext()) - true + val restoreManager = findPreference("restore")!! + + // Remove/Disable entries + + // Only show canary channels if user is already on canary channel + // or the user have already chosen canary channel + if (!Utils.isCanary && Config.updateChannel < Config.Value.CANARY_CHANNEL) { + // Remove the last 2 entries + val entries = updateChannel.entries + updateChannel.entries = entries.copyOf(entries.size - 2) } - val restoreManager = findPreference("restore") - restoreManager?.setOnPreferenceClickListener { - DownloadService(requireContext()) { - subject = DownloadSubject.Manager(Configuration.APK.Restore) + + // Remove dangerous settings in secondary user + if (Const.USER_ID > 0) { + suCategory.removePreference(multiuserConfig) + } + + // Remove re-authentication option on Android O, it will not work + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + suCategory.removePreference(reauth) + } + + // Disable fingerprint option if not possible + if (!FingerprintHelper.canUseFingerprint()) { + fingerprint.isEnabled = false + fingerprint.isChecked = false + fingerprint.setSummary(R.string.disable_fingerprint) + } + + if (Const.USER_ID == 0 && Info.isConnected.value && Shell.rootAccess()) { + if (activity.packageName == BuildConfig.APPLICATION_ID) { + generalCatagory.removePreference(restoreManager) + hideManager.setOnPreferenceClickListener { + showManagerNameDialog { + PatchAPK.hideManager(requireContext(), it) + } + true + } + } else { + generalCatagory.removePreference(hideManager) + restoreManager.setOnPreferenceClickListener { + DownloadService(requireContext()) { + subject = DownloadSubject.Manager(Configuration.APK.Restore) + } + true + } } - true + } else { + // Remove if not primary user, no connection, or no root + generalCatagory.removePreference(restoreManager) + generalCatagory.removePreference(hideManager) } + + if (!Utils.showSuperUser()) { + preferenceScreen.removePreference(suCategory) + } + + if (!Shell.rootAccess()) { + preferenceScreen.removePreference(magiskCategory) + generalCatagory.removePreference(hideManager) + } + findPreference("clear")?.setOnPreferenceClickListener { Completable.fromAction { repoDB.clear() }.subscribeK { Utils.toast(R.string.repo_cache_cleared, Toast.LENGTH_SHORT) @@ -125,58 +174,7 @@ class SettingsFragment : BasePreferenceFragment() { setLocalePreference(findPreference(Config.Key.LOCALE)!!) - /* We only show canary channels if user is already on canary channel - * or the user have already chosen canary channel */ - if (!Utils.isCanary && Config.updateChannel < Config.Value.CANARY_CHANNEL) { - // Remove the last 2 entries - val entries = updateChannel.entries - updateChannel.entries = entries.copyOf(entries.size - 2) - - } - setSummary() - - // Disable dangerous settings in secondary user - if (Const.USER_ID > 0) { - suCategory.removePreference(multiuserConfig) - } - - // Disable re-authentication option on Android O, it will not work - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - reauth.isEnabled = false - reauth.isChecked = false - reauth.setSummary(R.string.android_o_not_support) - } - - // Disable fingerprint option if not possible - if (!FingerprintHelper.canUseFingerprint()) { - fingerprint.isEnabled = false - fingerprint.isChecked = false - fingerprint.setSummary(R.string.disable_fingerprint) - } - - if (Shell.rootAccess() && Const.USER_ID == 0) { - if (activity.packageName == BuildConfig.APPLICATION_ID) { - generalCatagory.removePreference(restoreManager) - } else { - if (!Networking.checkNetworkStatus(requireContext())) { - generalCatagory.removePreference(restoreManager) - } - generalCatagory.removePreference(hideManager) - } - } else { - generalCatagory.removePreference(restoreManager) - generalCatagory.removePreference(hideManager) - } - - if (!Utils.showSuperUser()) { - preferenceScreen.removePreference(suCategory) - } - - if (!Shell.rootAccess()) { - preferenceScreen.removePreference(magiskCategory) - generalCatagory.removePreference(hideManager) - } } override fun onSharedPreferenceChanged(prefs: SharedPreferences, key: String) { @@ -299,8 +297,8 @@ class SettingsFragment : BasePreferenceFragment() { AlertDialog.Builder(requireActivity()) .setTitle(R.string.settings_update_custom) .setView(v) - .setPositiveButton(R.string.ok) { _, _ -> onSuccess(url.text.toString()) } - .setNegativeButton(R.string.close) { _, _ -> onCancel() } + .setPositiveButton(android.R.string.ok) { _, _ -> onSuccess(url.text.toString()) } + .setNegativeButton(android.R.string.cancel) { _, _ -> onCancel() } .setOnCancelListener { onCancel() } .show() } @@ -324,11 +322,35 @@ class SettingsFragment : BasePreferenceFragment() { AlertDialog.Builder(requireActivity()) .setTitle(R.string.settings_download_path_title) .setView(binding.root) - .setPositiveButton(R.string.ok) { _, _ -> + .setPositiveButton(android.R.string.ok) { _, _ -> Utils.ensureDownloadPath(data.text.value)?.let { onSuccess(data.text.value) } ?: Utils.toast(R.string.settings_download_path_error, Toast.LENGTH_SHORT) } - .setNegativeButton(R.string.close, null) + .setNegativeButton(android.R.string.cancel, null) .show() } -} \ No newline at end of file + + private inline fun showManagerNameDialog( + crossinline onSuccess: (String) -> Unit + ) { + val data = ManagerNameData() + val view = DialogCustomNameBinding + .inflate(LayoutInflater.from(requireContext())) + .also { it.data = data } + + AlertDialog.Builder(requireActivity()) + .setTitle(R.string.settings_app_name) + .setView(view.root) + .setPositiveButton(android.R.string.ok) { _, _ -> + if (view.dialogNameInput.error.isNullOrBlank()) { + onSuccess(data.name.value) + } + } + .setNegativeButton(android.R.string.cancel, null) + .show() + } + + inner class ManagerNameData { + val name = KObservableField(resources.getString(R.string.re_app_name)) + } +} diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/superuser/SuperuserViewModel.kt b/app/src/main/java/com/topjohnwu/magisk/ui/superuser/SuperuserViewModel.kt index adbae5577..aa0bcfee4 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/superuser/SuperuserViewModel.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ui/superuser/SuperuserViewModel.kt @@ -84,8 +84,8 @@ class SuperuserViewModel( CustomAlertDialog(this) .setTitle(R.string.su_revoke_title) .setMessage(getString(R.string.su_revoke_msg, item.item.appName)) - .setPositiveButton(R.string.yes) { _, _ -> updateState() } - .setNegativeButton(R.string.no_thanks, null) + .setPositiveButton(android.R.string.yes) { _, _ -> updateState() } + .setNegativeButton(android.R.string.no, null) .setCancelable(true) .show() } @@ -144,4 +144,4 @@ class SuperuserViewModel( private fun deletePolicy(policy: MagiskPolicy) = policyDB.delete(policy.uid).andThen(Single.just(policy)) -} \ No newline at end of file +} diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/surequest/SuRequestActivity.kt b/app/src/main/java/com/topjohnwu/magisk/ui/surequest/SuRequestActivity.kt index b55e108c2..4ce2a60be 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/surequest/SuRequestActivity.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ui/surequest/SuRequestActivity.kt @@ -3,7 +3,6 @@ package com.topjohnwu.magisk.ui.surequest import android.content.pm.ActivityInfo import android.os.Build import android.os.Bundle -import android.text.TextUtils import android.view.Window import com.topjohnwu.magisk.R import com.topjohnwu.magisk.base.BaseActivity @@ -31,19 +30,17 @@ open class SuRequestActivity : BaseActivity { + if (!viewModel.handleRequest(intent)) + finish() + return + } + GeneralReceiver.LOG -> SuLogger.handleLogs(this, intent) + GeneralReceiver.NOTIFY -> SuLogger.handleNotify(this, intent) } - if (TextUtils.equals(action, GeneralReceiver.LOG)) - SuLogger.handleLogs(intent) - else if (TextUtils.equals(action, GeneralReceiver.NOTIFY)) - SuLogger.handleNotify(intent) - finish() } diff --git a/app/src/main/java/com/topjohnwu/magisk/utils/CachedValue.kt b/app/src/main/java/com/topjohnwu/magisk/utils/CachedValue.kt new file mode 100644 index 000000000..889eb9fa0 --- /dev/null +++ b/app/src/main/java/com/topjohnwu/magisk/utils/CachedValue.kt @@ -0,0 +1,22 @@ +package com.topjohnwu.magisk.utils + +class CachedValue(private val factory: () -> T) : Lazy { + + private var _val : T? = null + + override val value: T + get() { + val local = _val + return local ?: synchronized(this) { + _val ?: factory().also { _val = it } + } + } + + override fun isInitialized() = _val != null + + fun invalidate() { + synchronized(this) { + _val = null + } + } +} diff --git a/app/src/main/java/com/topjohnwu/magisk/utils/DataBindingAdapters.kt b/app/src/main/java/com/topjohnwu/magisk/utils/DataBindingAdapters.kt index fff3d12de..b65a58fff 100644 --- a/app/src/main/java/com/topjohnwu/magisk/utils/DataBindingAdapters.kt +++ b/app/src/main/java/com/topjohnwu/magisk/utils/DataBindingAdapters.kt @@ -31,6 +31,7 @@ import com.google.android.material.button.MaterialButton import com.google.android.material.card.MaterialCardView import com.google.android.material.floatingactionbutton.FloatingActionButton import com.google.android.material.navigation.NavigationView +import com.google.android.material.textfield.TextInputLayout import com.topjohnwu.magisk.R import com.topjohnwu.magisk.extensions.drawableCompat import com.topjohnwu.magisk.extensions.replaceRandomWithSpecial @@ -240,6 +241,13 @@ fun setEnabled(view: View, isEnabled: Boolean) { view.isEnabled = isEnabled } +@BindingAdapter("error") +fun TextInputLayout.setErrorString(error: String) { + val newError = error.let { if (it.isEmpty()) null else it } + if (this.error == null && newError == null) return + this.error = newError +} + // md2 @BindingAdapter("onSelectClick", "onSelectReset", requireAll = false) diff --git a/app/src/main/java/com/topjohnwu/magisk/utils/KObservableField.kt b/app/src/main/java/com/topjohnwu/magisk/utils/KObservableField.kt index 7ecf125f4..cda0e5eb0 100644 --- a/app/src/main/java/com/topjohnwu/magisk/utils/KObservableField.kt +++ b/app/src/main/java/com/topjohnwu/magisk/utils/KObservableField.kt @@ -9,15 +9,11 @@ import java.io.Serializable * You can define if wrapped type is Nullable or not. * You can use kotlin get/set syntax for value */ -class KObservableField : ObservableField, Serializable { +open class KObservableField : ObservableField, Serializable { var value: T - set(value) { - if (field != value) { - field = value - notifyChange() - } - } + get() = get() + set(value) { set(value) } constructor(init: T) { value = init @@ -27,23 +23,8 @@ class KObservableField : ObservableField, Serializable { value = init } - @Deprecated( - message = "Needed for data binding, use KObservableField.value syntax from code", - replaceWith = ReplaceWith("value") - ) + @Suppress("UNCHECKED_CAST") override fun get(): T { - return value + return super.get() as T } - - @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 +} diff --git a/app/src/main/java/com/topjohnwu/magisk/utils/Keygen.kt b/app/src/main/java/com/topjohnwu/magisk/utils/Keygen.kt new file mode 100644 index 000000000..1ba0dc982 --- /dev/null +++ b/app/src/main/java/com/topjohnwu/magisk/utils/Keygen.kt @@ -0,0 +1,132 @@ +package com.topjohnwu.magisk.utils + +import android.content.pm.PackageManager +import android.util.Base64 +import android.util.Base64OutputStream +import com.topjohnwu.magisk.Config +import com.topjohnwu.magisk.di.koinModules +import com.topjohnwu.signing.CryptoUtils.readCertificate +import com.topjohnwu.signing.CryptoUtils.readPrivateKey +import com.topjohnwu.superuser.internal.InternalUtils +import org.bouncycastle.asn1.x500.X500Name +import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter +import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder +import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder +import org.koin.core.context.GlobalContext +import org.koin.core.context.startKoin +import timber.log.Timber +import java.io.ByteArrayOutputStream +import java.math.BigInteger +import java.security.KeyPairGenerator +import java.security.KeyStore +import java.security.MessageDigest +import java.security.PrivateKey +import java.security.cert.X509Certificate +import java.util.* +import java.util.zip.GZIPInputStream +import java.util.zip.GZIPOutputStream + +private interface CertKeyProvider { + val cert: X509Certificate + val key: PrivateKey +} + +@Suppress("DEPRECATION") +class Keygen: CertKeyProvider { + + companion object { + private const val ALIAS = "magisk" + private val PASSWORD get() = "magisk".toCharArray() + private const val TESTKEY_CERT = "61ed377e85d386a8dfee6b864bd85b0bfaa5af81" + private const val DNAME = "C=US,ST=California,L=Mountain View,O=Google Inc.,OU=Android,CN=Android" + private const val BASE64_FLAG = Base64.NO_PADDING or Base64.NO_WRAP + } + + private val start = Calendar.getInstance() + private val end = Calendar.getInstance().apply { add(Calendar.YEAR, 30) } + + override val cert get() = provider.cert + override val key get() = provider.key + + private val provider: CertKeyProvider + + inner class KeyStoreProvider : CertKeyProvider { + private val ks by lazy { init() } + override val cert by lazy { ks.getCertificate(ALIAS) as X509Certificate } + override val key by lazy { ks.getKey(ALIAS, PASSWORD) as PrivateKey } + } + + class TestProvider : CertKeyProvider { + override val cert by lazy { + readCertificate(javaClass.getResourceAsStream("/keys/testkey.x509.pem")) + } + override val key by lazy { + readPrivateKey(javaClass.getResourceAsStream("/keys/testkey.pk8")) + } + } + + init { + // This object could possibly be accessed from an external app + // Get context from reflection into Android's framework + val context = InternalUtils.getContext() + val pm = context.packageManager + val info = pm.getPackageInfo(context.packageName, PackageManager.GET_SIGNATURES) + val sig = info.signatures[0] + val digest = MessageDigest.getInstance("SHA1") + val chksum = digest.digest(sig.toByteArray()) + + val sb = StringBuilder() + for (b in chksum) { + sb.append("%02x".format(0xFF and b.toInt())) + } + + provider = if (sb.toString() == TESTKEY_CERT) { + // The app was signed by the test key, continue to use it (legacy mode) + TestProvider() + } else { + KeyStoreProvider() + } + } + + private fun init(): KeyStore { + GlobalContext.getOrNull() ?: { + // Invoked externally, do some basic initialization + startKoin { + modules(koinModules) + } + Timber.plant(Timber.DebugTree()) + }() + + val raw = Config.keyStoreRaw + val ks = KeyStore.getInstance("PKCS12") + if (raw.isEmpty()) { + ks.load(null) + } else { + GZIPInputStream(Base64.decode(raw, BASE64_FLAG).inputStream()).use { + ks.load(it, PASSWORD) + } + } + + // Keys already exist + if (ks.containsAlias(ALIAS)) + return ks + + // Generate new private key and certificate + val kp = KeyPairGenerator.getInstance("RSA").apply { initialize(4096) }.genKeyPair() + val dname = X500Name(DNAME) + val builder = JcaX509v3CertificateBuilder(dname, BigInteger(160, Random()), + start.time, end.time, dname, kp.public) + val signer = JcaContentSignerBuilder("SHA256WithRSA").build(kp.private) + val cert = JcaX509CertificateConverter().getCertificate(builder.build(signer)) + + // Store them into keystore + ks.setKeyEntry(ALIAS, kp.private, PASSWORD, arrayOf(cert)) + val bytes = ByteArrayOutputStream() + GZIPOutputStream(Base64OutputStream(bytes, BASE64_FLAG)).use { + ks.store(it, PASSWORD) + } + Config.keyStoreRaw = bytes.toString("UTF-8") + + return ks + } +} diff --git a/app/src/main/java/com/topjohnwu/magisk/utils/Locales.kt b/app/src/main/java/com/topjohnwu/magisk/utils/Locales.kt new file mode 100644 index 000000000..f5fe0c8ea --- /dev/null +++ b/app/src/main/java/com/topjohnwu/magisk/utils/Locales.kt @@ -0,0 +1,49 @@ +package com.topjohnwu.magisk.utils + +import android.annotation.SuppressLint +import android.content.res.Configuration +import android.content.res.Resources +import com.topjohnwu.magisk.R +import com.topjohnwu.magisk.ResourceMgr +import com.topjohnwu.magisk.extensions.langTagToLocale +import io.reactivex.Single +import java.util.* +import kotlin.Comparator + +var currentLocale: Locale = Locale.getDefault() + +@SuppressLint("ConstantLocale") +val defaultLocale: Locale = Locale.getDefault() + +@Suppress("DEPRECATION") +val availableLocales = Single.fromCallable { + val compareId = R.string.app_changelog + mutableListOf().apply { + // Add default locale + add(Locale.ENGLISH) + + // Add some special locales + add(Locale.TAIWAN) + add(Locale("pt", "BR")) + + val config = Configuration() + val metrics = ResourceMgr.resource.displayMetrics + val res = Resources(ResourceMgr.resource.assets, metrics, config) + + // Other locales + val otherLocales = ResourceMgr.resource.assets.locales + .map { it.langTagToLocale() } + .distinctBy { + config.setLocale(it) + res.updateConfiguration(config, metrics) + res.getString(compareId) + } + + listOf("", "").toTypedArray() + + addAll(otherLocales) + }.sortedWith(Comparator { a, b -> + a.getDisplayName(a).toLowerCase(a) + .compareTo(b.getDisplayName(b).toLowerCase(b)) + }) +}.cache()!! diff --git a/app/src/main/java/com/topjohnwu/magisk/utils/PatchAPK.kt b/app/src/main/java/com/topjohnwu/magisk/utils/PatchAPK.kt index 1ed022ead..fa1cf6aa2 100644 --- a/app/src/main/java/com/topjohnwu/magisk/utils/PatchAPK.kt +++ b/app/src/main/java/com/topjohnwu/magisk/utils/PatchAPK.kt @@ -1,11 +1,14 @@ package com.topjohnwu.magisk.utils -import android.content.ComponentName import android.content.Context +import android.os.Build.VERSION.SDK_INT import android.widget.Toast import com.topjohnwu.magisk.* +import com.topjohnwu.magisk.data.network.GithubRawServices +import com.topjohnwu.magisk.extensions.DynamicClassLoader +import com.topjohnwu.magisk.extensions.get import com.topjohnwu.magisk.extensions.subscribeK -import com.topjohnwu.magisk.ui.SplashActivity +import com.topjohnwu.magisk.extensions.writeTo import com.topjohnwu.magisk.view.Notifications import com.topjohnwu.signing.JarMap import com.topjohnwu.signing.SignAPK @@ -47,81 +50,86 @@ object PatchAPK { } private fun findAndPatch(xml: ByteArray, from: String, to: String): Boolean { - if (from.length != to.length) + if (to.length > from.length) return false val buf = ByteBuffer.wrap(xml).order(ByteOrder.LITTLE_ENDIAN).asCharBuffer() val offList = mutableListOf() var i = 0 - while (i < buf.length - from.length) { - var match = true - for (j in 0 until from.length) { + loop@ while (i < buf.length - from.length) { + for (j in from.indices) { if (buf.get(i + j) != from[j]) { - match = false - break + ++i + continue@loop } } - if (match) { - offList.add(i) - i += from.length - } - ++i + offList.add(i) + i += from.length } if (offList.isEmpty()) return false + + val toBuf = to.toCharArray().copyOf(from.length) for (off in offList) { buf.position(off) - buf.put(to) + buf.put(toBuf) } return true } - private fun findAndPatch(xml: ByteArray, a: Int, b: Int): Boolean { - val buf = ByteBuffer.wrap(xml).order(ByteOrder.LITTLE_ENDIAN).asIntBuffer() - val len = xml.size / 4 - for (i in 0 until len) { - if (buf.get(i) == a) { - buf.put(i, b) - return true + private fun patchAndHide(context: Context, label: String): Boolean { + // If not running as stub, and we are compatible with stub, use stub + val src = if (!isRunningAsStub && SDK_INT >= 28 && Info.env.connectionMode == 3) { + val stub = File(context.cacheDir, "stub.apk") + val svc = get() + runCatching { + svc.fetchFile(Info.remote.stub.link).blockingGet().byteStream().use { + it.writeTo(stub) + } + }.onFailure { + Timber.e(it) + return false } + stub.path + } else { + context.packageCodePath } - return false - } - private fun patchAndHide(context: Context): Boolean { - // Generate a new app with random package name - val repack = File(context.filesDir, "patched.apk") + // Generate a new random package name and signature + val repack = File(context.cacheDir, "patched.apk") val pkg = genPackageName("com.", BuildConfig.APPLICATION_ID.length) + Config.keyStoreRaw = "" - if (!patch(context.packageCodePath, repack.path, pkg)) + if (!patch(src, repack.path, pkg, label)) return false // Install the application repack.setReadable(true, false) - if (!Shell.su("pm install $repack").exec().isSuccess) + if (!Shell.su("force_pm_install $repack").exec().isSuccess) return false Config.suManager = pkg Config.export() - Utils.rmAndLaunch(BuildConfig.APPLICATION_ID, - ComponentName(pkg, ClassMap.get>(SplashActivity::class.java).name)) + Shell.su("pm uninstall ${BuildConfig.APPLICATION_ID}").submit() return true } @JvmStatic - fun patch(apk: String, out: String, pkg: String): Boolean { + @JvmOverloads + fun patch(apk: String, out: String, pkg: String, label: String = "Manager"): Boolean { try { val jar = JarMap(apk) val je = jar.getJarEntry(Const.ANDROID_MANIFEST) val xml = jar.getRawData(je) if (!findAndPatch(xml, BuildConfig.APPLICATION_ID, pkg) || - !findAndPatch(xml, R.string.app_name, R.string.re_app_name)) + !findAndPatch(xml, "Magisk Manager", label)) return false // Write apk changes jar.getOutputStream(je).write(xml) - SignAPK.sign(jar, FileOutputStream(out).buffered()) + val keys = Keygen() + SignAPK.sign(keys.cert, keys.key, jar, FileOutputStream(out).buffered()) } catch (e: Exception) { Timber.e(e) return false @@ -130,11 +138,36 @@ object PatchAPK { return true } - fun hideManager(context: Context) { + fun patch(apk: File, out: File, pkg: String, label: String): Boolean { + try { + if (apk.length() < 1 shl 18) { + // APK is smaller than 256K, must be stub + return patch(apk.path, out.path, pkg, label) + } + + // Try using the new APK to patch itself + val loader = DynamicClassLoader(apk) + val cls = loader.loadClass("a.a") + + for (m in cls.declaredMethods) { + val pars = m.parameterTypes + if (pars.size == 4 && pars[0] == String::class.java) { + return m.invoke(null, apk.path, out.path, pkg, label) as Boolean + } + } + throw Exception("No matching method found") + } catch (e: Exception) { + Timber.e(e) + // Fallback to use the current implementation + return patch(apk.path, out.path, pkg, label) + } + } + + fun hideManager(context: Context, label: String) { Completable.fromAction { val progress = Notifications.progress(context, context.getString(R.string.hide_manager_title)) Notifications.mgr.notify(Const.ID.HIDE_MANAGER_NOTIFICATION_ID, progress.build()) - if (!patchAndHide(context)) + if (!patchAndHide(context, label)) Utils.toast(R.string.hide_manager_fail_toast, Toast.LENGTH_LONG) Notifications.mgr.cancel(Const.ID.HIDE_MANAGER_NOTIFICATION_ID) }.subscribeK() diff --git a/app/src/main/java/com/topjohnwu/magisk/utils/ResourceMgr.kt b/app/src/main/java/com/topjohnwu/magisk/utils/ResourceMgr.kt deleted file mode 100644 index fb3d7d503..000000000 --- a/app/src/main/java/com/topjohnwu/magisk/utils/ResourceMgr.kt +++ /dev/null @@ -1,126 +0,0 @@ -@file:Suppress("DEPRECATION") - -package com.topjohnwu.magisk.utils - -import android.annotation.SuppressLint -import android.content.Context -import android.content.ContextWrapper -import android.content.res.AssetManager -import android.content.res.Configuration -import android.content.res.Resources -import androidx.annotation.StringRes -import com.topjohnwu.magisk.Config -import com.topjohnwu.magisk.R -import com.topjohnwu.magisk.extensions.langTagToLocale -import io.reactivex.Single -import java.util.* - -var isRunningAsStub = false - -var currentLocale: Locale = Locale.getDefault() - private set - -@SuppressLint("ConstantLocale") -val defaultLocale: Locale = Locale.getDefault() - -val availableLocales = Single.fromCallable { - val compareId = R.string.app_changelog - mutableListOf().apply { - // Add default locale - add(Locale.ENGLISH) - - // Add some special locales - add(Locale.TAIWAN) - add(Locale("pt", "BR")) - - val config = Configuration() - val metrics = ResourceMgr.resource.displayMetrics - val res = Resources(ResourceMgr.resource.assets, metrics, config) - - // Other locales - val otherLocales = ResourceMgr.resource.assets.locales - .map { it.langTagToLocale() } - .distinctBy { - config.setLocale(it) - res.updateConfiguration(config, metrics) - res.getString(compareId) - } - - listOf("", "").toTypedArray() - - addAll(otherLocales) - }.sortedWith(Comparator { a, b -> - a.getDisplayName(a).toLowerCase(a) - .compareTo(b.getDisplayName(b).toLowerCase(b)) - }) -}.cache()!! - -private val addAssetPath by lazy { - AssetManager::class.java.getMethod("addAssetPath", String::class.java) -} - -fun AssetManager.addAssetPath(path: String) { - addAssetPath.invoke(this, path) -} - -fun Context.wrap(global: Boolean = true): Context - = if (!global) ResourceMgr.ResContext(this) else ResourceMgr.GlobalResContext(this) - -object ResourceMgr { - - lateinit var resource: Resources - private lateinit var resApk: String - - fun init(context: Context) { - resource = context.resources - if (isRunningAsStub) - resApk = DynAPK.current(context).path - } - - // Override locale and inject resources from dynamic APK - private fun Resources.patch(config: Configuration = Configuration(configuration)): Resources { - config.setLocale(currentLocale) - updateConfiguration(config, displayMetrics) - if (isRunningAsStub) - assets.addAssetPath(resApk) - return this - } - - fun reload(config: Configuration = Configuration(resource.configuration)) { - val localeConfig = Config.locale - currentLocale = when { - localeConfig.isEmpty() -> defaultLocale - else -> localeConfig.langTagToLocale() - } - Locale.setDefault(currentLocale) - resource.patch(config) - } - - fun getString(locale: Locale, @StringRes id: Int): String { - val config = Configuration() - config.setLocale(locale) - return Resources(resource.assets, resource.displayMetrics, config).getString(id) - } - - open class GlobalResContext(base: Context) : ContextWrapper(base) { - open val mRes: Resources get() = resource - private val loader by lazy { javaClass.classLoader!! } - - override fun getResources(): Resources { - return mRes - } - - override fun getClassLoader(): ClassLoader { - return loader - } - - override fun createConfigurationContext(config: Configuration): Context { - return ResContext(super.createConfigurationContext(config)) - } - } - - class ResContext(base: Context) : GlobalResContext(base) { - override val mRes by lazy { base.resources.patch() } - } - -} diff --git a/app/src/main/java/com/topjohnwu/magisk/utils/RootInit.kt b/app/src/main/java/com/topjohnwu/magisk/utils/RootInit.kt index c4bbfde4e..c7c01a1d1 100644 --- a/app/src/main/java/com/topjohnwu/magisk/utils/RootInit.kt +++ b/app/src/main/java/com/topjohnwu/magisk/utils/RootInit.kt @@ -5,6 +5,7 @@ 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.wrap import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.ShellUtils import com.topjohnwu.superuser.io.SuFile @@ -16,12 +17,14 @@ class RootInit : Shell.Initializer() { } fun init(context: Context, shell: Shell): Boolean { + // Invalidate env state if shell is recreated + Info.envRef.invalidate() + val job = shell.newJob() if (shell.isRoot) { job.add(context.rawResource(R.raw.util_functions)) - .add(context.rawResource(R.raw.utils)) + .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)) } @@ -35,6 +38,7 @@ class RootInit : Shell.Initializer() { Info.keepVerity = ShellUtils.fastCmd("echo \$KEEPVERITY").toBoolean() Info.keepEnc = ShellUtils.fastCmd("echo \$KEEPFORCEENCRYPT").toBoolean() Info.recovery = ShellUtils.fastCmd("echo \$RECOVERYMODE").toBoolean() + return true } } diff --git a/app/src/main/java/com/topjohnwu/magisk/utils/SuLogger.kt b/app/src/main/java/com/topjohnwu/magisk/utils/SuLogger.kt index b6134d584..98b201235 100644 --- a/app/src/main/java/com/topjohnwu/magisk/utils/SuLogger.kt +++ b/app/src/main/java/com/topjohnwu/magisk/utils/SuLogger.kt @@ -2,14 +2,13 @@ package com.topjohnwu.magisk.utils import android.content.Context import android.content.Intent -import android.content.pm.PackageManager import android.os.Process import android.widget.Toast import com.topjohnwu.magisk.Config import com.topjohnwu.magisk.R import com.topjohnwu.magisk.data.database.PolicyDao import com.topjohnwu.magisk.data.repository.LogRepository -import com.topjohnwu.magisk.extensions.inject +import com.topjohnwu.magisk.extensions.get import com.topjohnwu.magisk.model.entity.MagiskPolicy import com.topjohnwu.magisk.model.entity.toLog import com.topjohnwu.magisk.model.entity.toPolicy @@ -17,15 +16,13 @@ import java.util.* object SuLogger { - private val context: Context by inject() - - fun handleLogs(intent: Intent) { + fun handleLogs(context: Context, intent: Intent) { val fromUid = intent.getIntExtra("from.uid", -1) if (fromUid < 0) return if (fromUid == Process.myUid()) return - val pm: PackageManager by inject() + val pm = context.packageManager val notify: Boolean val data = intent.extras @@ -36,7 +33,7 @@ object SuLogger { }.getOrElse { return } } else { // Doesn't report whether notify or not, check database ourselves - val policyDB: PolicyDao by inject() + val policyDB = get() val policy = policyDB.fetch(fromUid).blockingGet() ?: return notify = policy.notification policy @@ -46,7 +43,7 @@ object SuLogger { return if (notify) - handleNotify(policy) + handleNotify(context, policy) val toUid = intent.getIntExtra("to.uid", -1) if (toUid < 0) return @@ -62,11 +59,11 @@ object SuLogger { date = Date() ) - val logRepo: LogRepository by inject() + val logRepo = get() logRepo.put(log).blockingGet()?.printStackTrace() } - private fun handleNotify(policy: MagiskPolicy) { + private fun handleNotify(context: Context, policy: MagiskPolicy) { if (policy.notification && Config.suNotification == Config.Value.NOTIFICATION_TOAST) { Utils.toast( context.getString( @@ -80,16 +77,16 @@ object SuLogger { } } - fun handleNotify(intent: Intent) { + fun handleNotify(context: Context, intent: Intent) { val fromUid = intent.getIntExtra("from.uid", -1) if (fromUid < 0) return if (fromUid == Process.myUid()) return runCatching { - val packageManager: PackageManager by inject() - val policy = fromUid.toPolicy(packageManager) + val pm = context.packageManager + val policy = fromUid.toPolicy(pm) .copy(policy = intent.getIntExtra("policy", -1)) if (policy.policy >= 0) - handleNotify(policy) + handleNotify(context, policy) } } } diff --git a/app/src/main/java/com/topjohnwu/magisk/utils/Utils.kt b/app/src/main/java/com/topjohnwu/magisk/utils/Utils.kt index 4e873aa8a..252b7b6d8 100644 --- a/app/src/main/java/com/topjohnwu/magisk/utils/Utils.kt +++ b/app/src/main/java/com/topjohnwu/magisk/utils/Utils.kt @@ -1,6 +1,5 @@ package com.topjohnwu.magisk.utils -import android.content.ComponentName import android.content.Context import android.content.Intent import android.content.res.Resources @@ -46,7 +45,7 @@ object Utils { .setRequiresDeviceIdle(true) .build() val request = PeriodicWorkRequest - .Builder(ClassMap[UpdateCheckService::class.java], 12, TimeUnit.HOURS) + .Builder(ClassMap[UpdateCheckService::class.java] as Class, 12, TimeUnit.HOURS) .setConstraints(constraints) .build() WorkManager.getInstance(context).enqueueUniquePeriodicWork( @@ -73,8 +72,4 @@ object Utils { if ((exists() && isDirectory) || mkdirs()) this else null } - fun rmAndLaunch(rm: String, component: ComponentName) { - Shell.su("(rm_launch $rm ${component.flattenToString()})").exec() - } - } diff --git a/app/src/main/java/com/topjohnwu/magisk/view/MarkDownWindow.kt b/app/src/main/java/com/topjohnwu/magisk/view/MarkDownWindow.kt index 071913a63..d5b844b92 100644 --- a/app/src/main/java/com/topjohnwu/magisk/view/MarkDownWindow.kt +++ b/app/src/main/java/com/topjohnwu/magisk/view/MarkDownWindow.kt @@ -48,7 +48,7 @@ object MarkDownWindow : KoinComponent { AlertDialog.Builder(activity) .setTitle(title) .setView(mv) - .setNegativeButton(R.string.close) { dialog, _ -> dialog.dismiss() } + .setNegativeButton(android.R.string.cancel) { dialog, _ -> dialog.dismiss() } .show() } } diff --git a/app/src/main/java/com/topjohnwu/magisk/view/Notifications.kt b/app/src/main/java/com/topjohnwu/magisk/view/Notifications.kt index 38770a8d8..ef8d5e026 100644 --- a/app/src/main/java/com/topjohnwu/magisk/view/Notifications.kt +++ b/app/src/main/java/com/topjohnwu/magisk/view/Notifications.kt @@ -4,15 +4,11 @@ import android.app.NotificationChannel import android.app.NotificationManager import android.app.PendingIntent import android.content.Context -import android.content.Intent import android.os.Build import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat import androidx.core.app.TaskStackBuilder -import com.topjohnwu.magisk.ClassMap -import com.topjohnwu.magisk.Const -import com.topjohnwu.magisk.Info -import com.topjohnwu.magisk.R +import com.topjohnwu.magisk.* import com.topjohnwu.magisk.extensions.get import com.topjohnwu.magisk.model.receiver.GeneralReceiver import com.topjohnwu.magisk.ui.SplashActivity @@ -20,6 +16,7 @@ import com.topjohnwu.magisk.ui.SplashActivity object Notifications { val mgr by lazy { NotificationManagerCompat.from(get()) } + private val icon by lazy { resolveRes(DynAPK.NOTIFICATION) } fun setup(context: Context) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { @@ -34,16 +31,16 @@ object Notifications { } fun magiskUpdate(context: Context) { - val intent = Intent(context, ClassMap[SplashActivity::class.java]) - intent.putExtra(Const.Key.OPEN_SECTION, "magisk") + val intent = context.intent(SplashActivity::class.java) + .putExtra(Const.Key.OPEN_SECTION, "magisk") val stackBuilder = TaskStackBuilder.create(context) - stackBuilder.addParentStack(ClassMap.get>(SplashActivity::class.java)) + stackBuilder.addParentStack(SplashActivity::class.java.cmp(context.packageName)) stackBuilder.addNextIntent(intent) val pendingIntent = stackBuilder.getPendingIntent(Const.ID.MAGISK_UPDATE_NOTIFICATION_ID, PendingIntent.FLAG_UPDATE_CURRENT) val builder = NotificationCompat.Builder(context, Const.ID.UPDATE_NOTIFICATION_CHANNEL) - builder.setSmallIcon(R.drawable.ic_magisk_outline) + builder.setSmallIcon(icon) .setContentTitle(context.getString(R.string.magisk_update_title)) .setContentText(context.getString(R.string.manager_download_install)) .setVibrate(longArrayOf(0, 100, 100, 100)) @@ -54,15 +51,15 @@ object Notifications { } fun managerUpdate(context: Context) { - val intent = Intent(context, ClassMap[GeneralReceiver::class.java]) - intent.action = Const.Key.BROADCAST_MANAGER_UPDATE - intent.putExtra(Const.Key.INTENT_SET_APP, Info.remote.app) + val intent = context.intent(GeneralReceiver::class.java) + .setAction(Const.Key.BROADCAST_MANAGER_UPDATE) + .putExtra(Const.Key.INTENT_SET_APP, Info.remote.app) val pendingIntent = PendingIntent.getBroadcast(context, Const.ID.APK_UPDATE_NOTIFICATION_ID, intent, PendingIntent.FLAG_UPDATE_CURRENT) val builder = NotificationCompat.Builder(context, Const.ID.UPDATE_NOTIFICATION_CHANNEL) - builder.setSmallIcon(R.drawable.ic_magisk_outline) + builder.setSmallIcon(icon) .setContentTitle(context.getString(R.string.manager_update_title)) .setContentText(context.getString(R.string.manager_download_install)) .setVibrate(longArrayOf(0, 100, 100, 100)) @@ -73,13 +70,13 @@ object Notifications { } fun dtboPatched(context: Context) { - val intent = Intent(context, ClassMap[GeneralReceiver::class.java]) + val intent = context.intent(GeneralReceiver::class.java) .setAction(Const.Key.BROADCAST_REBOOT) val pendingIntent = PendingIntent.getBroadcast(context, Const.ID.DTBO_NOTIFICATION_ID, intent, PendingIntent.FLAG_UPDATE_CURRENT) val builder = NotificationCompat.Builder(context, Const.ID.UPDATE_NOTIFICATION_CHANNEL) - builder.setSmallIcon(R.drawable.ic_magisk_outline) + builder.setSmallIcon(icon) .setContentTitle(context.getString(R.string.dtbo_patched_title)) .setContentText(context.getString(R.string.dtbo_patched_reboot)) .setVibrate(longArrayOf(0, 100, 100, 100)) diff --git a/app/src/main/java/com/topjohnwu/magisk/view/Shortcuts.kt b/app/src/main/java/com/topjohnwu/magisk/view/Shortcuts.kt index a97720bb8..61af35fda 100644 --- a/app/src/main/java/com/topjohnwu/magisk/view/Shortcuts.kt +++ b/app/src/main/java/com/topjohnwu/magisk/view/Shortcuts.kt @@ -15,55 +15,56 @@ import com.topjohnwu.superuser.Shell object Shortcuts { fun setup(context: Context) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) { + if (Build.VERSION.SDK_INT >= 25) { val manager = context.getSystemService(ShortcutManager::class.java) manager?.dynamicShortcuts = getShortCuts(context) } } - @RequiresApi(api = Build.VERSION_CODES.N_MR1) + @RequiresApi(api = 25) private fun getShortCuts(context: Context): List { val shortCuts = mutableListOf() val root = Shell.rootAccess() + val intent = context.intent(SplashActivity::class.java) if (Utils.showSuperUser()) { shortCuts.add(ShortcutInfo.Builder(context, "superuser") .setShortLabel(context.getString(R.string.superuser)) - .setIntent(Intent(context, ClassMap[SplashActivity::class.java]) + .setIntent(Intent(intent) .putExtra(Const.Key.OPEN_SECTION, "superuser") .setAction(Intent.ACTION_VIEW) .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)) - .setIcon(Icon.createWithResource(context, R.drawable.sc_superuser)) + .setIcon(Icon.createWithResource(context, resolveRes(DynAPK.SUPERUSER))) .setRank(0) .build()) } - if (root && Config.magiskHide) { + if (root && Info.env.magiskHide) { shortCuts.add(ShortcutInfo.Builder(context, "magiskhide") .setShortLabel(context.getString(R.string.magiskhide)) - .setIntent(Intent(context, ClassMap[SplashActivity::class.java]) + .setIntent(Intent(intent) .putExtra(Const.Key.OPEN_SECTION, "magiskhide") .setAction(Intent.ACTION_VIEW) .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)) - .setIcon(Icon.createWithResource(context, R.drawable.sc_magiskhide)) + .setIcon(Icon.createWithResource(context, resolveRes(DynAPK.MAGISKHIDE))) .setRank(1) .build()) } - if (!Config.coreOnly && root && Info.magiskVersionCode >= 0) { + if (!Config.coreOnly && root && Info.env.magiskVersionCode >= 0) { shortCuts.add(ShortcutInfo.Builder(context, "modules") .setShortLabel(context.getString(R.string.modules)) - .setIntent(Intent(context, ClassMap[SplashActivity::class.java]) + .setIntent(Intent(intent) .putExtra(Const.Key.OPEN_SECTION, "modules") .setAction(Intent.ACTION_VIEW) .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)) - .setIcon(Icon.createWithResource(context, R.drawable.sc_extension)) + .setIcon(Icon.createWithResource(context, resolveRes(DynAPK.MODULES))) .setRank(3) .build()) shortCuts.add(ShortcutInfo.Builder(context, "downloads") .setShortLabel(context.getString(R.string.downloads)) - .setIntent(Intent(context, ClassMap[SplashActivity::class.java]) + .setIntent(Intent(intent) .putExtra(Const.Key.OPEN_SECTION, "downloads") .setAction(Intent.ACTION_VIEW) .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)) - .setIcon(Icon.createWithResource(context, R.drawable.sc_cloud_download)) + .setIcon(Icon.createWithResource(context, resolveRes(DynAPK.DOWNLOAD))) .setRank(2) .build()) } diff --git a/app/src/main/java/com/topjohnwu/magisk/view/dialogs/EnvFixDialog.kt b/app/src/main/java/com/topjohnwu/magisk/view/dialogs/EnvFixDialog.kt index 8eb37107b..12d8b81a2 100644 --- a/app/src/main/java/com/topjohnwu/magisk/view/dialogs/EnvFixDialog.kt +++ b/app/src/main/java/com/topjohnwu/magisk/view/dialogs/EnvFixDialog.kt @@ -23,7 +23,7 @@ class EnvFixDialog(activity: Activity) : CustomAlertDialog(activity) { setTitle(R.string.env_fix_title) setMessage(R.string.env_fix_msg) setCancelable(true) - setPositiveButton(R.string.yes) { _, _ -> + setPositiveButton(android.R.string.yes) { _, _ -> val pd = ProgressDialog.show(activity, activity.getString(R.string.setup_title), activity.getString(R.string.setup_msg)) @@ -46,6 +46,6 @@ class EnvFixDialog(activity: Activity) : CustomAlertDialog(activity) { } }.exec() } - setNegativeButton(R.string.no_thanks, null) + setNegativeButton(android.R.string.no, null) } } diff --git a/app/src/main/java/com/topjohnwu/magisk/view/dialogs/FingerprintAuthDialog.kt b/app/src/main/java/com/topjohnwu/magisk/view/dialogs/FingerprintAuthDialog.kt index e11b5a6bf..a27d599de 100644 --- a/app/src/main/java/com/topjohnwu/magisk/view/dialogs/FingerprintAuthDialog.kt +++ b/app/src/main/java/com/topjohnwu/magisk/view/dialogs/FingerprintAuthDialog.kt @@ -31,7 +31,7 @@ class FingerprintAuthDialog(activity: Activity, private val callback: () -> Unit binding.message.compoundDrawablePadding = Utils.dpInPx(20) binding.message.gravity = Gravity.CENTER setMessage(R.string.auth_fingerprint) - setNegativeButton(R.string.close) { _, _ -> + setNegativeButton(android.R.string.cancel) { _, _ -> helper?.cancel() failureCallback?.invoke() } diff --git a/app/src/main/java/com/topjohnwu/magisk/view/dialogs/InstallMethodDialog.kt b/app/src/main/java/com/topjohnwu/magisk/view/dialogs/InstallMethodDialog.kt index 4d0e0c09a..6e1e812c1 100644 --- a/app/src/main/java/com/topjohnwu/magisk/view/dialogs/InstallMethodDialog.kt +++ b/app/src/main/java/com/topjohnwu/magisk/view/dialogs/InstallMethodDialog.kt @@ -62,12 +62,12 @@ internal class InstallMethodDialog(activity: BaseActivity<*, *>, options: List + .setPositiveButton(android.R.string.yes) { _, _ -> DownloadService(activity) { subject = DownloadSubject.Magisk(Configuration.Flash.Secondary) } } - .setNegativeButton(R.string.no_thanks, null) + .setNegativeButton(android.R.string.no, null) .show() } } diff --git a/app/src/main/res/layout/activity_main_content.xml b/app/src/main/res/layout/activity_main_content.xml index b2b8363df..ab5775c0d 100644 --- a/app/src/main/res/layout/activity_main_content.xml +++ b/app/src/main/res/layout/activity_main_content.xml @@ -32,7 +32,7 @@ - + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/raw/utils.sh b/app/src/main/res/raw/utils.sh index 4e2a298a1..79ca207b4 100644 --- a/app/src/main/res/raw/utils.sh +++ b/app/src/main/res/raw/utils.sh @@ -110,8 +110,12 @@ EOF cd / } -rm_launch() { - pm uninstall $1 - am start -n $2 - exit +force_pm_install() { + local APK=$1 + local VERIFY=`settings get global package_verifier_enable` + [ "$VERIFY" -eq 1 ] && settings put global package_verifier_enable 0 + pm install -r $APK + local res=$? + [ "$VERIFY" -eq 1 ] && settings put global package_verifier_enable 1 + return $res } diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 708b2223a..a7be4c183 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -89,7 +89,6 @@ إعادة التشغيل خلال خمس ثواني… - إغلاق تثبيت %1$s هل تريد تثبيت %1$s ? التنزيل @@ -184,7 +183,6 @@ تستخدم كافة جلسات العمل للروت مساحة الاسم ذات التركيب العامة سترث جلسات العمل للروت مساحة الأسماء لطالبيها سيكون لكل جلسة عمل للروت مساحة اسم معزولة خاصة بها - لا يدعم إصدار الأندرويد +8.0 لم تُعين بصمات الأصابع أو لا يوجد قارئ بصمات خطأ عند إنشاء مجلد. عليه أن يكون سهلا الوصول إليه من خلال مجلد التخزين للروت و ألا يكون ملفا. diff --git a/app/src/main/res/values-az/strings.xml b/app/src/main/res/values-az/strings.xml index 3f4d0b1c2..d8ae0ac98 100644 --- a/app/src/main/res/values-az/strings.xml +++ b/app/src/main/res/values-az/strings.xml @@ -1,122 +1,70 @@ - Əlavələr - Endirmələr - Superuser - Log - Tənzimləmələr - Quraşdır - Dəstəklənməyən Magisk Versiyası - Magisk Manager\'in bu versiyası Magisk\'in v18.0 versiyasndan aşağısını dəstəkləmir.\n\nMagisk\'i əllə yüksəldə, yaxud tətbiqi əvvəlki versiyalarına qaytara bilərsiniz. - Magisk yüklənməyib. - Yeniləmələr yoxlanılır… - Etibarsız Yeniləmə Kanalı - SafetyNet vəziyətinə bax - SafetyNet vəziyəti yoxlanılır… - SafetyNet Uğurla Yoxlanıldı - SafetyNet API Xətası - Cavab etibarsızdır. - Magisk ən yenidir - Magisk Manager ən yenidir - Qabaqcıl Parametrlər - Şifrələməyə məcbur etməni qoru - AVB 2.0/dm-verity\'i qoru - Yüklənən: %1$s - Ən son: %1$s - Sil - Magisk\'i Sil - Bütün əlavələr ləğv olunacaq/silinəcək. Root silinəcək, və əgər hal-hazırda deyilsə, bütün məlumatlarınız potensiyal olaraq şifrələnəcək. - Yenilə - (Yalnız nüvə modu qoşulub) - (Məlumat təmin edilməyib) - Əlavələr yoxdur. - Əlavə sonrakı yenidən başlatmada yenilənəcək. - Əlavə sonrakı yenidən başlatmada silinəcək. - Əlavə sonrakı yenidən başlatmada silinməyəcək. - Əlavə sonrakı yenidən başlatmada qapadılacaq. - Əlavə sonrakı yenidən başlatmada açılacaq. - %1$s tərəfindən yaradılıb - Bərpa rejimində yenidən başlat - Bootloader\'ə yenidən başlat - Yükləmə rejimində yenidən başlat EDL\'ə yenidən başlat - Yeniləmə Var - Yüklənib - Yüklənməyib - Yeniləmə vaxtı: %1$s - Nizamlama Qaydası - Ada görə nizamla - Son yeniləməyə görə nizamla - Log\'u saxla - Təzələ - Log\'u indi təmizlə - Log uğurla təmizləndi. - Yeniliklər - Magisk Yeniləmələri Nəticə Bildirişləri Yükləmə bitdi @@ -127,262 +75,135 @@ Magisk Manager Yeniləməsi Var! - - Qapat - %1$s faylını yüklə - %1$s faylını indi yükləmək istəyirsiniz? - Yüklə - Yenidən Başlat - Tənzimləmələri saxlamaq üçün yenidən başladın. - Yeniliklər - Repo keşi silindi - Yükləyib quraşdırmaq üçün toxun. - DTBO yamaqlanıb! - Magisk Manager dtbo.img\'ni yamaqladı. Xahiş olunur yenidən başladın. - Qurulur - Magisk Manager gizlədilir… - Magisk Manager\'i gizlətmək alınmadı. - Keçid açmağa heçbir tətbiq tapılmadı. - Yalnız Zip yüklə - Birdəfəlik Yüklə (Tövsiyə olunur) - Fayl Seç və Yamaqla - Fəal olmayan slota quraşdır (OTA\'dan sonra) - Xəbərdarlıq - Cihazınız yenidən başladıldıqdan sonra fəal olmayan slota başlamağa MƏCBUR ediləcək!\nBu seçimi yalnız OTA bitdikdən sonra istifadə edin.\nDavam edirsiniz? - Üsul Seçin - Silməni Bitir - Surətləri Qaytar - Geri qaytarılır… - Geri qaytarma bitdi! - Stock nüsxə mövcud deyil! - Özəl kodu yükləyin - Magisk Manager açıq lisenziyalıdır və Google\'ın özəl SafetyNet API kodunu ehtiva etmir.\n\Magisk Managerə SafetyNet yoxlamaları üçün tərkibində GoogleApiClient olan əlavəni yükləməyə icazə verirsiniz? - Quraşdırma alınmadı. - Əlavə Quraşdırma Lazımdır - Cihazınızın Magisk\'in düzgün işləməsi üçün əlavə quraşdırmaya ehtiyacı var . Bu Magisk zip faylını endirəcək, davam etmək istəyirsiniz? - Əlavə quraşdırma - Quraşdırma yerinə yetirilir… - Ümumi - Qaranlıq Mövzu - Qaranlıq mövzunu aç. - Repo Keşini Təmizlə - Onlayn repolar üçün keşlənmiş məlumatı silin. Bu tətbiqi onlayn şəkildə yenilənməyə məcbur edir. - Magisk Manager\'i Gizlə - Magisk Manager\'i təsadüfi adla yenidən sıxışdır. - Magisk Manager\'i Geri Qaytar - Magisk Manager\'i orjinal sıxışdırma ilə geri qaytar - Dil - (Sistem Dili) - Tənzimləmələri Yenilə - Yeniləmələri Yoxla - Axraplanda vaxtaşırı yeniləmələri yoxla. - Kanalı Yenilə - Stabil - Beta - Özəl - Özəl URL daxil edin - Magisk Yalnız Nüvə Modu - Yalnız nüvə xüsusiyyətlərini aç. MagiskSU və MagiskHide hələ də açıq qalacaq, amma əlavələr yüklənməyəcək. - Magisk\'i fərqli növdə aşkarlamalardan gizləyin. - Sistemsiz host\'lar - Adblock tətbiqləri üçün Sistemsiz host dəstəyi. - Sistemsiz host əlavəsi quraşdırıldı - Tətbiqlər və ADB - Yalnız Tətbiqlər - Yalnız ADB - Qapalı - 10 saniyə - 15 saniyə - 20 saniyə - 30 saniyə - 45 saniyə - 60 saniyə - Superuser İcazəsi - Avtomatik Cavab - İcazə Vaxtaşımı - Superuser Bildirişləri - %1$d saniyə - Yüksəltmədən sonra Yenidən İdentifikasiya et - Tətbiq yeniləmələridən sonra superuser icazələrini yenidən identifikasiya et - Barmaq İzi İdentifikasiyasını Aç - Barmaq izi oxuyucunu superuser icazələri üçün işlət - Barmaq izini İdentifikasiya et - Çox-istifadəçi modu - Yalnız cihaz sahibi - Cihaz sahibinin idarəçiliyində - İstifadəçidən asılı olmayaraq - Yalnız cihaz sahibinin root icazəsi var. - Yalnız cihaz sahibi root icazələrini redaktə edə və icazə istəkləri qəbul edə bilər. - Hər istifadəçinin ayrı root qaydaları var. - Namespace Modunu Qoş - Qlobal Namespace - Keçmə Namespace - Ayrılmış Namespace - Bütün root sessyaları qlobal qoşma namespace\'dən istifadə edir. - Root sessyaları soruşulan namespace\'ləri birindən digərinə keçirəcək. - Hər bir root sessyasının ayrılmış namespace\'i olacaq. - - Android 8.0+\'da dəstəklənmir. - Barmaq izi təyin edilməyib ya da dəstəklənmir. - Superuser Tələbi - Ləğv et - Yönləndir - Təmin et - Cihazın tam icazəsi ilə təmin edin.\nƏmin deyilsinizsə ləğv edin! - Sonsuz - Bir dəfəlik - 10 dəq - 20 dəq - 30 dəq - 60 dəq - %1$s SuperUser icazəsi ilə təmin edildi - %1$s SuperUser icazəsi ilə təmin edilmədi - Tətbiq yoxdur - %1$s üçün Superuser icazəsi verilib - %1$s üçün Superuser icazəsi verilməyib - %1$s üçün bildirişlər açıqdır - %1$s üçün bildirişlər bağlıdır - %1$s üçün giriş açıqdır - %1$s üçün giriş bağlıdır - Ləğv olunsun? - %1$s üçün haqları ləğv etməyi təsdiq edirsiniz? - Tost - Heçnə - İdentifikasiya xətası - PID: %1$d - Hədəf UID: %1$d - Komanda: %1$s - Sistem tətbiqlərini göstər - diff --git a/app/src/main/res/values-bg/strings.xml b/app/src/main/res/values-bg/strings.xml index 5f7cce367..10c8b3378 100644 --- a/app/src/main/res/values-bg/strings.xml +++ b/app/src/main/res/values-bg/strings.xml @@ -61,7 +61,6 @@ Списък с промени - Затваряне Инсталиране на %1$s Желаете ли да инсталирате %1$s сега? Изтегляне @@ -162,7 +161,6 @@ Всички сесии с руут достъп използват глобалното именно пространство. Всички сесии с руут достъп наследяват именното пространство на запитващото приложение. Всички сесии с руут достъп имат собствени именни пространства. - Не поддържа Android 8.0+. Не са добавени пръстови отпечатъци или устройството не поддържа тази функция. diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml index 7f1af1549..76edbd704 100644 --- a/app/src/main/res/values-ca/strings.xml +++ b/app/src/main/res/values-ca/strings.xml @@ -24,6 +24,7 @@ Configuració avançada Mantenir el xifrat forçat Mantenir AVB 2.0/dm-verity + Mode de Recuperació Instal·lada: %1$s Última: %1$s Desinstal·lar @@ -75,19 +76,18 @@ Actualització de Magisk Manager disponible! - Prem per descarregar i instalar. - Descarrega només el ZIP + Premi per baixar i instalar. + Únicament baixa el ZIP Instal·lació directa (Recomanat) Instal·la a la ranura inactiva (Després d\'una OTA) El teu dispositiu serà FORÇAT a arrancar en l\'actual ranura inactiva després del reinici!\nUtilitza aquesta opció NOMÉS quan l\'OTA s\'hagi fet.\nContinuar? - Sel·lecciona mètode + Sel·lecciona un mètode Instal·lació addicional Sel·lecciona i arranja un arxiu Sel·lecciona una imatge crua (*.img) o un ODIN tarfile (*.tar) Reinici en 5 segons… - Tancar Instal·lar %1$s Vols instal·lar %1$s ara? Baixar @@ -110,23 +110,23 @@ Restaurant… Restauració feta! La còpia de seguretat de Stock no existeix! - Descarrega codi propietari - Magisk Manager és codi lliure i no conté codi de l\'API de SafetyNet, ja que és codi propietari de Google.\n\nPot permetre que Magisk Manager descarregui una extensió que conté el GoogleApiClient per poder fer la comprobació de SafetyNet? + Baixar codi propietari + Magisk Manager és codi lliure i no conté codi de l\'API de SafetyNet, ja que és codi propietari de Google.\n\nPot permetre que Magisk Manager baixi una extensió que conté el GoogleApiClient per poder fer la comprobació de SafetyNet? Instal·lació fallida. Es requereix instal·lació addicional - El teu dispositiu necessita instal·lació addicional per Magisk per funcionar correctament. Es descarregarà el ZIP d\'instal·lació de Magisk , vol procedir a l\'instalació ara? + El teu dispositiu necessita instal·lació addicional per Magisk per funcionar correctament. Es baixarà el ZIP d\'instal·lació de Magisk , vol procedir a l\'instalació ara? S\'està executant la configuració de l\'entorn… General - Tema obscur - Habilitar el tema obscur - Directori de descàrrega + Tema fosc + Habilitar el tema fosc + Directori de baixades Els arxius es desaràn a %1$s Netejar memòria cau del repositori Neteja l\'informació en memòria cau per als repositoris en línia, força a l\'aplicació a actualitzar-se en línia. Amagar Magisk Manager - Re-empaquetar Magisk Manager amb un nom de paquet a l\'atzar + Reempaquetar Magisk Manager amb un nom de paquet a l\'atzar Restaurar Magisk Manager Restaura Magisk Manager amb el nom de paquet original Idioma @@ -146,6 +146,10 @@ Suport per aplicacions tipus Adblock fora de la partició del sistema Agregat el mòdul Systemless Hosts + Escriu el nom desitjat per l\'App + Nou nom + Es refarà l\'App amb aquest nom + Format invàl·lid Aplicacions y ADB Només aplicacions Només ADB @@ -161,8 +165,8 @@ Temps de petició Notificació de superusuari %1$d segons - Re-autenticació - Demanar permisos de superusuari novament si una aplicació es actualitzada o reinstal·lada + Demanar després d\'una actualització + Demanar permisos de superusuari novament si una aplicació és actualitzada o reinstal·lada Autenticació per Empremta Dactilar Utilitza el sensor d\'Empremta Dactilar per permetre les sol·licituds de superusuari Autenticar Emprempta Digital @@ -174,7 +178,7 @@ Només l\'administrador té accés d\'arrel Només l\'administrador pot supervisar l\'acces d\'arrel y rebre sol·licituds d\'altres usuaris Tots els usuaris tenen separades les seves pròpies regles d\'arrel - + Muntar Namespace Namespace Global Heretar Namespace @@ -182,7 +186,6 @@ Totes les sessions d\'arrel utilitzen el suport Namespace Global Les sessions d\'arrel heretaran les peticiones Namespace Totes les sessions d\'arrel tindran la seva pròpia Namespace - No es compatible amb Android 8.0+ No s\'han establert empremtes dactilars o no existeix el suport del dispositiu Error al crear la carpeta. El directori ha de ser accesible desde el directori arrel i no pot ser un arxiu. @@ -217,7 +220,7 @@ PID: %1$d UID de l\'objectiu: %1$d Ordre: %1$s - + Mostra apps del sistema diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 13679ed99..7f9d1abfc 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -84,7 +84,6 @@ Restartování za 5 sekund… - Zavřít Instalovat %1$s Chcete nyní nainstalovat %1$s? Stáhnout @@ -175,7 +174,6 @@ Všechny relace root používají globální připojení jmenného prostoru. Kořenové relace dědí jmenný prostor žadatele. Každá relace root bude mít svůj vlastní izolovaný jmenný prostor. - Nepodporuje Android 8.0+. Nebyly nastaveny žádné otisky prstů ani žádná podpora zařízení. diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index b17951ed0..4a954d4c2 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -84,7 +84,6 @@ Neustart in 5 Sekunden… - Schließen Installiere %1$s Möchtest du %1$s installieren? Herunterladen @@ -177,7 +176,6 @@ Alle Root-Sitzungen benutzen den global angelegten Namespace Root-Sitzungen erben den Namespace des Abfragenden Jede Root-Sitzung hat ihren isolierten Namespace - Android 8.0+ wird nicht unterstützt Keine Fingerabdrücke gespeichert oder keine Geräteunterstützung diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index d8e3a535a..38311bb25 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -62,7 +62,6 @@ Καταγραφή αλλαγών εφαρμογής - Κλείσιμο Εγκατάσταση %1$s Θέλετε να εγκαταστήσετε το %1$s τώρα; Λήψη @@ -144,7 +143,6 @@ Όλες οι συνεδρίες root χρησιμοποιούν τον καθολικό χώρο oνομάτων προσάρτησης Οι συνεδρίες root θα κληρονομούν το χώρο ονομάτων του αιτούντα τους Κάθε συνεδρία root θα έχει το δικό της απομονωμένο χώρο ονομάτων - Δεν υποστηρίζεται Android 8.0+ Αίτημα υπερχρήστη diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 44722a4d6..547e88569 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -24,7 +24,7 @@ Ajustes avanzados Mantener cifrado forzado Mantener AVB 2.0/dm-verity - Modo Recovery + Modo Recovery Instalada: %1$s Última: %1$s Desinstalar @@ -45,7 +45,7 @@ Reiniciar en Modo Recovery Reiniciar en Modo Bootloader Reiniciar en Modo Download - Reiniciar en Modo EDL + Reiniciar en Modo EDL Actualización Disponible @@ -72,21 +72,20 @@ Error descargando archivo Actualización de Magisk disponible! Actualización de Magisk Manager disponible! - - - Pulse para descargar e instalar - Descargar sólo el archivo ZIP - Instalación Directa (Recomendado) - Instalar en ranura inactiva (después de OTA) - ¡Se forzará su dispositivo para que arranque en la ranura inactiva actual después de un reinicio!\nUtilice esta opción solo después de que se haya completado la OTA.\nContinuar? - Seleccionar Método - Configuración Adicional - Seleccionar y parchear un archivo - Seleccione una imagen raw (* .img) o un archivo tar de ODIN (* .tar) - Reiniciando en 5 segundos… - + + + Pulse para descargar e instalar + Descargar sólo el archivo ZIP + Instalación Directa (Recomendado) + Instalar en ranura inactiva (después de OTA) + ¡Se forzará su dispositivo para que arranque en la ranura inactiva actual después de un reinicio!\nUtilice esta opción solo después de que se haya completado la OTA.\nContinuar? + Seleccionar Método + Configuración Adicional + Seleccionar y parchear un archivo + Seleccione una imagen raw (* .img) o un archivo tar de ODIN (* .tar) + Reiniciando en 5 segundos… + - Cerrar Instalar %1$s ¿Quieres instalar %1$s ahora? Descargar @@ -121,7 +120,7 @@ Tema oscuro Habilitar el tema oscuro Ruta de Descarga - Los archivos se guardarán en %1$s + Los archivos se guardarán en %1$s Limpiar caché del repositorio Limpiar la información en caché para los repositorios en línea, fuerza a la aplicación a actualizar en línea Ocultar Magisk Manager @@ -180,7 +179,6 @@ Todas las sesiones de root utilizan el soporte Global Namespace Las sesiones de root heredarán las peticiones Namespace Cada sesión root tendrá su propia Namespace - No es compatible con Android 8.0+ No se establecieron huellas dactilares o no existe soporte del dispositivo Error al crear la carpeta. Debe ser accesible desde el directorio raíz de almacenamiento y no debe ser un archivo. @@ -218,5 +216,5 @@ Mostrar sistema - + diff --git a/app/src/main/res/values-et/strings.xml b/app/src/main/res/values-et/strings.xml index 46006d63b..4effbee81 100644 --- a/app/src/main/res/values-et/strings.xml +++ b/app/src/main/res/values-et/strings.xml @@ -88,7 +88,6 @@ Taaskäivitamine 5 sekundi pärast… - Sulge Installi %1$s Kas soovid kohe installida %1$s? Allalaadimine @@ -183,7 +182,6 @@ Kõik juurkasutaja sessioonid kasutavad globaalset monteerimise nimeruumi. Juurkasutaja sessioonid võtavad üle selle taotleja nimeruumi. Iga juurkasutaja sessioon saab oma isoleeritud nimeruumi. - Ei toeta Androidi versiooni 8.0+. Sõrmejälgi pole määratud või seade pole toetatud. Faili loomisel esines viga. See peab olema ligipääsetav mäluruumi juurkaustast ning ei tohi olla fail. diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 47c188174..2fcb32888 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -88,7 +88,6 @@ Redémarrage dans 5 secondes… - Fermer Installer %1$s Voulez‐vous installer %1$s maintenant ? Télécharger @@ -183,7 +182,6 @@ Toutes les sessions super‐utilisateur utilisent l’espace de noms global du montage. Les sessions super‐utilisateur hériteront de l’espace de noms de leur demandeur. Chaque session super‐utilisateur aura son propre espace de noms isolé. - Android 8.0 et supérieurs ne sont pas pris en charge. Aucune empreinte digitale n’a été définie ou le lecteur d’empreinte n’est pas pris en charge. Erreur lors de la création du dossier. Il doit être accessible depuis le répertoire racine du stockage et ne doit pas être un fichier. diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml new file mode 100644 index 000000000..759d5dd33 --- /dev/null +++ b/app/src/main/res/values-hi/strings.xml @@ -0,0 +1,223 @@ + + + + मॉड्यूल + डाउनलोड + उत्तम उपयोगकर्ता + अभिलेख + सेटिंग्स + स्थापित करें + असमर्थित Magisk संस्करण + Magisk Manager का यह संस्करण Magisk के v18.0 संस्करण से कम का समर्थन नहीं करता है.\n\nआप या तो खुद से Magisk को अपग्रेड करें, या फिर एप्लीकेशन को पुराने संस्करण पे डाउनग्रेड करें . + + + Magisk स्थापित नहीं है + अपडेट्स के लिए जांच हो रही है… + अमान्य अपडेट चैनल + SafetyNet की जांच शुरू करें + SafetyNet के स्थिति की जाँच हो रही है… + SafetyNet की जांच सफल हुई + SafetyNet API त्रुटि + अनुक्रिया अमान्य है. + Magisk अप टू डेट है + Magisk Manager अप टू डेट है + एडवांस सेटिंग्स + बल एन्क्रिप्शन को बनाये रखें + AVB 2.0/dm-verity को बनाये रखें + रिकवरी मोड + स्थापित: %1$s + नवीनतम: %1$s + स्थापना रद्द करें + Magisk की स्थापना रद्द करें + सभी मॉड्यूल अक्षम/हटा दिए जाएंगे. रुट और आपका संभावित रूप से एन्क्रिप्ट डाटा हटा दिया जाएगा. + अपडेट करें + (केवल मूल मोड समर्थकृत) + + + (कोई जानकारी प्रदान नहीं की गई) + कोई मॉड्यूल नहीं मिला + मॉड्यूल अगले रिबूट पे अपडेट किया जाएगा ! + मॉड्यूल अगले रिबूट पे हटाया जाएगा ! + मॉड्यूल अगले रिबूट पे नहीं हटाया जाएगा ! + मॉड्यूल अगले रिबूट पे निर्योग्य किया जाएगा ! + मॉड्यूल अगले रिबूट पे योग्य किया जाएगा ! + %1$s के द्वारा बनाया गया + रिकवरी मोड में रिबूट करें + बूटलोडर में रिबूट करें + डाउनलोड में रिबूट करें + EDL मोड में रिबूट करें + + + अपडेट उपलब्ध है + स्थापित + स्थापित नहीं है + %1$s को अपडेट किया गया + छँटाई क्रम + नाम द्वारा छांटें + आखिरी अपडेट द्वारा छांटें + + + अभिलेख सेव करें + पुनः लोड करें + अभिलेख साफ़ करें + अभिलेख सफलतापूर्वक साफ़ हो गया. + + + परिवर्तन अभिलेख + + + Magisk की अपडेट + प्रगति सूचनाएँ + डाउनलोड सम्पन्न हुआ + फ़ाइल डाउनलोड करने में त्रुटि + मूल फ़ोल्डर में दिखाएँ + फाइल दिखाएँ + Magisk की अपडेट उपलब्ध है! + Magisk Manager की अपडेट उपलब्ध है! + + + डाउनलोड और स्थापित करने के लिए दबाएँ. + खाली Zip डाउनलोड करें + सीधा स्थापित करें (अनुशंसित) + निष्क्रिय स्लॉट में स्थापित करें (OTA के बाद) + आपके डिवाइस को रीबूट के बाद वर्तमान निष्क्रिय स्लॉट में बूट करने के लिए मजबूर किया जाएगा!\nOTA होने के बाद ही इस विकल्प का उपयोग करें.\nजारी रखें? + विधि का चयन करें + अतिरिक्त सेटअप + एक फ़ाइल का चयन और पैच करें + एक कच्ची इमेज चुनें (*.img) या एक ODIN tarfile (*.tar) + 5 सेकंड में रिबूट हो रहा है … + + + %1$s को स्थापित करें + क्या आप %1$s को स्थापित करना चाहते हैं ? + डाउनलोड + रिबूट + सेटिंग्स लागू करने के लिए रिबूट करें. + रिलीज नोट्स + Repo cache साफ़ हो गया + + DTBO पैच कर दिया गया! + Magisk Manager ने dtbo.img को पैच कर दिया. कृपया रिबूट करें. + फ़्लैश हो रहा है… + हो गया! + विफल हुआ + Magisk Manager छुप रहा है… + Magisk Manager छुपने में असफल रहा. + लिंक खोलने के लिए कोई एप्लिकेशन नहीं मिला. + चेतावनी + पूरी तरह से स्थापना रद्द करें + इमेजेज को पुनर्स्थापित करें + वापस लाया जा रहा… + वापस ले आया गया! + स्टॉक बैकअप मौजूद नहीं है! + मालिकाना कोड डाउनलोड करें + Magisk Manager FOSS है और उस्में Google का मालिकाना SafetyNet API कोड शामिल नहीं है.\n\nक्या आप Magisk Manager को SafetyNet चेक के लिए एक्सटेंशन (GoogleApiClient शामिल) डाउनलोड करने की अनुमति देंगे ? + सेटअप असफल हुआ. + अतिरिक्त सेटअप की आवश्यकता है + ठीक से काम करने के लिए आपके डिवाइस को Magisk के लिए अतिरिक्त सेटअप की आवश्यकता है. यह Magisk सेटअप zip डाउनलोड करेगा, क्या आप आगे बढ़ना चाहते हैं? + पर्यावरण सेटअप चल रहा है… + + + सामान्य + डार्क थीम + डार्क थीम सक्षम करें. + डाउनलोड करने की जगह + %1$s में फाइल्स रखी जाएँगी + Repo Cache साफ़ करें + ऑनलाइन Repo के लिए Cached जानकारी साफ़ करें. यह एप्लिकेशन को ऑनलाइन रिफ्रेश होने के लिए मजबूर करता है. + Magisk Manager को छुपाएं + Magisk Manager को क्रमरहित नाम से फिर से पैकेज करें. + Magisk Manager को पुनर्स्थापित करें + Magisk Manager को अपने मूल पैकेज नाम से पुनर्स्थापित करें + भाषा + (सिस्टम डिफ़ॉल्ट) + सेटिंग्स अपडेट करें + अपडेट के लिए जाँच करें + समय-समय पर बैकग्राउंड में अपडेट की जांच करते रहें. + अपडेट का चैनल + स्थिर + बीटा + कस्टम + एक कस्टम URL डालें + Magisk का केवल मूल मोड + केवल मुख्य विशेषताएं सक्षम करें. MagiskSU और MagiskHide अभी भी सक्षम रहेंगे, लेकिन कोई मॉड्यूल लोड नहीं किया जाएगा. + पता लगाने के विभिन्न रूपों से Magisk को छुपाएं. + सिस्टमलेस होस्ट्स + एडब्लॉक ऍप्लिकेशन्स के लिए सिस्टमलेस होस्ट्स का समर्थन + सिस्टमलेस होस्ट्स का मॉड्यूल जोड़ दिया गया + + ऍप्लिकेशन्स और ADB + केवल ऍप्लिकेशन्स + केवल ADB + निर्योग्य + 10 सेकंड्‌स + 15 सेकंड्‌स + 20 सेकंड्‌स + 30 सेकंड्‌स + 45 सेकंड्‌स + 60 सेकंड्‌स + उत्तम उपयोगकर्ता की पहुँच + स्वचालित प्रतिक्रिया + निवेदन का समय समाप्त + उत्तम उपयोगकर्ता सूचना + %1$d सेकंड्‌स + अपग्रेड के बाद फिर से प्रमाणित करें + एप्लीकेशन अपग्रेड होने के बाद उत्तम उपयोगकर्ता की अनुमतियों को फिर से प्रमाणित करें + फिंगरप्रिंट प्रमाणीकरण सक्षम करें + उत्तम उपयोगकर्ता के अनुरोधों की अनुमति के लिए फिंगरप्रिंट स्कैनर का उपयोग करें + फिंगरप्रिंट को प्रमाणित करें + + बहु उपयोगकर्ता मोड + केवल डिवाइस का मालिक + केवल डिवाइस के मालिक द्वौरा प्रभंदित + उपयोगकर्ता स्वतंत्र + केवल मालिक के पास ही रूट की पहुँच है. + केवल मालिक ही रूट की पहुँच का प्रबंधन कर सकता है और अनुरोध संकेत प्राप्त कर सकता है. + प्रत्येक उपयोगकर्ता का अपना अलग रूट नियम होता है. + + माउंट नेमस्पेस मोड + वैश्विक नेमस्पेस + नेमस्पेस को इन्हेरिट करें + संगरोध नेमस्पेस + सभी रूट सत्र वैश्विक माउंट नेमस्पेस का उपयोग करते हैं. + रूट सत्रों को उनके अनुरोधकर्ताओं के नेमस्पेस विरासत में मिलेंगे. + प्रत्येक रूट सत्र का अपना अलग नेमस्पेस होगा. + कोई फ़िंगरप्रिंट नहीं सेट किया गया या डिवाइस का समर्थन नहीं है. + फोल्डर बनाने में त्रुटि. यह स्टोरेज रूट डायरेक्टरी से एक्सेस होना चाहिए और फाइल नहीं होना चाहिए. + + + उत्तम उपयोगकर्ता का अनुरोध + इंकार करें + आदेश + अनुमति दें + यह आपके डिवाइस की पूरी पहुँच की अनुमति देगा. यदि आप सुनिश्चित नहीं हैं तो इंकार करें! + सदैव + एक बार + 10 मिनट + 20 मिनट + 30 मिनट + 60 मिनट + %1$s को उत्तम उपयोगकर्ता के अधिकार की अनुमति दी गई + %1$s को उत्तम उपयोगकर्ता के अधिकार से इनकार किया गया + कोई एप्लीकेशन नहीं मिला + %1$s को उत्तम उपयोगकर्ता के अधिकार की अनुमति है + %1$s को उत्तम उपयोगकर्ता के अधिकार की अनुमति नहीं है + %1$s की सूचनाएं सक्षम हैं + %1$s की सूचनाएं अक्षम हैं + %1$s के लिए अभिलेख सक्षम है + %1$s के लिए अभिलेख अक्षम है + वापस लें? + %1$s के अधिकारों को वापस लेने की पुष्टि करें? + पॉप-अप नोट + कोई नहीं + प्रमाणीकरण विफल हुआ + + + PID: %1$d + लक्ष्य UID: %1$d + Command: %1$s + + + सिस्टम ऍप्लिकेशन्स दिखाएं + + diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml index c3388c82c..964a0d98a 100644 --- a/app/src/main/res/values-hr/strings.xml +++ b/app/src/main/res/values-hr/strings.xml @@ -55,7 +55,6 @@ Popis izmjena aplikacije - Zatvori Instaliraj %1$s Da li želite instalirati %1$s sada? Preuzmi diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml index 41ed173d5..361e0e09f 100644 --- a/app/src/main/res/values-in/strings.xml +++ b/app/src/main/res/values-in/strings.xml @@ -86,7 +86,6 @@ Me-reboot dalam 5 detik… - Tutup Pasang %1$s Apakah Anda ingin memasang %1$s sekarang? Unduh @@ -181,7 +180,6 @@ Semua sesi root menggunakan mount ruang nama global. Sesi root akan mewarisi ruang nama peminta mereka. Setiap sesi root akan memiliki ruang nama tersendiri. - Tidak mendukung Android 8.0+. Tidak ada sidik jari diatur atau tidak ada dukungan perangkat. Kesalahan membuat folder. Folder harus dapat diakses dari direktori penyimpanan root dan bukan merupakan file. diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 2b1d5f4d3..7d20c8d46 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -1,223 +1,221 @@ - - Moduli - Download - Superuser - Registro eventi - Impostazioni - Installa - Versione di Magisk non supportata - Questa versione di Magisk Manager non supporta versioni di Magisk inferiori alla v18.0.\n\nPuoi aggiornare manualmente Magisk o tornare a una versione meno recente dell\'app. + + Moduli + Download + Superuser + Registro eventi + Impostazioni + Installa + Versione di Magisk non supportata + Questa versione di Magisk Manager non supporta versioni di Magisk inferiori alla v18.0.\n\nPuoi aggiornare manualmente Magisk o tornare a una versione meno recente dell\'app. - - Magisk non è installato. - Controllo aggiornamenti… - Canale di aggiornamento non valido - Tocca per controllare SafetyNet - Controllo stato SafetyNet… - Controllo SafetyNet Superato - Errore API SafetyNet - La risposta non è valida. - Magisk è aggiornato - Magisk Manager è aggiornato - Impostazioni avanzate - Mantieni crittografia forzata - Mantieni AVB 2.0/dm-verity - Installata: %1$s - Ultima: %1$s - Disinstalla - Disinstalla Magisk - Tutti i moduli verranno disabilitati/rimossi. Il root verrà rimosso e i tuoi dati potrebbero essere criptati, nel caso non lo siano già. - Aggiorna - (Modalità solo Core abilitata) + + Magisk non è installato. + Controllo aggiornamenti… + Canale di aggiornamento non valido + Tocca per controllare SafetyNet + Controllo stato SafetyNet… + Controllo SafetyNet Superato + Errore API SafetyNet + La risposta non è valida. + Magisk è aggiornato + Magisk Manager è aggiornato + Impostazioni avanzate + Mantieni crittografia forzata + Mantieni AVB 2.0/dm-verity + Installata: %1$s + Ultima: %1$s + Disinstalla + Disinstalla Magisk + Tutti i moduli verranno disabilitati/rimossi. Il root verrà rimosso e i tuoi dati potrebbero essere criptati, nel caso non lo siano già. + Aggiorna + (Modalità solo Core abilitata) - - (Nessuna informazione) - Nessun modulo trovato. - Il modulo sarà aggiornato al prossimo riavvio. - Il modulo sarà rimosso al prossimo riavvio. - Il modulo non sarà rimosso al prossimo riavvio. - Il modulo sarà disabilitato al prossimo riavvio. - Il modulo sarà abilitato al prossimo riavvio. - Creato da: %1$s - Riavvia in Recovery - Riavvia in Bootloader - Riavvia in Download Mode + + (Nessuna informazione) + Nessun modulo trovato. + Il modulo sarà aggiornato al prossimo riavvio. + Il modulo sarà rimosso al prossimo riavvio. + Il modulo non sarà rimosso al prossimo riavvio. + Il modulo sarà disabilitato al prossimo riavvio. + Il modulo sarà abilitato al prossimo riavvio. + Creato da: %1$s + Riavvia in Recovery + Riavvia in Bootloader + Riavvia in Download Mode - - Aggiornamento disponibile - Installato - Non installato - Aggiornato il: %1$s - Ordinamento - Ordina per nome - Ordina per ultimo aggiornamento + + Aggiornamento disponibile + Installato + Non installato + Aggiornato il: %1$s + Ordinamento + Ordina per nome + Ordina per ultimo aggiornamento - - Salva registro eventi - Ricarica - Svuota il registro eventi - Registro eventi svuotato correttamente. + + Salva registro eventi + Ricarica + Svuota il registro eventi + Registro eventi svuotato correttamente. - Novità + Novità - Aggiornamenti Magisk - Notifiche di avanzamento - Download completato + Aggiornamenti Magisk + Notifiche di avanzamento + Download completato Errore durante il download del file Mostra nella cartella padre - Mostra file - È disponibile un aggiornamento di Magisk! - È disponibile un aggiornamento di Magisk Manager! + Mostra file + È disponibile un aggiornamento di Magisk! + È disponibile un aggiornamento di Magisk Manager! - - Apri questa notifica per scaricare e installare. - Scarica solo il file zip - Installazione diretta (raccomandata) - Installa nello slot inattivo (dopo un OTA) - Questo dispositivo verrà FORZATO ad avviarsi usando lo slot inattivo!\nUsa questo metodo solo dopo che un OTA è stato installato.\nVuoi continuare? - Seleziona un metodo - Configurazione aggiuntiva - Seleziona e aggiorna un file - Seleziona un\'immagine in formato .img o un file ODIN .tar - Riavvio fra 5 secondi… - - - Chiudi - Installazione di %1$s - Vuoi installare %1$s? - Download - Riavvia - Riavvia per applicare i cambiamenti. - Note di rilascio - La cache delle repository è stata svuotata + + Apri questa notifica per scaricare e installare. + Scarica solo il file zip + Installazione diretta (raccomandata) + Installa nello slot inattivo (dopo un OTA) + Questo dispositivo verrà FORZATO ad avviarsi usando lo slot inattivo!\nUsa questo metodo solo dopo che un OTA è stato installato.\nVuoi continuare? + Seleziona un metodo + Configurazione aggiuntiva + Seleziona e aggiorna un file + Seleziona un\'immagine in formato .img o un file ODIN .tar + Riavvio fra 5 secondi… + + + Installazione di %1$s + Vuoi installare %1$s? + Download + Riavvia + Riavvia per applicare i cambiamenti. + Note di rilascio + La cache delle repository è stata svuotata DTBO è stato aggiornato! - Magisk Manager ha aggiornato dtbo.img. Riavvia per completare. - Flash in corso… - Completato! - Fallito - Nascondendo Magisk Manager… - Non è stato possibile nascondere Magisk Manager. - Nessuna app disponibile per aprire il link. - Attenzione - Disinstallazione completa - Ripristina Immagini - Ripristino… - Ripristino completato! - Non esiste un\'immagine originale di boot! - Scarica codice proprietario - Magisk Manager è FOSS e non contiene codice proprietario delle API Google SafetyNet.\n\nVuoi scaricare un\'estensione (contenente GoogleApiClient) per controllare lo stato di SafetyNet? - Configurazione fallita. - Configurazione aggiuntiva richiesta - Il tuo dispositivo necessita di una configurazione aggiuntiva per far funzionare Magisk correttamente. Verrà scaricato il file zip di Magisk, vuoi procedere ora? - Configurazione dell\'ambiente in corso… + Magisk Manager ha aggiornato dtbo.img. Riavvia per completare. + Flash in corso… + Completato! + Fallito + Nascondendo Magisk Manager… + Non è stato possibile nascondere Magisk Manager. + Nessuna app disponibile per aprire il link. + Attenzione + Disinstallazione completa + Ripristina Immagini + Ripristino… + Ripristino completato! + Non esiste un\'immagine originale di boot! + Scarica codice proprietario + Magisk Manager è FOSS e non contiene codice proprietario delle API Google SafetyNet.\n\nVuoi scaricare un\'estensione (contenente GoogleApiClient) per controllare lo stato di SafetyNet? + Configurazione fallita. + Configurazione aggiuntiva richiesta + Il tuo dispositivo necessita di una configurazione aggiuntiva per far funzionare Magisk correttamente. Verrà scaricato il file zip di Magisk, vuoi procedere ora? + Configurazione dell\'ambiente in corso… Generale - Tema scuro - Abilita il tema scuro. + Tema scuro + Abilita il tema scuro. Percorso di download I file verranno salvati in %1$s - Svuota cache repository - Svuota la cache delle repository. Questa opzione forza l\'aggiornamento online dell\'app. - Nascondi Magisk Manager - Reinstalla Magisk Manager con un nome pacchetto casuale. - Ripristina Magisk Manager - Ripristina Magisk Manager con il nome pacchetto originale - Lingua - (Sistema) - Impostazioni aggiornamento - Controlla aggiornamenti - Controlla automaticamente gli aggiornamenti in background. - Canale di aggiornamento - Stabile - Beta - Personalizzato - Inserisci un URL personalizzato - Modalità Magisk Core - Abilita solo le funzioni principali. Nessun modulo verrà caricato. MagiskSU e MagiskHide rimarranno abilitati - Nasconde Magisk da numerose rilevazioni. - Host systemless - Supporto a host systemless per le app che bloccano le pubblicità. - Aggiunto modulo per host systemless + Svuota cache repository + Svuota la cache delle repository. Questa opzione forza l\'aggiornamento online dell\'app. + Nascondi Magisk Manager + Reinstalla Magisk Manager con un nome pacchetto casuale. + Ripristina Magisk Manager + Ripristina Magisk Manager con il nome pacchetto originale + Lingua + (Sistema) + Impostazioni aggiornamento + Controlla aggiornamenti + Controlla automaticamente gli aggiornamenti in background. + Canale di aggiornamento + Stabile + Beta + Personalizzato + Inserisci un URL personalizzato + Modalità Magisk Core + Abilita solo le funzioni principali. Nessun modulo verrà caricato. MagiskSU e MagiskHide rimarranno abilitati + Nasconde Magisk da numerose rilevazioni. + Host systemless + Supporto a host systemless per le app che bloccano le pubblicità. + Aggiunto modulo per host systemless - App e ADB - Solo app - Solo ADB - Disabilitato - 10 secondi - 15 secondi - 20 secondi - 30 secondi - 45 secondi - 60 secondi - Accesso Superuser - Accesso predefinito - Timeout richiesta - Notifica Superuser - %1$d secondi - Riautentica dopo aggiornamento - Riautentica i permessi Superuser dopo un aggiornamento dell\'app - Abilita autenticazione impronta - Utilizza il sensore di impronte per accettare le richieste Superuser - Conferma impronta + App e ADB + Solo app + Solo ADB + Disabilitato + 10 secondi + 15 secondi + 20 secondi + 30 secondi + 45 secondi + 60 secondi + Accesso Superuser + Accesso predefinito + Timeout richiesta + Notifica Superuser + %1$d secondi + Riautentica dopo aggiornamento + Riautentica i permessi Superuser dopo un aggiornamento dell\'app + Abilita autenticazione impronta + Utilizza il sensore di impronte per accettare le richieste Superuser + Conferma impronta - Modalità multiutente - Solo proprietario del dispositivo - Gestito dal proprietario utente - Idipendente dall\'utente - Solo il proprietario ha i permessi di root. - Solo il proprietario può gestire accesso root e ricevere richieste. - Ogni utente ha le sue regole di root indpendenti. + Modalità multiutente + Solo proprietario del dispositivo + Gestito dal proprietario utente + Idipendente dall\'utente + Solo il proprietario ha i permessi di root. + Solo il proprietario può gestire accesso root e ricevere richieste. + Ogni utente ha le sue regole di root indpendenti. - Modalità mount namespace - Namespace globale - Namespace ereditato - Namespace isolato - Tutte le sessioni di root erediteranno il namespace globale. - Le sessioni di root erediteranno il namespace del loro richiedente. - Ogni sessione di root avrà il suo namespace isolato. - Non è supportato da Android 8.0+. - Non è presente alcuna impronta o il dispositivo non è supportato. - Errore durante la creazione della cartella. Deve essere accessibile dalla radice della memoria di archiviazione e non essere un file. + Modalità mount namespace + Namespace globale + Namespace ereditato + Namespace isolato + Tutte le sessioni di root erediteranno il namespace globale. + Le sessioni di root erediteranno il namespace del loro richiedente. + Ogni sessione di root avrà il suo namespace isolato. + Non è presente alcuna impronta o il dispositivo non è supportato. + Errore durante la creazione della cartella. Deve essere accessibile dalla radice della memoria di archiviazione e non essere un file. - - Richiesta Superuser + + Richiesta Superuser Nega - Chiedi - Concedi - Concede il pieno accesso al dispositivo.\nNega se non sei sicuro! - Sempre - Una volta - 10 minuti - 20 minuti - 30 minuti - 60 minuti - %1$s ha ottenuto i permessi Superuser - %1$s non ha ottenuto i permessi Superuser - Nessuna app trovata - %1$s ha ottenuto i permessi Superuser - %1$s non ha ottenuto i permessi Superuser - Notifiche per %1$s abilitate - Notifiche per %1$s disabilitate - Registro eventi abilitato per %1$s - Registro eventi non abilitato per %1$s + Chiedi + Concedi + Concede il pieno accesso al dispositivo.\nNega se non sei sicuro! + Sempre + Una volta + 10 minuti + 20 minuti + 30 minuti + 60 minuti + %1$s ha ottenuto i permessi Superuser + %1$s non ha ottenuto i permessi Superuser + Nessuna app trovata + %1$s ha ottenuto i permessi Superuser + %1$s non ha ottenuto i permessi Superuser + Notifiche per %1$s abilitate + Notifiche per %1$s disabilitate + Registro eventi abilitato per %1$s + Registro eventi non abilitato per %1$s Vuoi revocare? - Confermi la revoca dei diritti di %1$s? - Toast - Nessuno - Autenticatione fallita + Confermi la revoca dei diritti di %1$s? + Toast + Nessuno + Autenticatione fallita - - PID: %1$d - UID destinazione: %1$d - Comando: %1$s + + PID: %1$d + UID destinazione: %1$d + Comando: %1$s - - Mostra app di sistema + + Mostra app di sistema diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 1bdfab663..61faf6ea1 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -72,7 +72,6 @@ Magisk Managerの更新があります! - 閉じる %1$s をインストール %1$s をインストールしますか? ダウンロード @@ -169,7 +168,6 @@ すべてのrootセッションがグローバル名前空間を使用します rootセッションはリクエスト者の名前空間を継承します rootセッション毎に分離された名前空間を使用します - Android 8.0以降では対応していません 指紋が登録されていないか、お使いの端末でサポートされていません。 diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index 36f165686..395760601 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -83,7 +83,6 @@ 원시 이미지 (*.img) 또는 오딘 tar 파일 (*.tar) 선택 - 닫기 %1$s 설치 정말 %1$s을(를) 설치하시겠습니까? 다운로드 @@ -176,7 +175,6 @@ 모든 루트 세션이 전역 마운트 이름공간을 사용합니다. 루트 세션은 요청자의 이름공간을 상속합니다. 각각의 루트 세션은 자신만의 독립된 이름공간을 사용합니다. - 안드로이드 8.0 이상 버전에서 사용할 수 없습니다. 지문이 등록되지 않았거나 기기가 지문 인식을 지원하지 않습니다. diff --git a/app/src/main/res/values-lt/strings.xml b/app/src/main/res/values-lt/strings.xml index 8b83cbbbd..4484d78a6 100644 --- a/app/src/main/res/values-lt/strings.xml +++ b/app/src/main/res/values-lt/strings.xml @@ -61,7 +61,6 @@ Pakeitimų sąrašas - Uždaryti Instaliuoti %1$s Ar jūs norite instaliuoti %1$s? Atsisiųsti @@ -156,7 +155,6 @@ Visos root sesijos naudoja globalią vardų sritį Root sesijos paveldi jos išprašytojo/s vardų sritį Kiekviena root sesija turi savo izoliuotą vardų sritį - Įrenginiai su Android 8.0+ nepalaiko šio nustatymo Jūsų įrenginyje nebuvo surasta pirštų antspaudų arba jūsų įrenginys neturi pirštų antspaudų skaitytuvo diff --git a/app/src/main/res/values-mk/strings.xml b/app/src/main/res/values-mk/strings.xml index 721057a09..c891083a7 100644 --- a/app/src/main/res/values-mk/strings.xml +++ b/app/src/main/res/values-mk/strings.xml @@ -84,7 +84,6 @@ Рестартирање за 5 секунди… - Затвори Инсталирај %1$s Дали сакате да го инсталирате %1$s сега? Преземи @@ -175,7 +174,6 @@ Сите рут сесии го користат глобалниот именски простор. Рут сесиите ќе го наследат именскиот простор на нивниот барател. Секоја рут сесија ќе има свој изолиран именски простор. - Не е поддржано на Android 8.0+. Нема регистрирано отпечатоци од прсти или уредот не ја поддржува оваа функција. diff --git a/app/src/main/res/values-nb/strings.xml b/app/src/main/res/values-nb/strings.xml index 7f32d75b2..c8d884ccf 100644 --- a/app/src/main/res/values-nb/strings.xml +++ b/app/src/main/res/values-nb/strings.xml @@ -69,7 +69,6 @@ En Magisk Manager-oppdatering er tilgjengelig! - Lukk Installer %1$s Vil du installere %1$s nå? Last ned @@ -166,7 +165,6 @@ Alle root-økter benytter det altdekkende monteringsnavnefeltet. Root-økter vil arve forespørrerens navnefelt. Hver root-økt vil ha sitt eget isolerte navnefelt. - Støtter ikke Android ≥8.0. Ingen fingeravtrykk ble gitt, eller så støttes det ikke av enheten. diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 15d29519b..2d00fb929 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -62,7 +62,6 @@ App\'s changelog - Sluiten %1$s installeren Zeker weten %1$s installeren? Downloaden @@ -151,7 +150,6 @@ Alle rootsessies gebruiken de globale naamruimte Rootsessies verkrijgen de verzoeker\'s naamruimte Iedere rootsessie heeft een eigen geïsoleerde naamruimte - Ondersteunt geen Android 8.0+ Geen vingerafdrukken ingesteld, of geen apparaatondersteuning diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 2d65d2b6d..a96a201e1 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -9,12 +9,12 @@ Instaluj Nieobsługiwana Wersja Magisk Ta wersja Magisk Managera nie obsługuje wersji Magisk niższej niż v18.0.\n\nMożesz albo ręcznie zaktualizować Magisk lub obniżyć w aplikacji do starszej wersji. - - + + Magisk nie jest zainstalowany. Sprawdzanie aktualizacji… Nieprawidłowy Kanał Aktualizacji - Dotknij aby sprawdzić SafetyNet + Dotknij aby sprawdzić SafetyNet Sprawdzanie statusu SafetyNet… SafetyNet Poprawny SafetyNet API Błędny @@ -28,11 +28,11 @@ Ostatnia: %1$s Odinstaluj Odinstaluj Magisk - Wszystkie moduły będą wyłączone/usunięte. Root zostanie usunięty i przywrócone szyfrowanie danych, jeśli nie są te dane obecnie szyfrowane + Wszystkie moduły będą wyłączone/usunięte. Root zostanie usunięty i przywrócone szyfrowanie danych, jeśli nie są te dane obecnie szyfrowane Pobierz (Włączony tylko tryb jądra) - - + + (Nie umieszczono informacji) Nie znaleziono modułów. Moduł zostanie zaktualizowany przy następnym restarcie. @@ -45,12 +45,12 @@ Restart do Bootloadera Restart do Download Restart do EDL - - + + Aktualizacja dostępna Zainstalowany Nie zainstalowany - Zaktualizowano: %1$s + Zaktualizowano: %1$s Kolejność Sortowania Sortuj po nazwie Sortuj po ostatniej aktualizacji @@ -71,23 +71,22 @@ Błąd pobierania pliku Pokaż w folderze nadrzędnym Pokaż pliki - Nowa Wersja Magisk Dostępna! + Nowa Wersja Magisk Dostępna! Nowa Wersja Magisk Manager Dostępna! - - Naciśnij aby pobrać i zainstalować - Pobierz Tylko Zip + + Naciśnij aby pobrać i zainstalować + Pobierz Tylko Zip Bezpośrednia instalacja (Zalecane) - Zainstaluj do Nieaktywnego Slotu (po OTA) + Zainstaluj do Nieaktywnego Slotu (po OTA) Urządzenie będzie MUSIAŁO uruchomić się z bieżącego nieaktywnego slotu po restarcie! /\nUżyj tylko tej opcji po zakończeniu OTA.\nKontynuować? - Wybierz Metodę - Dodatkowa konfiguracja + Wybierz Metodę + Dodatkowa konfiguracja Wybierz i Wgraj Plik Wybierz obraz raw (*.img) lub plik ODIN tar (*.tar) Ponowne uruchomienie za 5 sekund… - - - Zamknij + + Instaluj %1$s Czy chcesz zainstalować %1$s ? Pobierz @@ -100,14 +99,14 @@ Magisk Manager wgrał dtbo.img. Uruchom ponownie Flashowanie Gotowe! - Błąd - Ukryj Magisk Manager… + Błąd + Ukryj Magisk Manager… Błąd Ukrycia Magisk Managera. - Nie znaleziono aplikacji pod linkiem. + Nie znaleziono aplikacji pod linkiem. Uwaga Odinstalowywanie Zakończone Przywróć Obraz - Przywracanie… + Przywracanie… Przywracanie zakończone! Stock backup nie istnieje! Pobierz Kod @@ -123,7 +122,7 @@ Włącz ciemny motyw Pobierz dodatek Pliki zostaną zapisane w %1$s - Wyczyść Pamięć Repozytorium + Wyczyść Pamięć Repozytorium Wymusza na aplikacji odświeżenie repozytorium online. Ukryj Magisk Manager Przepakowanie Magisk Manager z losową nazwą pakietu @@ -145,7 +144,7 @@ Włącz systemless hosts Wsparcie systemless dla aplikacji Adblock Dodano moduł systemless hosts - + Aplikacje i ADB Tylko Aplikacje Tylko ADB @@ -166,8 +165,8 @@ Włącz Uwierzytelnienie Odciskiem Palca Użyj skanera linii papilarnych, aby zezwolić na żądania supersu Uwierzytelnianie Odciskiem Palca - - Tryb Multiusera + + Tryb Multiusera Tylko Właściciel Urządzenia Zarządzanie Właścicielami Urządzenia Niezależny Użytkownik @@ -182,11 +181,10 @@ Wszystkie sesje root za pomocą globalnej przestrzeni montowań nazw Sesje Root będzie dziedziczyć prośby i nazwy W każdej sesji root będzie miał własną odosobnioną nazwę - Brak wsparcia dla Androida 8.0+ Nie ustawiono żadnych odcisków palców lub brak obsługi urządzenia Błąd podczas tworzenia folderu. Musi być dostępny z głównego katalogu pamięci i nie może być plikiem. - - + + Prośba o dostęp Superusera Odmów Zapytaj @@ -220,5 +218,5 @@ Pokaż aplikacje systemowe - + diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index d8abf1566..b69f2f4fe 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -63,7 +63,6 @@ Registro de mudanças - Fechar Instalar %1$s Instalar %1$s agora? Baixar @@ -153,7 +152,6 @@ Todas as sessões root usam montagem de espaço de nome global As sessões root herdarão espaço de nome de seu solicitante Cada sessão root terá seu próprio espaço de nome isolado - Não suporta Android 8.0+ Nenhuma impressão digital foi definida ou o dispostivo não tem suporte diff --git a/app/src/main/res/values-pt-rPT/strings.xml b/app/src/main/res/values-pt-rPT/strings.xml index a0e0dd852..0f3a0da64 100644 --- a/app/src/main/res/values-pt-rPT/strings.xml +++ b/app/src/main/res/values-pt-rPT/strings.xml @@ -53,7 +53,6 @@ Lista de alterações da aplicação - Fechar Instalar %1$s Deseja instalar%1$s agora? Transferir diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml index a34362b06..6e6a8e423 100644 --- a/app/src/main/res/values-ro/strings.xml +++ b/app/src/main/res/values-ro/strings.xml @@ -87,7 +87,6 @@ Repornire în 5 secunde… - Închide Instalează %1$s Vrei să instalezi acum %1$s? Descarcă @@ -182,7 +181,6 @@ Toate sesiunile de root folosesc spațiul de nume global. Sesiunile de root vor moșteni spațiul de nume al solicitantului. Fiecare sesiune de root va avea propriul spațiu de nume izolat. - Nu suportă Android 8.0+. Nu au fost setate amprente sau scannerul de amprentă lipsește. Eroare la crearea dosarului. Acesta trebuie să fie accesibil din directorul rădăcină al stocării și nu trebuie să fie un fișier. diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 06076f1d0..be293523f 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -18,18 +18,18 @@ Проверка статуса SafetyNet… Результат проверки SafetyNet Ошибка SafetyNet API - Некорректный ответ. + Некорректный ответ Magisk актуален Magisk Manager актуален Расширенные опции - Сохранить принудительное шифрование - Сохранить AVB 2.0/dm-verity - Режим Recovery - Установлена: %1$s - Последняя: %1$s + Не отключать шифрование /data + Не отключать AVB 2.0/dm-verity + Режим установки в recovery + Установлен: %1$s + Последний: %1$s Удаление Удаление Magisk - Все модули будут отключены/удалены. Root-права будут удалены. Шифрование будет активировано. + Все модули будут отключены/удалены!\nRoot-права будут удалены!\nШифрование будет активировано! Обновить (Активирован режим Magisk Core) @@ -76,24 +76,23 @@ Доступно обновление Magisk Manager! - Нажмите, чтобы загрузить и установить. - Загрузка установочного ZIP + Нажмите, чтобы загрузить и установить + Только загрузка ZIP Прямая установка (Рекомендуется) - Установка в неактивный слот (После OTA) - Ваше устройство будет принудительно перезагружено в неактивный слот!\nИспользуйте эту опцию только при установке OTA.\nПродолжить? + Установка во второй слот (OTA) + Ваше устройство будет принудительно перезагружено в неактивный (противоположный) слот!\nИспользуйте эту опцию только при интеграции после OTA.\nПродолжить? Выбор способа Дополнительная установка - Выбрать и пропатчить файл + Вручную пропатчить образ Выберите файл образа (*.img) или архив ODIN (*.tar) Перезагрузка через 5 секунд… - Закрыть Установка %1$s Установить %1$s ? Скачать Перезагрузка - Для применения настроек перезагрузите устройство. + Для применения настроек перезагрузите устройство О версии Кэш репозитория очищен @@ -102,9 +101,9 @@ Прошивка… Завершено! Ошибка - Маскировка Magisk Manager… + Скрытие Magisk Manager… Не удалось пересобрать Magisk Manager - Не найдено приложений для открытия ссылки. + Не найдено приложений для открытия ссылки Предупреждение Полное удаление Восстановить разделы @@ -112,8 +111,8 @@ Восстановление завершено! Резервная копия отсутствует! Загрузка SafetyNet - Magisk Manager — свободно распространяемый продукт, он не содержит собственный код SafetyNet API от Google.\n\nРазрешить Magisk Manager загрузить расширение для проверки SafetyNet? (содержит GoogleApiClient) - Ошибка установки. + Magisk Manager — проект с открытым исходным кодом и не содержит проприетарный код SafetyNet API от Google.\n\nРазрешить Magisk Manager загрузить расширение для проверки SafetyNet? (содержит GoogleApiClient) + Ошибка установки Требуется дополнительная установка Вашему устройству требуется дополнительная установка Magisk для корректной работы. Будет загружен установочный ZIP Magisk, продолжить? Настройка рабочей среды… @@ -121,32 +120,36 @@ Основные Тёмная тема - Включить тёмное оформление. + Включить тёмное оформление Папка загрузки Файлы будут загружаться в %1$s Очистка кэша репозитория - Очистить кэш репозитория. Будет загружен заново. - Маскировка Magisk Manager - Пересобрать Magisk Manager со случайным именем пакета. + Очистить кэш репозитория. Будет загружен заново + Скрытие Magisk Manager + Пересобрать Magisk Manager со случайным названием и именем пакета Восстановление Magisk Manager - Восстановить Magisk Manager с исходным именем пакета. + Восстановить Magisk Manager с исходным названием и именем пакета Язык По умолчанию (Системный) Настройки обновлений Проверка обновлений - Периодически проверять наличие обновлений в фоновом режиме. + Периодически проверять наличие обновлений в фоновом режиме Источник обновлений Стабильный канал Beta канал Сторонний канал Укажите ссылку Magisk Core - Активировать только основные возможности. Модули не будут загружены. MagiskSU и Magisk Hide останутся активными. - Скрыть Magisk от различных обнаружений. + Активировать только основные возможности. Модули не будут загружены. MagiskSU и Magisk Hide останутся активными + Скрывать Magisk от различных обнаружений Внесистемные хосты - Поддержка внесистемных хостов для приложений, блокирующих рекламу. + Поддержка внесистемных хостов для приложений, блокирующих рекламу Добавлен модуль внесистемных хостов + Укажите имя приложения + Новое имя + Приложение будет пересобрано с этим именем + Некорректный формат Приложения и ADB Только приложения Только ADB @@ -163,18 +166,18 @@ Уведомления суперпользователя %1$d секунд Повторная аутентификация - Повторный запрос прав суперпользователя после обновления приложений. + Повторный запрос прав суперпользователя после обновления приложений Биометрическая аутентификация - Использовать сканер отпечатков пальцев для запросов прав суперпользователя. - Аутентифицировать отпечаток пальца + Использовать сканер отпечатков пальцев для запросов прав суперпользователя + Подтвердите отпечаток пальца Многопользовательский режим Только владелец Регулировка владельцем Правила пользователей - Только владелец имеет Root-доступ. - Только владелец управляет Root-доступом и обрабатывает запросы. - Каждый пользователь имеет свои собственные правила Root-доступа. + Только владелец имеет Root-доступ + Только владелец управляет Root-доступом и обрабатывает запросы + Каждый пользователь имеет свои собственные правила Root-доступа Настройка пространств имён Общее пространство имён @@ -183,18 +186,17 @@ Сессии суперпользователя используют общее пространство имён Сессии суперпользователя наследуют пространство имён запрашивающего Сессии суперпользователя используют изолированные пространства имён - Не поддерживается в Android 8.0+ Не поддерживается устройством или не заданы отпечатки Ошибка создания папки. Она должна быть доступна из корневой директории хранилища и не должна быть файлом. Запрос прав суперпользователя - Отказать + Запретить Запрос - Предоставить - Предоставить полный доступ к устройству.\nЕсли не уверены - отклоните данное действие! + Разрешить + Разрешить полный доступ к устройству?\nЕсли не уверены - отклоните данное действие! Навсегда - Сейчас + Единожды 10 мин. 20 мин. 30 мин. @@ -220,6 +222,6 @@ Команда: %1$s - Показать системные приложения + Системные приложения diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index 3af5729c2..9d62740bf 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -72,7 +72,6 @@ Je dostupná aktualizácia Magisk Manager! - Zavrieť Nainštalovať %1$s Chcete teraz nainštalovať %1$s? Stiahnuť @@ -167,7 +166,6 @@ Všetky relácie root použijú globálne mount namespace Relácie root zdedia namespace od požadovateľa Každá relácia root bude mať vlastný izolovaný namespace - Nepodporuje Android 8.0+ Neboli odoslané žiadne odtlačky prsta alebo ich zariadenie nepodporuje diff --git a/app/src/main/res/values-sr/strings.xml b/app/src/main/res/values-sr/strings.xml index f12be9089..337589fba 100644 --- a/app/src/main/res/values-sr/strings.xml +++ b/app/src/main/res/values-sr/strings.xml @@ -55,7 +55,6 @@ Дневник промена апликације - Затвори Инсталирај %1$s Да ли желите да инсталирате %1$s? Преузми diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index b180ec304..2ce35d2a9 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -53,7 +53,6 @@ Ändringslogg - Stäng Installera %1$s Vill du installera %1$s ? Ladda ner @@ -62,7 +61,7 @@ Starta om för att tillämpa inställningar Release notes Repo-cache rensad - En uppdatering av Magisk maneger finns tillgänglig! + En uppdatering av Magisk Manager finns tillgänglig! Tryck för att ladda ner och installera Magiska uppdateringar Fel vid nerladdning av fil diff --git a/app/src/main/res/values-th/strings.xml b/app/src/main/res/values-th/strings.xml index 64477ff84..066df9c69 100644 --- a/app/src/main/res/values-th/strings.xml +++ b/app/src/main/res/values-th/strings.xml @@ -69,7 +69,6 @@ มีการอัพเดต Magisk Manager! - ปิด ติดตั้ง %1$s ต้องการติดตั้ง %1$s ตอนนี้หรือไม่? ดาวน์โหลด @@ -166,7 +165,6 @@ All root sessions use the global mount namespace. Root sessions will inherit their requester\'s namespace. Each root session will have its own isolated namespace. - ไม่รองรับ Android 8.0 ขึ้นไป ไม่มีลายนิ้วมือหรืออุปกรณ์แสกน diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 701a610a0..bc574a0b8 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -88,7 +88,6 @@ 5 saniye içinde yeniden başlatılacak... - Kapat %1$s yükle %1$s yüklensin mi? İndir @@ -147,6 +146,10 @@ Reklam engelleme uygulamaları için sistemsiz host desteği Sistemsiz host modülü eklendi + İstediğiniz uygulama adını yazın + Yeni ad + Uygulama bu ad ile yeniden paketlenecek + Geçersiz format Uygulamalar ve ADB Sadece uygulamalar Sadece ADB @@ -183,9 +186,8 @@ Tüm kök oturumları genel bağlama ad alanını kullanır Kök oturumları, istekte bulunanın ad alanını devralır Her bir kök oturumunun kendi izole ad alanı olacaktır - Android 8.0 ve üzerinde desteklenmiyor Parmak izi ayarlanmadı veya cihaz desteği yok - Klasör oluşturma hatası. Depolama kök dizininden erişilebilir olmalı ve bir dosya olmamalıdır. + Klasör oluşturma hatası. Depolama kök dizininden erişilebilir olmalı ve bir dosya olmamalıdır. Yetkili Kullanıcı İsteği diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 7a8d1a43c..4a3f86992 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -88,7 +88,6 @@ Перезавантаження через 5 секунд… - Закрити Встановити %1$s Бажаєте встановити %1$s ? Завантажити @@ -106,8 +105,8 @@ Не вдалося приховати Magisk Manager. Не знайдено програм для відкриття посилання. Попередження - Видалення виконано - Відновити образи + Повне видалення + Відновити розділи Відновлення… Відновлення завершено! Немає резервної копії оригінального boot образу @@ -140,13 +139,17 @@ Бета реліз Власний Вставте власний URL - Режим ядра Magisk + Режим Magisk Core Увімкнути тільки можливості ядра. MagiskSU i Magisk Hide залишуться увімкненими, проте ніякі модулі не будуть завантажені. Приховати Magisk від різних форм виявлень. Позасистемні хости Підтримка позасистемних хостів для програм блокування реклами. Додано модуль позасистемних хостів + Введіть бажане ім\'я застосунку + Нове ім\'я + Застосунок буде перезібрано з цим ім\'ям + Неправильний формат Програми і ADB Програми ADB @@ -183,7 +186,6 @@ Всі сеанси Суперкористувача використовують глобальний простір імен. Сеанси Суперкористувача наслідують простір імен запитувача. Кожнен сеанс Суперкористувача має власний ізольований простір імен. - Не працює на Android 8.0+. Немає відбитків пальця або пристрій не підтримується. Помилка створення папки. Вона повинна бути доступна з кореневої директорії сховища і не повинна бути файлом. diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index 5b797b94f..1b9cef3be 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -61,7 +61,6 @@ Nhật ký thay đổi của ứng dụng - Đóng Cài đặt %1$s Bạn muốn cài đặt %1$s ? Tải xuống @@ -159,7 +158,6 @@ Tất cả các phiên root sử dụng không gian tên gắn kết chung. Các phiên root sẽ kế thừa không gian tên của người yêu cầu. Mỗi phiên root sẽ có không gian tên riêng biệt. - Không hỗ trợ Android 8.0+. Không có dấu vân tay nào được thiết lập hoặc thiết bị không hỗ trợ. diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index a36ef1a10..e52a41ad6 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -88,12 +88,11 @@ 5 秒后重启 - 关闭 安装 %1$s 是否安装 %1$s ? 下载 重启 - 重启以应用设置 + 重启设备以应用设置 发布说明 仓库缓存已清除 @@ -147,6 +146,10 @@ 为广告屏蔽应用提供 Systemless hosts 支持 已添加 systemless hosts 模块 + 输入想要的应用名称 + 新名称 + 新应用将使用此名称 + 输入无效 应用和 ADB 仅应用 仅 ADB @@ -183,7 +186,6 @@ 所有的 ROOT 会话使用全局挂载命名空间 ROOT 会话继承原程序的命名空间 每一个 ROOT 会话使用自己独立的命名空间 - 不支持 Android 8.0+ 没有设置指纹或设备不支持 创建文件夹出错。路径需要位于内部存储空间,并且不能有同名文件。 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 9842a9715..7e092939d 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -84,7 +84,6 @@ 將在 5 秒後重新啟動… - 關閉 安裝 %1$s 即將安裝 %1$s 下載 @@ -177,7 +176,6 @@ 所有 Root 工作階段皆使用全域 Namespace 所有 Root 工作階段皆繼承原程式 Namespace 所有 Root 工作階段個別擁有獨立 Namespace - 不支援 Android 8.0 及更新版本 未設定指紋或無指紋辨識器 diff --git a/app/src/main/res/values/resources.xml b/app/src/main/res/values/resources.xml new file mode 100644 index 000000000..9c785befd --- /dev/null +++ b/app/src/main/res/values/resources.xml @@ -0,0 +1,11 @@ + + + + + Magisk Manager + Manager + Magisk + Magisk Hide + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d258cacfb..ca53b8223 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -18,7 +18,7 @@ Checking SafetyNet status… SafetyNet Check Success SafetyNet API Error - The response is invalid. + The response is invalid Magisk is up to date Magisk Manager is up to date Advanced Settings @@ -29,7 +29,7 @@ Latest: %1$s Uninstall Uninstall Magisk - All modules will be disabled/removed. Root will be removed, and your data potentially encrypted if not already. + All modules will be disabled/removed!\nRoot will be removed!\nYour data potentially encrypted if not already! Update (Core only mode enabled) @@ -76,7 +76,7 @@ Magisk Manager Update Available! - Press to download and install. + Press to download and install Download Zip Only Direct Install (Recommended) Install to Inactive Slot (After OTA) @@ -88,12 +88,11 @@ Rebooting in 5 seconds… - Close Install %1$s Do you want to install %1$s now? Download Reboot - Reboot to apply settings. + Reboot to apply settings Release notes Repo cache cleared @@ -104,7 +103,7 @@ Failed Hiding Magisk Manager… Hide Magisk Manager failed. - No application found to open the link. + No application found to open the link Warning Complete Uninstall Restore Images @@ -113,7 +112,7 @@ Stock backup does not exist! Download Proprietary Code Magisk Manager is FOSS and doesn\'t contain Google\'s proprietary SafetyNet API code.\n\nWill you allow Magisk Manager to download an extension (contains GoogleApiClient) for SafetyNet checks? - Setup failed. + Setup failed Requires Additional Setup Your device needs additional setup for Magisk to work properly. It will download the Magisk setup zip, do you want to proceed now? Running environment setup… @@ -121,32 +120,36 @@ General Dark Theme - Enable dark theme. + Enable dark theme Download path Files will be saved to %1$s Clear Repo Cache - Clear the cached information for online repos. This forces the app to refresh online. + Clear the cached information for online repos. This forces the app to refresh online Hide Magisk Manager - Repackage Magisk Manager with random package name. + Repackage Magisk Manager with random package and app names Restore Magisk Manager - Restore Magisk Manager with original package + Restore Magisk Manager with original package and app names Language (System Default) Update Settings Check Updates - Periodically check for updates in the background. + Periodically check for updates in the background Update Channel Stable Beta Custom Insert a custom URL Magisk Core Only Mode - Enable only core features. MagiskSU and MagiskHide will still be enabled, but no modules will be loaded. - Hide Magisk from various forms of detection. + Enable only core features. MagiskSU and MagiskHide will still be enabled, but no modules will be loaded + Hide Magisk from various forms of detection Systemless hosts - Systemless hosts support for Adblock apps. + Systemless hosts support for Adblock apps Added systemless hosts module + Type desired app name + New name + App will be repackaged to this name + Invalid format Apps and ADB Apps only ADB only @@ -172,19 +175,18 @@ Device Owner Only Device Owner Managed User-Independent - Only owner has root access. - Only owner can manage root access and receive request prompts. - Each user has his/her own separate root rules. + Only owner has root access + Only owner can manage root access and receive request prompts + Each user has his/her own separate root rules Mount Namespace Mode Global Namespace Inherit Namespace Isolated Namespace - All root sessions use the global mount namespace. - Root sessions will inherit their requester\'s namespace. - Each root session will have its own isolated namespace. - Does not support Android 8.0+. - No fingerprints were set or no device support. + All root sessions use the global mount namespace + Root sessions will inherit their requester\'s namespace + Each root session will have its own isolated namespace + No fingerprints were set or no device support Error creating folder. It must be accessible from storage root directory and must not be a file. diff --git a/build.gradle b/build.gradle index 52c34561a..f0236679e 100644 --- a/build.gradle +++ b/build.gradle @@ -62,7 +62,7 @@ subprojects { android { signingConfigs { config { - storeFile rootProject.file('release-key.jks') + storeFile new File(props['keyStore']) storePassword props['keyStorePass'] keyAlias props['keyAlias'] keyPassword props['keyPass'] @@ -85,13 +85,12 @@ subprojects { } 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}" - } + // Handle resource IDs + File resId = project.file('res-ids.txt') + if (resId.exists()) + additionalParameters "--stable-ids", "${resId.absolutePath}" + else + additionalParameters "--emit-ids", "${resId.absolutePath}" } } } diff --git a/build.py b/build.py index 513fbe29b..595eb2e81 100755 --- a/build.py +++ b/build.py @@ -3,7 +3,8 @@ import sys import os import subprocess -if os.name == 'nt': +is_windows = os.name == 'nt' +if is_windows: import colorama colorama.init() @@ -44,6 +45,7 @@ import shutil import lzma import tempfile +# Constants if 'ANDROID_NDK_HOME' in os.environ: ndk_build = os.path.join(os.environ['ANDROID_NDK_HOME'], 'ndk-build') else: @@ -51,14 +53,16 @@ else: os.environ['ANDROID_HOME'], 'ndk-bundle', 'ndk-build') cpu_count = multiprocessing.cpu_count() -gradlew = os.path.join('.', 'gradlew' + ('.bat' if os.name == 'nt' else '')) +gradlew = os.path.join('.', 'gradlew' + ('.bat' if is_windows else '')) archs = ['armeabi-v7a', 'x86'] arch64 = ['arm64-v8a', 'x86_64'] -keystore = 'release-key.jks' -config = {} support_targets = ['magisk', 'magiskinit', 'magiskboot', 'magiskpolicy', 'busybox', 'test'] default_targets = ['magisk', 'magiskinit', 'magiskboot', 'busybox'] +build_tools = os.path.join(os.environ['ANDROID_HOME'], 'build-tools', '29.0.2') +# Global vars +config = {} +STDOUT = None def mv(source, target): try: @@ -96,18 +100,56 @@ def mkdir_p(path, mode=0o777): os.makedirs(path, mode, exist_ok=True) -def execv(cmd, redirect=None): - return subprocess.run(cmd, stdout=redirect if redirect != None else STDOUT) +def execv(cmd): + return subprocess.run(cmd, stdout=STDOUT) -def system(cmd, redirect=None): - return subprocess.run(cmd, shell=True, stdout=redirect if redirect != None else STDOUT) +def system(cmd): + return subprocess.run(cmd, shell=True, stdout=STDOUT) def xz(data): return lzma.compress(data, preset=9, check=lzma.CHECK_NONE) +def load_config(args): + # Some default values + config['outdir'] = 'out' + config['prettyName'] = 'false' + config['keyStore'] = 'release-key.jks' + + # Load prop file + if not os.path.exists(args.config): + error(f'Please make sure {args.config} existed') + + with open(args.config, 'r') as f: + for line in [l.strip(' \t\r\n') for l in f]: + if line.startswith('#') or len(line) == 0: + continue + prop = line.split('=') + if len(prop) != 2: + continue + config[prop[0].strip(' \t\r\n')] = prop[1].strip(' \t\r\n') + + config['prettyName'] = config['prettyName'].lower() == 'true' + + # Sanitize configs + if 'version' not in config or 'versionCode' not in config: + error('Config error: "version" and "versionCode" is required') + + try: + config['versionCode'] = int(config['versionCode']) + except ValueError: + error('Config error: "versionCode" is required to be an integer') + + if args.release and not os.path.exists(config['keyStore']): + error(f'Config error: assign "keyStore" to a java keystore') + + mkdir_p(config['outdir']) + global STDOUT + STDOUT = None if args.verbose else subprocess.DEVNULL + + def zip_with_msg(zip_file, source, target): if not os.path.exists(source): error(f'{source} does not exist! Try build \'binary\' and \'apk\' before zipping!') @@ -125,12 +167,13 @@ def collect_binary(): def clean_elf(): - if os.name == 'nt': + if is_windows: elf_cleaner = os.path.join('tools', 'elf-cleaner.exe') else: elf_cleaner = os.path.join('native', 'out', 'elf-cleaner') - if not os.path.exists(elf_cleaner): - execv(['g++', 'tools/termux-elf-cleaner/termux-elf-cleaner.cpp', '-o', elf_cleaner]) + if not os.path.exists(elf_cleaner): + execv(['g++', 'tools/termux-elf-cleaner/termux-elf-cleaner.cpp', + '-o', elf_cleaner]) args = [elf_cleaner] args.extend(os.path.join('native', 'out', arch, 'magisk') for arch in archs + arch64) execv(args) @@ -152,7 +195,7 @@ def sign_zip(unsigned, output, release): header('* Signing Zip') - proc = execv(['java', '-jar', zipsigner, keystore, config['keyStorePass'], + proc = execv(['java', '-jar', zipsigner, config['keyStore'], config['keyStorePass'], config['keyAlias'], config['keyPass'], unsigned, output]) if proc.returncode != 0: @@ -259,14 +302,47 @@ def build_apk(args, module): proc = execv([gradlew, f'{module}:assemble{build_type}', '-PconfigPath=' + os.path.abspath(args.config)]) if proc.returncode != 0: - error('Build Magisk Manager failed!') + error(f'Build {module} failed!') build_type = build_type.lower() apk = f'{module}-{build_type}.apk' source = os.path.join(module, 'build', 'outputs', 'apk', build_type, apk) target = os.path.join(config['outdir'], apk) - mv(source, target) + + if args.release: + zipalign = os.path.join(build_tools, 'zipalign' + ('.exe' if is_windows else '')) + aapt2 = os.path.join(build_tools, 'aapt2' + ('.exe' if is_windows else '')) + apksigner = os.path.join(build_tools, 'apksigner' + ('.bat' if is_windows else '')) + try: + with tempfile.NamedTemporaryFile(delete=False) as f: + tmp = f.name + + # AAPT2 optimization + execv([aapt2, 'optimize', '-o', tmp, '--enable-resource-obfuscation', + '--enable-resource-path-shortening', source]) + + # Recompress everything just to piss people off + with zipfile.ZipFile(source, 'w', compression=zipfile.ZIP_DEFLATED) as zout: + with zipfile.ZipFile(tmp) as zin: + for e in zin.namelist(): + zout.writestr(e, zin.read(e)) + + # Zipalign + execv([zipalign, '-fz', '4', source, target]) + + # Sign APK + execv([apksigner, 'sign', '--v1-signer-name', 'CERT', + '--ks', config['keyStore'], + '--ks-pass', f'pass:{config["keyStorePass"]}', + '--ks-key-alias', config['keyAlias'], + '--key-pass', f'pass:{config["keyPass"]}', target]) + finally: + rm(tmp) + rm(source) + else: + mv(source, target) + header('Output: ' + target) return target @@ -309,7 +385,8 @@ def build_snet(args): def zip_main(args): header('* Packing Flashable Zip') - unsigned = tempfile.mkstemp()[1] + with tempfile.NamedTemporaryFile(delete=False) as f: + unsigned = f.name with zipfile.ZipFile(unsigned, 'w', compression=zipfile.ZIP_DEFLATED, allowZip64=False) as zipf: # update-binary @@ -367,13 +444,15 @@ def zip_main(args): output = os.path.join(config['outdir'], f'Magisk-v{config["version"]}.zip' if config['prettyName'] else 'magisk-release.zip' if args.release else 'magisk-debug.zip') sign_zip(unsigned, output, args.release) + rm(unsigned) header('Output: ' + output) def zip_uninstaller(args): header('* Packing Uninstaller Zip') - unsigned = tempfile.mkstemp()[1] + with tempfile.NamedTemporaryFile(delete=False) as f: + unsigned = f.name with zipfile.ZipFile(unsigned, 'w', compression=zipfile.ZIP_DEFLATED, allowZip64=False) as zipf: # update-binary @@ -412,6 +491,7 @@ def zip_uninstaller(args): output = os.path.join(config['outdir'], f'Magisk-uninstaller-{datestr}.zip' if config['prettyName'] else 'magisk-uninstaller.zip') sign_zip(unsigned, output, args.release) + rm(unsigned) header('Output: ' + output) @@ -444,28 +524,28 @@ def build_all(args): parser = argparse.ArgumentParser(description='Magisk build script') parser.add_argument('-r', '--release', action='store_true', - help='compile Magisk for release') + help='compile in release mode') parser.add_argument('-v', '--verbose', action='store_true', help='verbose output') parser.add_argument('-c', '--config', default='config.prop', - help='config file location') + help='override config file (default: config.prop)') subparsers = parser.add_subparsers(title='actions') all_parser = subparsers.add_parser( - 'all', help='build everything (binaries/apks/zips)') + 'all', help='build binaries, apks, zips') all_parser.set_defaults(func=build_all) binary_parser = subparsers.add_parser('binary', help='build binaries') binary_parser.add_argument( - 'target', nargs='*', help=f"Either {', '.join(support_targets)}, \ + 'target', nargs='*', help=f"{', '.join(support_targets)}, \ or empty for defaults ({', '.join(default_targets)})") binary_parser.set_defaults(func=build_binary) -apk_parser = subparsers.add_parser('apk', help='build Magisk Manager APK') -apk_parser.set_defaults(func=build_app) +app_parser = subparsers.add_parser('app', help='build Magisk Manager') +app_parser.set_defaults(func=build_app) stub_parser = subparsers.add_parser( - 'stub', help='build stub Magisk Manager APK') + 'stub', help='build stub Magisk Manager') stub_parser.set_defaults(func=build_stub) snet_parser = subparsers.add_parser( @@ -480,9 +560,9 @@ un_parser = subparsers.add_parser( 'uninstaller', help='create flashable uninstaller') un_parser.set_defaults(func=zip_uninstaller) -clean_parser = subparsers.add_parser('clean', help='cleanup.') +clean_parser = subparsers.add_parser('clean', help='cleanup') clean_parser.add_argument( - 'target', nargs='*', help='Either native, java, or empty to clean both.') + 'target', nargs='*', help='native, java, or empty to clean both') clean_parser.set_defaults(func=cleanup) if len(sys.argv) == 1: @@ -490,33 +570,7 @@ if len(sys.argv) == 1: sys.exit(1) args = parser.parse_args() - -# Some default values -config['outdir'] = 'out' -config['prettyName'] = 'false' - -with open(args.config, 'r') as f: - for line in [l.strip(' \t\r\n') for l in f]: - if line.startswith('#') or len(line) == 0: - continue - prop = line.split('=') - config[prop[0].strip(' \t\r\n')] = prop[1].strip(' \t\r\n') - -if 'version' not in config or 'versionCode' not in config: - error('"version" and "versionCode" is required in "config.prop"') - -try: - config['versionCode'] = int(config['versionCode']) -except ValueError: - error('"versionCode" is required to be an integer') - -config['prettyName'] = config['prettyName'].lower() == 'true' - -mkdir_p(config['outdir']) - -if args.release and not os.path.exists(keystore): - error(f'Please generate a java keystore and place it in "{keystore}"') -STDOUT = None if args.verbose else subprocess.DEVNULL +load_config(args) # Call corresponding functions args.func(args) diff --git a/config.prop.sample b/config.prop.sample index ffeb3678d..261e3f750 100644 --- a/config.prop.sample +++ b/config.prop.sample @@ -13,8 +13,9 @@ outdir=out prettyName=false # Only used when building with release flag -# These passwords are used along with release-key.jks to sign APKs and zips +# These passwords are used along with keyStore to sign APKs and zips # keyPass is the pwd for the specified keyAlias +keyStore=release-key.jks keyStorePass= keyAlias= keyPass= diff --git a/docs/README.md b/docs/README.md index 5f1763509..6d1732db5 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,4 +1,4 @@ -# Magisk Documentations +# Magisk Documentation (Updated on 2019.9.19) - [Installation](install.md) @@ -6,7 +6,7 @@ - [OTA Installation](tutorials.md#ota-installation) - [Best Practices for MagiskHide](tutorials.md#best-practices-for-magiskhide) -The followings are for developers +The following sections are for developers - [Magisk Details](details.md) - [Magisk Tools](tools.md) diff --git a/native/jni/core/daemon.cpp b/native/jni/core/daemon.cpp index 41dc7d97c..f13f05d3e 100644 --- a/native/jni/core/daemon.cpp +++ b/native/jni/core/daemon.cpp @@ -54,6 +54,7 @@ static void *request_handler(void *args) { case BOOT_COMPLETE: case SQLITE_CMD: case BROADCAST_ACK: + case BROADCAST_TEST: if (credential.uid != 0) { write_int(client, ROOT_REQUIRED); close(client); @@ -91,9 +92,10 @@ static void *request_handler(void *args) { exec_sql(client); break; case BROADCAST_ACK: - LOGD("* Use broadcasts for su logging and notify\n"); - CONNECT_BROADCAST = true; - close(client); + broadcast_ack(client); + break; + case BROADCAST_TEST: + broadcast_test(client); break; case REMOVE_MODULES: if (credential.uid == UID_SHELL || credential.uid == UID_ROOT) { diff --git a/native/jni/core/db.cpp b/native/jni/core/db.cpp index 701f62c41..bd451b64d 100644 --- a/native/jni/core/db.cpp +++ b/native/jni/core/db.cpp @@ -219,7 +219,6 @@ int get_db_strings(db_strings &str, int key) { char *err; auto string_cb = [&](db_row &row) -> bool { str[row["key"]] = row["value"]; - LOGD("magiskdb: query %s=[%s]\n", row["key"].data(), row["value"].data()); return true; }; if (key >= 0) { @@ -273,6 +272,7 @@ int validate_manager(string &alt_pkg, int userid, struct stat *st) { } void exec_sql(int client) { + run_finally f([=]{ close(client); }); char *sql = read_string(client); char *err = db_exec(sql, [&](db_row &row) -> bool { string out; @@ -289,9 +289,6 @@ void exec_sql(int client) { return true; }); free(sql); - db_err_cmd(err, - write_int(client, 0); - return; - ); - close(client); + write_int(client, 0); + db_err_cmd(err, return; ); } diff --git a/native/jni/core/magisk.cpp b/native/jni/core/magisk.cpp index a93ea1eaa..821e53be1 100644 --- a/native/jni/core/magisk.cpp +++ b/native/jni/core/magisk.cpp @@ -9,7 +9,6 @@ #include #include #include -#include #include using namespace std::literals; @@ -36,7 +35,8 @@ Advanced Options (Internal APIs): --clone-attr SRC DEST clone permission, owner, and selinux context --clone SRC DEST clone SRC to DEST --sqlite SQL exec SQL commands to Magisk database - --use-broadcast use broadcast for su logging and notify + --connect-mode [MODE] get/set connect mode for su request and notify + --broadcast-test manually trigger broadcast tests Supported init triggers: post-fs-data, service, boot-complete @@ -79,12 +79,10 @@ int magisk_main(int argc, char *argv[]) { restore_rootcon(); restorecon(); return 0; - } else if (argv[1] == "--clone-attr"sv) { - if (argc < 4) usage(); + } else if (argc >= 4 && argv[1] == "--clone-attr"sv) {; clone_attr(argv[2], argv[3]); return 0; - } else if (argv[1] == "--clone"sv) { - if (argc < 4) usage(); + } else if (argc >= 4 && argv[1] == "--clone"sv) { cp_afc(argv[2], argv[3]); return 0; } else if (argv[1] == "--daemon"sv) { @@ -103,7 +101,7 @@ int magisk_main(int argc, char *argv[]) { int fd = connect_daemon(true); write_int(fd, BOOT_COMPLETE); return read_int(fd); - } else if (argv[1] == "--sqlite"sv) { + } else if (argc >= 3 && argv[1] == "--sqlite"sv) { int fd = connect_daemon(); write_int(fd, SQLITE_CMD); write_string(fd, argv[2]); @@ -115,14 +113,23 @@ int magisk_main(int argc, char *argv[]) { printf("%s\n", res); free(res); } - } else if (argv[1] == "--use-broadcast"sv) { + } else if (argv[1] == "--connect-mode"sv) { int fd = connect_daemon(); write_int(fd, BROADCAST_ACK); - return 0; + if (argc >= 3) { + write_int(fd, parse_int(argv[2])); + } else { + write_int(fd, -1); + } + return read_int(fd); } else if (argv[1] == "--remove-modules"sv) { int fd = connect_daemon(); write_int(fd, REMOVE_MODULES); return read_int(fd); + } else if (argv[1] == "--broadcast-test"sv) { + int fd = connect_daemon(); + write_int(fd, BROADCAST_TEST); + return read_int(fd); } #if 0 /* Entry point for testing stuffs */ diff --git a/native/jni/include/daemon.h b/native/jni/include/daemon.h index 92e1b5b62..0f6956b0e 100644 --- a/native/jni/include/daemon.h +++ b/native/jni/include/daemon.h @@ -19,6 +19,7 @@ enum { SQLITE_CMD, BROADCAST_ACK, REMOVE_MODULES, + BROADCAST_TEST, }; // Return codes for daemon @@ -84,10 +85,13 @@ void magiskhide_handler(int client); *************/ void su_daemon_handler(int client, struct ucred *credential); -void broadcast_test(); +void broadcast_test(int client = -1); +void broadcast_ack(int client); + +/********************* + * Daemon Global Vars + *********************/ extern int SDK_INT; extern bool RECOVERY_MODE; -extern bool CONNECT_BROADCAST; - #define APP_DATA_DIR (SDK_INT >= 24 ? "/data/user_de" : "/data/user") diff --git a/native/jni/init/init.cpp b/native/jni/init/init.cpp index c95b29fdb..c1b56dd9e 100644 --- a/native/jni/init/init.cpp +++ b/native/jni/init/init.cpp @@ -215,7 +215,7 @@ int main(int argc, char *argv[]) { init = make_unique(argv, &cmd); } else { decompress_ramdisk(); - if (access("/sbin/recovery", F_OK) == 0) + if (access("/sbin/recovery", F_OK) == 0 || access("/system/bin/recovery", F_OK) == 0) init = make_unique(argv, &cmd); else if (access("/apex", F_OK) == 0) init = make_unique(argv, &cmd); diff --git a/native/jni/magiskboot/dtb.cpp b/native/jni/magiskboot/dtb.cpp index 6ca6442af..d1641a12c 100644 --- a/native/jni/magiskboot/dtb.cpp +++ b/native/jni/magiskboot/dtb.cpp @@ -17,7 +17,8 @@ using namespace std; #define QCDT_MAGIC "QCDT" #define DTBH_MAGIC "DTBH" #define PXADT_MAGIC "PXA-DT" -#define PXA_19xx_MAGIC "PXA-19xx" +#define PXA19xx_MAGIC "PXA-19xx" +#define SPRD_MAGIC "SPRD" struct qcdt_hdr { char magic[4]; /* "QCDT" */ @@ -58,19 +59,31 @@ struct bhtable_v2 { struct pxadt_hdr { char magic[6]; /* "PXA-DT" */ - uint32_t version; /* PXA-DT version */ + uint32_t version; /* PXA-* version */ uint32_t num_dtbs; /* Number of DTBs */ } __attribute__((packed)); -struct pxa_19xx_hdr { +struct pxa19xx_hdr { char magic[8]; /* "PXA-19xx" */ - uint32_t version; /* PXA-DT version */ + uint32_t version; /* PXA-* version */ uint32_t num_dtbs; /* Number of DTBs */ } __attribute__((packed)); -struct pxa_table_v1 { +struct pxatable_v1 { uint32_t cpu_info[2]; /* Some CPU info */ - uint32_t offset; /* DTB offset in PXA-DT */ + uint32_t offset; /* DTB offset in PXA-* */ + uint32_t len; /* DTB size */ +} __attribute__((packed)); + +struct sprd_hdr { + char magic[4]; /* "SPRD" */ + uint32_t version; /* SPRD version */ + uint32_t num_dtbs; /* Number of DTBs */ +} __attribute__((packed)); + +struct sprdtable_v1 { + uint32_t cpu_info[3]; /* Some CPU info */ + uint32_t offset; /* DTB offset in SPRD */ uint32_t len; /* DTB size */ } __attribute__((packed)); @@ -358,16 +371,25 @@ static int dtb_patch(const char *in, const char *out) { switch (hdr->version) { case 1: fprintf(stderr, "PXA-DT v1\n"); - return dtb_patch(hdr, in, out); + return dtb_patch(hdr, in, out); default: return 1; } - } else if (MATCH(PXA_19xx_MAGIC)) { - auto hdr = reinterpret_cast(dtb); + } else if (MATCH(PXA19xx_MAGIC)) { + auto hdr = reinterpret_cast(dtb); switch (hdr->version) { case 1: fprintf(stderr, "PXA-19xx v1\n"); - return dtb_patch(hdr, in, out); + return dtb_patch(hdr, in, out); + default: + return 1; + } + } else if (MATCH(SPRD_MAGIC)) { + auto hdr = reinterpret_cast(dtb); + switch (hdr->version) { + case 1: + fprintf(stderr, "SPRD v1\n"); + return dtb_patch(hdr, in, out); default: return 1; } diff --git a/native/jni/su/connect.cpp b/native/jni/su/connect.cpp index f59155863..f83cf2c9c 100644 --- a/native/jni/su/connect.cpp +++ b/native/jni/su/connect.cpp @@ -7,12 +7,20 @@ #include #include +#include #include "su.h" using namespace std; -bool CONNECT_BROADCAST; +enum connect_mode { + UNINITIALIZED = 0, + MODE_ACTIVITY, + MODE_BROADCAST_COMPONENT, + MODE_BROADCAST_PACKAGE +}; + +static connect_mode current_mode = UNINITIALIZED; #define START_ACTIVITY \ "/system/bin/app_process", "/system/bin", "com.android.commands.am.Am", \ @@ -25,9 +33,28 @@ bool CONNECT_BROADCAST; "broadcast", "-n", nullptr, "--user", nullptr, "-f", "0x00000020", \ "-a", "android.intent.action.REBOOT", "--es", "action" +#define START_BROADCAST_PKG \ +"/system/bin/app_process", "/system/bin", "com.android.commands.am.Am", \ +"broadcast", "-p", nullptr, "--user", nullptr, "-f", "0x00000020", \ +"-a", "android.intent.action.REBOOT", "--es", "action" + // 0x00000020 = FLAG_INCLUDE_STOPPED_PACKAGES -static inline const char *get_command(const su_request *to) { +#define am_app_info(info, ...) \ +if (current_mode == MODE_BROADCAST_PACKAGE) { \ + const char *cmd[] = { START_BROADCAST_PKG, __VA_ARGS__, nullptr }; \ + exec_am_cmd(cmd, info); \ +} else if (current_mode == MODE_BROADCAST_COMPONENT) { \ + const char *cmd[] = { START_BROADCAST, __VA_ARGS__, nullptr }; \ + exec_am_cmd(cmd, info); \ +} else { \ + const char *cmd[] = { START_ACTIVITY, __VA_ARGS__, nullptr }; \ + exec_am_cmd(cmd, info); \ +} + +#define am_app(...) am_app_info(ctx.info.get(), __VA_ARGS__) + +static const char *get_command(const su_request *to) { if (to->command[0]) return to->command; if (to->shell[0]) @@ -35,14 +62,14 @@ static inline const char *get_command(const su_request *to) { return DEFAULT_SHELL; } -static inline void get_user(char *user, const su_info *info) { +static void get_user(char *user, const su_info *info) { sprintf(user, "%d", info->cfg[SU_MULTIUSER_MODE] == MULTIUSER_MODE_USER ? info->uid / 100000 : 0); } -static inline void get_uid(char *uid, const su_info *info) { +static void get_uid(char *uid, const su_info *info) { sprintf(uid, "%d", info->cfg[SU_MULTIUSER_MODE] == MULTIUSER_MODE_OWNER_MANAGED ? info->uid % 100000 @@ -50,13 +77,25 @@ static inline void get_uid(char *uid, const su_info *info) { } static void exec_am_cmd(const char **args, const su_info *info) { - char component[128]; - sprintf(component, "%s/%s", info->str[SU_MANAGER].data(), args[3][0] == 'b' ? "a.h" : "a.m"); + char target[128]; + if (args[3][0] == 'b') { + // Broadcast + if (args[4][1] == 'p') { + // Broadcast to package (receiver can be obfuscated) + strcpy(target, info->str[SU_MANAGER].data()); + } else { + // a.h is the broadcast receiver + sprintf(target, "%s/a.h", info->str[SU_MANAGER].data()); + } + } else { + // a.m is the activity + sprintf(target, "%s/a.m", info->str[SU_MANAGER].data()); + } char user[8]; get_user(user, info); - /* Fill in dynamic arguments */ - args[5] = component; + // Fill in non static arguments + args[5] = target; args[7] = user; exec_t exec { @@ -79,8 +118,7 @@ static void exec_am_cmd(const char **args, const su_info *info) { "--ei", "pid", pid, \ "--ei", "policy", policy, \ "--es", "command", get_command(&ctx.req), \ -"--ez", "notify", ctx.info->access.notify ? "true" : "false", \ -nullptr +"--ez", "notify", ctx.info->access.notify ? "true" : "false" void app_log(const su_context &ctx) { char fromUid[8]; @@ -95,20 +133,13 @@ void app_log(const su_context &ctx) { char policy[2]; sprintf(policy, "%d", ctx.info->access.policy); - if (CONNECT_BROADCAST) { - const char *cmd[] = { START_BROADCAST, LOG_BODY }; - exec_am_cmd(cmd, ctx.info.get()); - } else { - const char *cmd[] = { START_ACTIVITY, LOG_BODY }; - exec_am_cmd(cmd, ctx.info.get()); - } + am_app(LOG_BODY) } #define NOTIFY_BODY \ "notify", \ "--ei", "from.uid", fromUid, \ -"--ei", "policy", policy, \ -nullptr +"--ei", "policy", policy void app_notify(const su_context &ctx) { char fromUid[8]; @@ -117,33 +148,59 @@ void app_notify(const su_context &ctx) { char policy[2]; sprintf(policy, "%d", ctx.info->access.policy); - if (CONNECT_BROADCAST) { - const char *cmd[] = { START_BROADCAST, NOTIFY_BODY }; - exec_am_cmd(cmd, ctx.info.get()); - } else { - const char *cmd[] = { START_ACTIVITY, NOTIFY_BODY }; - exec_am_cmd(cmd, ctx.info.get()); + am_app(NOTIFY_BODY) +} + +#define SOCKET_BODY \ +"request", \ +"--es", "socket", socket + +void app_socket(const char *socket, const shared_ptr &info) { + am_app_info(info.get(), SOCKET_BODY) +} + +#define TEST_BODY \ +"test", "--ei", "mode", mode, nullptr + +void broadcast_test(int client) { + if (client >= 0) { + // Make it not uninitialized + current_mode = MODE_ACTIVITY; + write_int(client, 0); + close(client); } -} - -void app_connect(const char *socket, const shared_ptr &info) { - const char *cmd[] = { - START_ACTIVITY, "request", - "--es", "socket", socket, - nullptr - }; - exec_am_cmd(cmd, info.get()); -} - -void broadcast_test() { su_info info; get_db_settings(info.cfg); get_db_strings(info.str); validate_manager(info.str[SU_MANAGER], 0, &info.mgr_st); - const char *cmd[] = { START_BROADCAST, "test", nullptr }; - exec_am_cmd(cmd, &info); + char mode[2]; + { + sprintf(mode, "%d", MODE_BROADCAST_PACKAGE); + const char *cmd[] = { START_BROADCAST_PKG, TEST_BODY }; + exec_am_cmd(cmd, &info); + } + { + sprintf(mode, "%d", MODE_BROADCAST_COMPONENT); + const char *cmd[] = { START_BROADCAST, TEST_BODY }; + exec_am_cmd(cmd, &info); + } +} + +void broadcast_ack(int client) { + int mode = read_int(client); + if (mode < 0) { + // Return connection mode to client + write_int(client, current_mode); + } else { + if (mode > current_mode) { + LOGD("* Use connect mode [%d] for su request and notify\n", mode); + current_mode = static_cast(mode); + } + write_int(client, 0); + } + close(client); } void socket_send_request(int fd, const shared_ptr &info) { diff --git a/native/jni/su/su.h b/native/jni/su/su.h index c496e5bbf..2c18fc5c8 100644 --- a/native/jni/su/su.h +++ b/native/jni/su/su.h @@ -68,5 +68,5 @@ struct su_context { void app_log(const su_context &ctx); void app_notify(const su_context &ctx); -void app_connect(const char *socket, const std::shared_ptr &info); +void app_socket(const char *socket, const std::shared_ptr &info); void socket_send_request(int fd, const std::shared_ptr &info); diff --git a/native/jni/su/su_daemon.cpp b/native/jni/su/su_daemon.cpp index 5debb5cd5..660ca1175 100644 --- a/native/jni/su/su_daemon.cpp +++ b/native/jni/su/su_daemon.cpp @@ -144,7 +144,7 @@ static shared_ptr get_su_info(unsigned uid) { int sockfd = create_rand_socket(&addr); // Connect manager - app_connect(addr.sun_path + 1, info); + app_socket(addr.sun_path + 1, info); int fd = socket_accept(sockfd, 60); if (fd < 0) { info->access.policy = DENY; diff --git a/scripts/emulator.sh b/scripts/emulator.sh index af7be4a6a..ceb3e8388 100755 --- a/scripts/emulator.sh +++ b/scripts/emulator.sh @@ -36,7 +36,6 @@ if [ ! -f /system/build.prop ]; then else adb push native/out/x86/magiskinit /data/local/tmp fi - adb root || abort 'adb root failed' adb shell sh /data/local/tmp/emulator.sh exit 0 fi @@ -45,12 +44,10 @@ cd /data/local/tmp chmod 777 busybox chmod 777 magiskinit -# Emulator's adb shell should have root -[ `./busybox id -u` -eq 0 ] || abort 'ADB shell should have root access' - -# Check whether already setup -[ -f /sbin/magisk ] && abort "Magisk is already setup" -./busybox pgrep magiskd && abort "Magisk is already setup" +if [ `./busybox id -u` -ne 0 ]; then + # Re-run script with root + exec /system/xbin/su 0 sh $0 +fi # First setup a good env to work with rm -rf bin @@ -59,6 +56,10 @@ rm -rf bin OLD_PATH="$PATH" PATH="/data/local/tmp/bin:$PATH" +# Remove previous setup if exist +pgrep magiskd >/dev/null && pkill -9 magiskd +[ -f /sbin/magisk ] && umount -l /sbin + # SELinux stuffs [ -e /sys/fs/selinux ] && SELINUX=true || SELINUX=false if $SELINUX; then diff --git a/shared/src/main/AndroidManifest.xml b/shared/src/main/AndroidManifest.xml index 73a5f831e..c42a2dd7f 100644 --- a/shared/src/main/AndroidManifest.xml +++ b/shared/src/main/AndroidManifest.xml @@ -8,7 +8,7 @@ = 24) { + // Use protected context to allow directBootAware + c = c.createDeviceProtectedStorageContext(); + } + dynDir = new File(c.getFilesDir().getParent(), "dyn"); + dynDir.mkdir(); + } + return dynDir; + } + + public static File current(Context c) { + return new File(getDynDir(c), "current.apk"); + } + + public static File update(Context c) { + return new File(getDynDir(c), "update.apk"); + } + + public static Data load(Object o) { + Object[] arr = (Object[]) o; + Data data = new Data(); + data.version = (int) arr[STUB_VERSION_ENTRY]; + data.componentMap = (Map) arr[COMPONENT_MAP]; + data.resourceMap = (int[]) arr[RESOURCE_MAP]; + return data; + } + + public static Object pack(Data data) { + Object[] arr = new Object[3]; + arr[STUB_VERSION_ENTRY] = STUB_VERSION; + arr[COMPONENT_MAP] = data.componentMap; + arr[RESOURCE_MAP] = data.resourceMap; + return arr; + } + + public static void addAssetPath(AssetManager asset, String path) { + try { + if (addAssetPath == null) + addAssetPath = AssetManager.class.getMethod("addAssetPath", String.class); + addAssetPath.invoke(asset, path); + } catch (Exception ignored) {} + } + + public static class Data { + public int version; + public Map componentMap; + public int[] resourceMap; + } +} diff --git a/shared/src/main/java/com/topjohnwu/magisk/utils/FileProvider.java b/shared/src/main/java/com/topjohnwu/magisk/FileProvider.java similarity index 99% rename from shared/src/main/java/com/topjohnwu/magisk/utils/FileProvider.java rename to shared/src/main/java/com/topjohnwu/magisk/FileProvider.java index 5d478f1b6..9b7ea0a73 100644 --- a/shared/src/main/java/com/topjohnwu/magisk/utils/FileProvider.java +++ b/shared/src/main/java/com/topjohnwu/magisk/FileProvider.java @@ -1,4 +1,4 @@ -package com.topjohnwu.magisk.utils; +package com.topjohnwu.magisk; import android.content.ContentProvider; import android.content.ContentValues; diff --git a/shared/src/main/java/com/topjohnwu/magisk/utils/APKInstall.java b/shared/src/main/java/com/topjohnwu/magisk/utils/APKInstall.java index 66f7567aa..2270017ec 100644 --- a/shared/src/main/java/com/topjohnwu/magisk/utils/APKInstall.java +++ b/shared/src/main/java/com/topjohnwu/magisk/utils/APKInstall.java @@ -5,6 +5,8 @@ import android.content.Intent; import android.net.Uri; import android.os.Build; +import com.topjohnwu.magisk.FileProvider; + import java.io.File; public class APKInstall { diff --git a/shared/src/main/java/com/topjohnwu/magisk/utils/DynAPK.java b/shared/src/main/java/com/topjohnwu/magisk/utils/DynAPK.java deleted file mode 100644 index 4c1d170c5..000000000 --- a/shared/src/main/java/com/topjohnwu/magisk/utils/DynAPK.java +++ /dev/null @@ -1,16 +0,0 @@ -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"); - } -} diff --git a/app/src/main/res/drawable-anydpi-v21/ic_magisk_outline.xml b/shared/src/main/res/drawable-anydpi-v21/ic_magisk_outline.xml similarity index 100% rename from app/src/main/res/drawable-anydpi-v21/ic_magisk_outline.xml rename to shared/src/main/res/drawable-anydpi-v21/ic_magisk_outline.xml diff --git a/app/src/main/res/drawable-anydpi-v26/ic_launcher.xml b/shared/src/main/res/drawable-anydpi-v26/ic_launcher.xml similarity index 100% rename from app/src/main/res/drawable-anydpi-v26/ic_launcher.xml rename to shared/src/main/res/drawable-anydpi-v26/ic_launcher.xml diff --git a/app/src/main/res/drawable-v26/sc_cloud_download.xml b/shared/src/main/res/drawable-v26/sc_cloud_download.xml similarity index 80% rename from app/src/main/res/drawable-v26/sc_cloud_download.xml rename to shared/src/main/res/drawable-v26/sc_cloud_download.xml index 734f23865..3d3c9114a 100644 --- a/app/src/main/res/drawable-v26/sc_cloud_download.xml +++ b/shared/src/main/res/drawable-v26/sc_cloud_download.xml @@ -1,6 +1,6 @@ - + - + - + - + diff --git a/app/src/main/res/drawable/ic_extension.xml b/shared/src/main/res/drawable/ic_extension.xml similarity index 92% rename from app/src/main/res/drawable/ic_extension.xml rename to shared/src/main/res/drawable/ic_extension.xml index 549fdce23..7fb337518 100644 --- a/app/src/main/res/drawable/ic_extension.xml +++ b/shared/src/main/res/drawable/ic_extension.xml @@ -4,6 +4,6 @@ android:viewportHeight="24.0" android:viewportWidth="24.0"> diff --git a/app/src/main/res/drawable/ic_magisk_padded.xml b/shared/src/main/res/drawable/ic_magisk_padded.xml similarity index 100% rename from app/src/main/res/drawable/ic_magisk_padded.xml rename to shared/src/main/res/drawable/ic_magisk_padded.xml diff --git a/app/src/main/res/drawable/ic_magiskhide.xml b/shared/src/main/res/drawable/ic_magiskhide.xml similarity index 98% rename from app/src/main/res/drawable/ic_magiskhide.xml rename to shared/src/main/res/drawable/ic_magiskhide.xml index 6a3ce181d..4b46314af 100644 --- a/app/src/main/res/drawable/ic_magiskhide.xml +++ b/shared/src/main/res/drawable/ic_magiskhide.xml @@ -5,6 +5,6 @@ android:width="24dp"> diff --git a/app/src/main/res/drawable/ic_superuser.xml b/shared/src/main/res/drawable/ic_superuser.xml similarity index 90% rename from app/src/main/res/drawable/ic_superuser.xml rename to shared/src/main/res/drawable/ic_superuser.xml index 01fb6a803..a4f956855 100644 --- a/app/src/main/res/drawable/ic_superuser.xml +++ b/shared/src/main/res/drawable/ic_superuser.xml @@ -4,6 +4,6 @@ android:viewportWidth="24" android:viewportHeight="24"> \ No newline at end of file diff --git a/shared/src/main/res/values-ar/strings.xml b/shared/src/main/res/values-ar/strings.xml deleted file mode 100644 index 6e7deae89..000000000 --- a/shared/src/main/res/values-ar/strings.xml +++ /dev/null @@ -1,5 +0,0 @@ - - لا شكراً - نعم - موافق - diff --git a/shared/src/main/res/values-cs/strings.xml b/shared/src/main/res/values-cs/strings.xml deleted file mode 100644 index 0aa2702ab..000000000 --- a/shared/src/main/res/values-cs/strings.xml +++ /dev/null @@ -1,5 +0,0 @@ - - Ne, díky - Ano - OK - diff --git a/shared/src/main/res/values-el/strings.xml b/shared/src/main/res/values-el/strings.xml deleted file mode 100644 index bd0c41ae2..000000000 --- a/shared/src/main/res/values-el/strings.xml +++ /dev/null @@ -1,5 +0,0 @@ - - Όχι ευχαριστώ - Ναι - OK - diff --git a/shared/src/main/res/values-hr/strings.xml b/shared/src/main/res/values-hr/strings.xml deleted file mode 100644 index 919a11b57..000000000 --- a/shared/src/main/res/values-hr/strings.xml +++ /dev/null @@ -1,5 +0,0 @@ - - Ne hvala - Da - OK - \ No newline at end of file diff --git a/shared/src/main/res/values-ja/strings.xml b/shared/src/main/res/values-ja/strings.xml deleted file mode 100644 index 66c34dc33..000000000 --- a/shared/src/main/res/values-ja/strings.xml +++ /dev/null @@ -1,5 +0,0 @@ - - いいえ - はい - OK - \ No newline at end of file diff --git a/shared/src/main/res/values-nl/strings.xml b/shared/src/main/res/values-nl/strings.xml deleted file mode 100644 index d6812f1e1..000000000 --- a/shared/src/main/res/values-nl/strings.xml +++ /dev/null @@ -1,5 +0,0 @@ - - Nee bedankt - Ja - Oké - diff --git a/shared/src/main/res/values-pt-rBR/strings.xml b/shared/src/main/res/values-pt-rBR/strings.xml deleted file mode 100644 index 9d4f3eafd..000000000 --- a/shared/src/main/res/values-pt-rBR/strings.xml +++ /dev/null @@ -1,5 +0,0 @@ - - Não - Sim - OK - diff --git a/shared/src/main/res/values-pt-rPT/strings.xml b/shared/src/main/res/values-pt-rPT/strings.xml deleted file mode 100644 index 6850a7943..000000000 --- a/shared/src/main/res/values-pt-rPT/strings.xml +++ /dev/null @@ -1,5 +0,0 @@ - - Não, Obrigado - Sim - OK - diff --git a/shared/src/main/res/values-sr/strings.xml b/shared/src/main/res/values-sr/strings.xml deleted file mode 100644 index 2e568e78b..000000000 --- a/shared/src/main/res/values-sr/strings.xml +++ /dev/null @@ -1,5 +0,0 @@ - - Не хвала - Да - ОК - diff --git a/shared/src/main/res/values-sv/strings.xml b/shared/src/main/res/values-sv/strings.xml deleted file mode 100644 index 57e72dd52..000000000 --- a/shared/src/main/res/values-sv/strings.xml +++ /dev/null @@ -1,5 +0,0 @@ - - Nej tack - Ja - OK - diff --git a/shared/src/main/res/values-th/strings.xml b/shared/src/main/res/values-th/strings.xml deleted file mode 100644 index 21ac194ff..000000000 --- a/shared/src/main/res/values-th/strings.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - ไม่, ขอบคุณ - ใช่ - โอเค - - \ No newline at end of file diff --git a/shared/src/main/res/values-v23/values.xml b/shared/src/main/res/values-v23/values.xml deleted file mode 100644 index 0a0a96685..000000000 --- a/shared/src/main/res/values-v23/values.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - false - true - diff --git a/shared/src/main/res/values-vi/strings.xml b/shared/src/main/res/values-vi/strings.xml deleted file mode 100644 index 18e8582b4..000000000 --- a/shared/src/main/res/values-vi/strings.xml +++ /dev/null @@ -1,5 +0,0 @@ - - Không, cảm ơn - - OK - diff --git a/shared/src/main/res/values/colors.xml b/shared/src/main/res/values/colors.xml index f307cc63e..df6f5cef5 100644 --- a/shared/src/main/res/values/colors.xml +++ b/shared/src/main/res/values/colors.xml @@ -1,4 +1,6 @@ #00AF9C - \ No newline at end of file + #00796B + #e0e0e0 + diff --git a/shared/src/main/res/values/strings.xml b/shared/src/main/res/values/strings.xml deleted file mode 100644 index 891efcd11..000000000 --- a/shared/src/main/res/values/strings.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - Magisk Manager - Manager - Magisk - Magisk Hide - - - No thanks - Yes - OK - Upgrade to full Magisk Manager to finish the setup. Download and install? - Please connect to the Internet! Upgrading to full Magisk Manager is required. - diff --git a/shared/src/main/res/values/values.xml b/shared/src/main/res/values/values.xml deleted file mode 100644 index 285dd6910..000000000 --- a/shared/src/main/res/values/values.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - true - false - false - diff --git a/signing/src/main/java/com/topjohnwu/signing/CryptoUtils.java b/signing/src/main/java/com/topjohnwu/signing/CryptoUtils.java index a80963f06..2bbc22a16 100644 --- a/signing/src/main/java/com/topjohnwu/signing/CryptoUtils.java +++ b/signing/src/main/java/com/topjohnwu/signing/CryptoUtils.java @@ -24,7 +24,7 @@ import java.security.spec.PKCS8EncodedKeySpec; import java.util.HashMap; import java.util.Map; -class CryptoUtils { +public class CryptoUtils { static final Map ID_TO_ALG; static final Map ALG_TO_ID; @@ -81,7 +81,7 @@ class CryptoUtils { return new AlgorithmIdentifier(new ASN1ObjectIdentifier(id)); } - static X509Certificate readCertificate(InputStream input) + public static X509Certificate readCertificate(InputStream input) throws IOException, GeneralSecurityException { try { CertificateFactory cf = CertificateFactory.getInstance("X.509"); @@ -92,7 +92,7 @@ class CryptoUtils { } /** Read a PKCS#8 format private key. */ - static PrivateKey readPrivateKey(InputStream input) + public static PrivateKey readPrivateKey(InputStream input) throws IOException, GeneralSecurityException { try { ByteArrayStream buf = new ByteArrayStream(); diff --git a/signing/src/main/java/com/topjohnwu/signing/SignAPK.java b/signing/src/main/java/com/topjohnwu/signing/SignAPK.java index f965fdfa8..1c84f33de 100644 --- a/signing/src/main/java/com/topjohnwu/signing/SignAPK.java +++ b/signing/src/main/java/com/topjohnwu/signing/SignAPK.java @@ -30,7 +30,6 @@ import java.io.PrintStream; import java.io.RandomAccessFile; import java.security.DigestOutputStream; import java.security.GeneralSecurityException; -import java.security.KeyStore; import java.security.MessageDigest; import java.security.PrivateKey; import java.security.cert.CertificateEncodingException; @@ -61,29 +60,8 @@ public class SignAPK { private static final int USE_SHA1 = 1; private static final int USE_SHA256 = 2; - public static void sign(JarMap input, OutputStream output) throws Exception { - sign(SignAPK.class.getResourceAsStream("/keys/testkey.x509.pem"), - SignAPK.class.getResourceAsStream("/keys/testkey.pk8"), input, output); - } - - public static void sign(InputStream certIs, InputStream keyIs, - JarMap input, OutputStream output) throws Exception { - X509Certificate cert = CryptoUtils.readCertificate(certIs); - PrivateKey key = CryptoUtils.readPrivateKey(keyIs); - sign(cert, key, input, output); - } - - public static void sign(InputStream jks, String keyStorePass, String alias, String keyPass, - JarMap input, OutputStream output) throws Exception { - KeyStore ks = KeyStore.getInstance("JKS"); - ks.load(jks, keyStorePass.toCharArray()); - X509Certificate cert = (X509Certificate) ks.getCertificate(alias); - PrivateKey key = (PrivateKey) ks.getKey(alias, keyPass.toCharArray()); - sign(cert, key, input, output); - } - - private static void sign(X509Certificate cert, PrivateKey key, - JarMap input, OutputStream output) throws Exception { + public static void signAndAdjust(X509Certificate cert, PrivateKey key, + JarMap input, OutputStream output) throws Exception { File temp1 = File.createTempFile("signAPK", null); File temp2 = File.createTempFile("signAPK", null); @@ -103,6 +81,11 @@ public class SignAPK { } } + public static void sign(X509Certificate cert, PrivateKey key, + JarMap input, OutputStream output) throws Exception { + sign(cert, key, input, output, false); + } + private static void sign(X509Certificate cert, PrivateKey key, JarMap input, OutputStream output, boolean minSign) throws Exception { int hashes = 0; @@ -498,11 +481,11 @@ public class SignAPK { outputStream.close(); } private static void signFile(Manifest manifest, JarMap inputJar, - X509Certificate publicKey, PrivateKey privateKey, + X509Certificate cert, PrivateKey privateKey, JarOutputStream outputJar) throws Exception { // Assume the certificate is valid for at least an hour. - long timestamp = publicKey.getNotBefore().getTime() + 3600L * 1000; + long timestamp = cert.getNotBefore().getTime() + 3600L * 1000; // MANIFEST.MF JarEntry je = new JarEntry(JarFile.MANIFEST_NAME); je.setTime(timestamp); @@ -512,15 +495,15 @@ public class SignAPK { je.setTime(timestamp); outputJar.putNextEntry(je); ByteArrayOutputStream baos = new ByteArrayOutputStream(); - writeSignatureFile(manifest, baos, getDigestAlgorithm(publicKey)); + writeSignatureFile(manifest, baos, getDigestAlgorithm(cert)); byte[] signedData = baos.toByteArray(); outputJar.write(signedData); // CERT.{EC,RSA} / CERT#.{EC,RSA} - final String keyType = publicKey.getPublicKey().getAlgorithm(); + final String keyType = cert.getPublicKey().getAlgorithm(); je = new JarEntry(String.format(CERT_SIG_NAME, keyType)); je.setTime(timestamp); outputJar.putNextEntry(je); writeSignatureBlock(new CMSProcessableByteArray(signedData), - publicKey, privateKey, outputJar); + cert, privateKey, outputJar); } } diff --git a/signing/src/main/java/com/topjohnwu/signing/ZipSigner.java b/signing/src/main/java/com/topjohnwu/signing/ZipSigner.java index 8e68de715..9e3fce6ed 100644 --- a/signing/src/main/java/com/topjohnwu/signing/ZipSigner.java +++ b/signing/src/main/java/com/topjohnwu/signing/ZipSigner.java @@ -4,23 +4,61 @@ import org.bouncycastle.jce.provider.BouncyCastleProvider; import java.io.FileInputStream; import java.io.FileOutputStream; +import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; import java.security.Security; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; public class ZipSigner { - public static void usage() { + private static void usage() { System.err.println("ZipSigner usage:"); System.err.println(" zipsigner.jar input.jar output.jar"); System.err.println(" sign jar with AOSP test keys"); System.err.println(" zipsigner.jar x509.pem pk8 input.jar output.jar"); System.err.println(" sign jar with certificate / private key pair"); - System.err.println(" zipsigner.jar jks keyStorePass keyAlias keyPass input.jar output.jar"); + System.err.println(" zipsigner.jar keyStore keyStorePass alias keyPass input.jar output.jar"); System.err.println(" sign jar with Java KeyStore"); System.exit(2); } + private static void sign(JarMap input, OutputStream output) throws Exception { + sign(SignAPK.class.getResourceAsStream("/keys/testkey.x509.pem"), + SignAPK.class.getResourceAsStream("/keys/testkey.pk8"), input, output); + } + + private static void sign(InputStream certIs, InputStream keyIs, + JarMap input, OutputStream output) throws Exception { + X509Certificate cert = CryptoUtils.readCertificate(certIs); + PrivateKey key = CryptoUtils.readPrivateKey(keyIs); + SignAPK.signAndAdjust(cert, key, input, output); + } + + private static void sign(String keyStore, String keyStorePass, String alias, String keyPass, + JarMap in, OutputStream out) throws Exception { + KeyStore ks; + try { + ks = KeyStore.getInstance("JKS"); + try (InputStream is = new FileInputStream(keyStore)) { + ks.load(is, keyStorePass.toCharArray()); + } + } catch (KeyStoreException|IOException|CertificateException|NoSuchAlgorithmException e) { + ks = KeyStore.getInstance("PKCS12"); + try (InputStream is = new FileInputStream(keyStore)) { + ks.load(is, keyStorePass.toCharArray()); + } + } + X509Certificate cert = (X509Certificate) ks.getCertificate(alias); + PrivateKey key = (PrivateKey) ks.getKey(alias, keyPass.toCharArray()); + SignAPK.signAndAdjust(cert, key, in, out); + } + public static void main(String[] args) throws Exception { if (args.length != 2 && args.length != 4 && args.length != 6) usage(); @@ -30,16 +68,14 @@ public class ZipSigner { try (JarMap in = new JarMap(args[args.length - 2], false); OutputStream out = new FileOutputStream(args[args.length - 1])) { if (args.length == 2) { - SignAPK.sign(in, out); + sign(in, out); } else if (args.length == 4) { try (InputStream cert = new FileInputStream(args[0]); InputStream key = new FileInputStream(args[1])) { - SignAPK.sign(cert, key, in, out); + sign(cert, key, in, out); } } else if (args.length == 6) { - try (InputStream jks = new FileInputStream(args[0])) { - SignAPK.sign(jks, args[1], args[2], args[3], in, out); - } + sign(args[0], args[1], args[2], args[3], in, out); } } } diff --git a/stub/.gitignore b/stub/.gitignore index 796b96d1c..b5977f73e 100644 --- a/stub/.gitignore +++ b/stub/.gitignore @@ -1 +1,2 @@ /build +/res-ids.txt diff --git a/stub/build.gradle b/stub/build.gradle index b9e76ddae..c43fcc5a8 100644 --- a/stub/build.gradle +++ b/stub/build.gradle @@ -4,7 +4,8 @@ android { defaultConfig { applicationId 'com.topjohnwu.magisk' versionCode 1 - versionName props['appVersion'] + versionName "1.0" + buildConfigField 'String', 'DEV_CHANNEL', props['DEV_CHANNEL'] ?: 'null' } buildTypes { diff --git a/stub/src/main/AndroidManifest.xml b/stub/src/main/AndroidManifest.xml index 16dfc8081..729d39a19 100644 --- a/stub/src/main/AndroidManifest.xml +++ b/stub/src/main/AndroidManifest.xml @@ -16,8 +16,8 @@ @@ -25,7 +25,7 @@ @@ -37,25 +37,27 @@ + @@ -68,114 +70,21 @@ - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + android:exported="true" + android:permission="android.permission.BIND_JOB_SERVICE" /> diff --git a/stub/src/main/java/a/w.java b/stub/src/main/java/a/w.java new file mode 100644 index 000000000..61a2e1079 --- /dev/null +++ b/stub/src/main/java/a/w.java @@ -0,0 +1,6 @@ +package a; + +import com.topjohnwu.magisk.dummy.DummyReceiver; + +public class w extends DummyReceiver { +} diff --git a/stub/src/main/java/a/a.java b/stub/src/main/java/a/x.java similarity index 57% rename from stub/src/main/java/a/a.java rename to stub/src/main/java/a/x.java index 3510c9096..073f7f38b 100644 --- a/stub/src/main/java/a/a.java +++ b/stub/src/main/java/a/x.java @@ -2,5 +2,5 @@ package a; import com.topjohnwu.magisk.DelegateComponentFactory; -public class a extends DelegateComponentFactory { +public class x extends DelegateComponentFactory { } diff --git a/stub/src/main/java/a/e.java b/stub/src/main/java/a/y.java similarity index 58% rename from stub/src/main/java/a/e.java rename to stub/src/main/java/a/y.java index cd2e45a4c..d0c3361cd 100644 --- a/stub/src/main/java/a/e.java +++ b/stub/src/main/java/a/y.java @@ -2,5 +2,5 @@ package a; import com.topjohnwu.magisk.DelegateApplication; -public class e extends DelegateApplication { +public class y extends DelegateApplication { } diff --git a/stub/src/main/java/a/c.java b/stub/src/main/java/a/z.java similarity index 59% rename from stub/src/main/java/a/c.java rename to stub/src/main/java/a/z.java index 44b3521e1..463eba48f 100644 --- a/stub/src/main/java/a/c.java +++ b/stub/src/main/java/a/z.java @@ -2,5 +2,5 @@ package a; import com.topjohnwu.magisk.DownloadActivity; -public class c extends DownloadActivity { +public class z extends DownloadActivity { } diff --git a/stub/src/main/java/androidx/work/impl/WorkManagerInitializer.java b/stub/src/main/java/androidx/work/impl/WorkManagerInitializer.java deleted file mode 100644 index 706a63085..000000000 --- a/stub/src/main/java/androidx/work/impl/WorkManagerInitializer.java +++ /dev/null @@ -1,12 +0,0 @@ -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. */ -} diff --git a/stub/src/main/java/com/topjohnwu/magisk/DelegateApplication.java b/stub/src/main/java/com/topjohnwu/magisk/DelegateApplication.java index e56d66ae8..c338c0658 100644 --- a/stub/src/main/java/com/topjohnwu/magisk/DelegateApplication.java +++ b/stub/src/main/java/com/topjohnwu/magisk/DelegateApplication.java @@ -1,5 +1,6 @@ package com.topjohnwu.magisk; +import android.annotation.SuppressLint; import android.app.AppComponentFactory; import android.app.Application; import android.content.Context; @@ -8,14 +9,12 @@ import android.content.res.Configuration; import android.os.Build; import android.util.Log; -import com.topjohnwu.magisk.utils.DynAPK; +import com.topjohnwu.magisk.obfuscate.Mapping; 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; @@ -33,34 +32,9 @@ public class DelegateApplication extends Application { 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(); - } - } + setUpDynAPK(); } else { - MANAGER_APK = new File(base.getCacheDir(), "manager.apk"); + MANAGER_APK = new File(base.getCacheDir(), "app.apk"); } } @@ -69,4 +43,36 @@ public class DelegateApplication extends Application { super.onConfigurationChanged(newConfig); delegate.onConfigurationChanged(newConfig); } + + @SuppressLint("NewApi") + private void setUpDynAPK() { + DelegateComponentFactory factory = (DelegateComponentFactory) this.factory; + MANAGER_APK = DynAPK.current(this); + File update = DynAPK.update(this); + if (update.exists()) + update.renameTo(MANAGER_APK); + 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").getConstructor(Object.class) + .newInstance(DynAPK.pack(Mapping.data)); + + // 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(getClass().getSimpleName(), "", e); + MANAGER_APK.delete(); + } + } + } } diff --git a/stub/src/main/java/com/topjohnwu/magisk/DelegateComponentFactory.java b/stub/src/main/java/com/topjohnwu/magisk/DelegateComponentFactory.java index 4a60ee911..f21839c52 100644 --- a/stub/src/main/java/com/topjohnwu/magisk/DelegateComponentFactory.java +++ b/stub/src/main/java/com/topjohnwu/magisk/DelegateComponentFactory.java @@ -13,6 +13,7 @@ import com.topjohnwu.magisk.dummy.DummyActivity; import com.topjohnwu.magisk.dummy.DummyProvider; import com.topjohnwu.magisk.dummy.DummyReceiver; import com.topjohnwu.magisk.dummy.DummyService; +import com.topjohnwu.magisk.obfuscate.Mapping; @SuppressLint("NewApi") public class DelegateComponentFactory extends AppComponentFactory { @@ -22,7 +23,7 @@ public class DelegateComponentFactory extends AppComponentFactory { @Override public Application instantiateApplication(ClassLoader cl, String className) { - loader = cl; + if (loader == null) loader = cl; return new DelegateApplication(this); } @@ -30,7 +31,7 @@ public class DelegateComponentFactory extends AppComponentFactory { public Activity instantiateActivity(ClassLoader cl, String className, Intent intent) throws ClassNotFoundException, IllegalAccessException, InstantiationException { if (delegate != null) - return delegate.instantiateActivity(loader, className, intent); + return delegate.instantiateActivity(loader, Mapping.get(className), intent); return create(className, DummyActivity.class); } @@ -38,7 +39,7 @@ public class DelegateComponentFactory extends AppComponentFactory { public BroadcastReceiver instantiateReceiver(ClassLoader cl, String className, Intent intent) throws ClassNotFoundException, IllegalAccessException, InstantiationException { if (delegate != null) - return delegate.instantiateReceiver(loader, className, intent); + return delegate.instantiateReceiver(loader, Mapping.get(className), intent); return create(className, DummyReceiver.class); } @@ -46,15 +47,16 @@ public class DelegateComponentFactory extends AppComponentFactory { public Service instantiateService(ClassLoader cl, String className, Intent intent) throws ClassNotFoundException, IllegalAccessException, InstantiationException { if (delegate != null) - return delegate.instantiateService(loader, className, intent); + return delegate.instantiateService(loader, Mapping.get(className), intent); return create(className, DummyService.class); } @Override public ContentProvider instantiateProvider(ClassLoader cl, String className) throws ClassNotFoundException, IllegalAccessException, InstantiationException { + if (loader == null) loader = cl; if (delegate != null) - return delegate.instantiateProvider(loader, className); + return delegate.instantiateProvider(loader, Mapping.get(className)); return create(className, DummyProvider.class); } diff --git a/stub/src/main/java/com/topjohnwu/magisk/DownloadActivity.java b/stub/src/main/java/com/topjohnwu/magisk/DownloadActivity.java index f020a2b0f..38e1e9c5e 100644 --- a/stub/src/main/java/com/topjohnwu/magisk/DownloadActivity.java +++ b/stub/src/main/java/com/topjohnwu/magisk/DownloadActivity.java @@ -11,31 +11,34 @@ import android.util.Log; import com.topjohnwu.magisk.net.ErrorHandler; import com.topjohnwu.magisk.net.Networking; import com.topjohnwu.magisk.net.ResponseListener; +import com.topjohnwu.magisk.obfuscate.RawData; import com.topjohnwu.magisk.utils.APKInstall; import org.json.JSONException; import org.json.JSONObject; +import static android.R.string.no; +import static android.R.string.ok; +import static android.R.string.yes; import static com.topjohnwu.magisk.DelegateApplication.MANAGER_APK; public class DownloadActivity extends Activity { - static final String TAG = "MMStub"; - private static final boolean IS_CANARY = BuildConfig.VERSION_NAME.contains("-"); private static final String URL = - "https://raw.githubusercontent.com/topjohnwu/magisk_files/" + - (IS_CANARY ? "canary/release.json" : "master/stable.json"); + BuildConfig.DEV_CHANNEL != null ? BuildConfig.DEV_CHANNEL : + RawData.urlBase() + (BuildConfig.DEBUG ? RawData.canary() : RawData.stable()); private String apkLink; private ErrorHandler err = (conn, e) -> { - Log.e(TAG, "network error", e); + Log.e(getClass().getSimpleName(), "", e); finish(); }; private void showDialog() { ProgressDialog.show(this, - "Downloading...", - "Downloading Magisk Manager", true); + RawData.dling(), + RawData.dling() + " " + RawData.appName(), + true); } private void dlAPK() { @@ -57,7 +60,9 @@ public class DownloadActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + RawData.res = getResources(); Networking.init(this); + if (Networking.checkNetworkStatus(this)) { Networking.get(URL) .setErrorHandler(err) @@ -65,9 +70,9 @@ public class DownloadActivity extends Activity { } else { new AlertDialog.Builder(this) .setCancelable(false) - .setTitle(R.string.app_name) - .setMessage(R.string.no_internet_msg) - .setNegativeButton(R.string.ok, (d, w) -> finish()) + .setTitle(RawData.appName()) + .setMessage(RawData.no_internet_msg()) + .setNegativeButton(ok, (d, w) -> finish()) .show(); } } @@ -81,10 +86,10 @@ public class DownloadActivity extends Activity { apkLink = manager.getString("link"); new AlertDialog.Builder(DownloadActivity.this) .setCancelable(false) - .setTitle(R.string.app_name) - .setMessage(R.string.upgrade_msg) - .setPositiveButton(R.string.yes, (d, w) -> dlAPK()) - .setNegativeButton(R.string.no_thanks, (d, w) -> finish()) + .setTitle(RawData.appName()) + .setMessage(RawData.upgrade_msg()) + .setPositiveButton(yes, (d, w) -> dlAPK()) + .setNegativeButton(no, (d, w) -> finish()) .show(); } catch (JSONException e) { finish(); diff --git a/stub/src/main/java/com/topjohnwu/magisk/obfuscate/Mapping.java b/stub/src/main/java/com/topjohnwu/magisk/obfuscate/Mapping.java new file mode 100644 index 000000000..65e7eec82 --- /dev/null +++ b/stub/src/main/java/com/topjohnwu/magisk/obfuscate/Mapping.java @@ -0,0 +1,42 @@ +package com.topjohnwu.magisk.obfuscate; + +import java.util.HashMap; +import java.util.Map; + +import static com.topjohnwu.magisk.DynAPK.*; +import static com.topjohnwu.magisk.R.drawable.*; + +public class Mapping { + private static Map map = new HashMap<>(); + + // This mapping will be sent into the guest app + public static Data data = new Data(); + + static { + map.put(a.z.class.getName(), "a.c"); + map.put("a.x", "a.f"); + map.put("a.o", "a.b"); + map.put("a.g", "a.m"); + map.put(a.w.class.getName(), "a.h"); + map.put("a.v", "a.j"); + map.put("a.j", "androidx.work.impl.background.systemjob.SystemJobService"); + + data.componentMap = new HashMap<>(map.size()); + for (Map.Entry e : map.entrySet()) { + data.componentMap.put(e.getValue(), e.getKey()); + } + int[] res = new int[5]; + res[NOTIFICATION] = ic_magisk_outline; + res[SUPERUSER] = sc_superuser; + res[MAGISKHIDE] = sc_magiskhide; + res[DOWNLOAD] = sc_cloud_download; + res[MODULES] = sc_extension; + data.resourceMap = res; + } + + public static String get(String name) { + String n = map.get(name); + return n != null ? n : name; + } + +} diff --git a/stub/src/main/java/com/topjohnwu/magisk/obfuscate/RawData.java b/stub/src/main/java/com/topjohnwu/magisk/obfuscate/RawData.java new file mode 100644 index 000000000..da3dabd4e --- /dev/null +++ b/stub/src/main/java/com/topjohnwu/magisk/obfuscate/RawData.java @@ -0,0 +1,38 @@ +package com.topjohnwu.magisk.obfuscate; + +import android.content.res.Resources; + +import com.topjohnwu.magisk.R; + +public class RawData { + + public static Resources res; + + public static String appName() { + return "Magisk Manager"; + } + + public static String urlBase() { + return "https://raw.githubusercontent.com/topjohnwu/magisk_files/"; + } + + public static String canary() { + return "canary/debug.json"; + } + + public static String stable() { + return "master/stable.json"; + } + + public static String no_internet_msg() { + return res.getString(R.string.no_internet_msg); + } + + public static String upgrade_msg() { + return res.getString(R.string.upgrade_msg); + } + + public static String dling() { + return res.getString(R.string.dling); + } +} diff --git a/stub/src/main/res/values-ar/strings.xml b/stub/src/main/res/values-ar/strings.xml new file mode 100644 index 000000000..7abc06d3b --- /dev/null +++ b/stub/src/main/res/values-ar/strings.xml @@ -0,0 +1 @@ + diff --git a/shared/src/main/res/values-az/strings.xml b/stub/src/main/res/values-az/strings.xml similarity index 69% rename from shared/src/main/res/values-az/strings.xml rename to stub/src/main/res/values-az/strings.xml index b0d32921f..f8d446377 100644 --- a/shared/src/main/res/values-az/strings.xml +++ b/stub/src/main/res/values-az/strings.xml @@ -1,7 +1,4 @@ Qurmanı sonlandırmaq üçün full Magisk Manager`ə yüksəldin. Yüklənib qurulsun? Lütfən internetə qoşulun! Full Magisk Manager\'ə yüksəltmə lazımidir. - Yox, sağolun - Bəli - OK diff --git a/shared/src/main/res/values-bg/strings.xml b/stub/src/main/res/values-bg/strings.xml similarity index 79% rename from shared/src/main/res/values-bg/strings.xml rename to stub/src/main/res/values-bg/strings.xml index 1421eb6b0..09255da5e 100644 --- a/shared/src/main/res/values-bg/strings.xml +++ b/stub/src/main/res/values-bg/strings.xml @@ -1,7 +1,4 @@ - Не, благодаря. - Да - OK Надградете до пълната версия на Magisk Manager, за да довършите първоначалната настройка. Изтегляне и инсталиране сега? Моля да се свържете към работеща интернет мрежа, защото надграждането до пълната версия на Magisk Manager е задължително. diff --git a/shared/src/main/res/values-ca/strings.xml b/stub/src/main/res/values-ca/strings.xml similarity index 65% rename from shared/src/main/res/values-ca/strings.xml rename to stub/src/main/res/values-ca/strings.xml index 04cb79758..23de839a9 100644 --- a/shared/src/main/res/values-ca/strings.xml +++ b/stub/src/main/res/values-ca/strings.xml @@ -1,9 +1,6 @@ - - - No, gràcies - - Ok + + Fes una actualització total de Magisk Manager per finalitzar l\'instalació. Descarregar i instalar? Si us plau, connecta\'t a internet! Es necessari fer una actualització total de Magisk Manager. - + Baixant diff --git a/stub/src/main/res/values-cs/strings.xml b/stub/src/main/res/values-cs/strings.xml new file mode 100644 index 000000000..7abc06d3b --- /dev/null +++ b/stub/src/main/res/values-cs/strings.xml @@ -0,0 +1 @@ + diff --git a/shared/src/main/res/values-de/strings.xml b/stub/src/main/res/values-de/strings.xml similarity index 73% rename from shared/src/main/res/values-de/strings.xml rename to stub/src/main/res/values-de/strings.xml index 426a03351..45488872f 100644 --- a/shared/src/main/res/values-de/strings.xml +++ b/stub/src/main/res/values-de/strings.xml @@ -1,7 +1,4 @@ - Nein danke - Ja - OK Upgrade zum vollständigen Magisk Manager, um das Setup abzuschließen. Herunterladen und installieren? Bitte eine Verbindung mit dem Internet herstellen! Upgrade zum vollständigen Magisk Manager ist erforderlich. diff --git a/stub/src/main/res/values-el/strings.xml b/stub/src/main/res/values-el/strings.xml new file mode 100644 index 000000000..7abc06d3b --- /dev/null +++ b/stub/src/main/res/values-el/strings.xml @@ -0,0 +1 @@ + diff --git a/shared/src/main/res/values-es/strings.xml b/stub/src/main/res/values-es/strings.xml similarity index 71% rename from shared/src/main/res/values-es/strings.xml rename to stub/src/main/res/values-es/strings.xml index 8904fdf32..5d1d2fd19 100644 --- a/shared/src/main/res/values-es/strings.xml +++ b/stub/src/main/res/values-es/strings.xml @@ -1,7 +1,4 @@ - No gracias - - Aceptar Actualizar a la Ver. Completa de Magisk Manager para finalizar la instalación. Descargar e instalar? ¡Por favor conectarse a Internet! Se requiere actualizar a la Ver. Completa de Magisk Manager diff --git a/shared/src/main/res/values-et/strings.xml b/stub/src/main/res/values-et/strings.xml similarity index 62% rename from shared/src/main/res/values-et/strings.xml rename to stub/src/main/res/values-et/strings.xml index 50288c12a..ec72c8a82 100644 --- a/shared/src/main/res/values-et/strings.xml +++ b/stub/src/main/res/values-et/strings.xml @@ -1,8 +1,4 @@ - - Tänan ei - Jah - OK Täienda seadistuse lõpetamiseks Magisk Manager\'i täisversioonile. Kas laadid alla ja installid? Palun ühendu Internetti! Nõutud on Magisk Manager\'i täisversioonile täiendamine. - \ No newline at end of file + diff --git a/shared/src/main/res/values-fr/strings.xml b/stub/src/main/res/values-fr/strings.xml similarity index 69% rename from shared/src/main/res/values-fr/strings.xml rename to stub/src/main/res/values-fr/strings.xml index d44357e3e..dc928c8e9 100644 --- a/shared/src/main/res/values-fr/strings.xml +++ b/stub/src/main/res/values-fr/strings.xml @@ -1,9 +1,5 @@ - - Non merci - Oui - OK Mettre à jour vers la version complête de Magisk Manager pour finir l\'installation. Télécharger et installer? Veuillez vous connecter à Internet! Une mise à niveau complête vers le Gestionnaire Magisk est requise. diff --git a/stub/src/main/res/values-hi/strings.xml b/stub/src/main/res/values-hi/strings.xml new file mode 100644 index 000000000..7328bb697 --- /dev/null +++ b/stub/src/main/res/values-hi/strings.xml @@ -0,0 +1,5 @@ + + + सेटअप को पूरा करने के लिए पूर्ण Magisk Manager में अपग्रेड करें. डाउनलोड करके स्थापित करें ? + कृपया इन्टरनेट से जुड़िये! पूर्ण Magisk Manager के उन्नयन की आवश्यकता है. + diff --git a/stub/src/main/res/values-hr/strings.xml b/stub/src/main/res/values-hr/strings.xml new file mode 100644 index 000000000..7abc06d3b --- /dev/null +++ b/stub/src/main/res/values-hr/strings.xml @@ -0,0 +1 @@ + diff --git a/shared/src/main/res/values-in/strings.xml b/stub/src/main/res/values-in/strings.xml similarity index 69% rename from shared/src/main/res/values-in/strings.xml rename to stub/src/main/res/values-in/strings.xml index 6bc1ef7f8..ec38f4ab0 100644 --- a/shared/src/main/res/values-in/strings.xml +++ b/stub/src/main/res/values-in/strings.xml @@ -1,7 +1,4 @@ - Tidak, terima kasih - Ya - OK Tingkatkan ke Magisk Manager versi penuh untuk menyelesaikan penyiapan. Unduh dan pasang? Harap menyambungkan ke Internet! Peningkatan ke Magisk Manager versi penuh diperlukan. diff --git a/shared/src/main/res/values-it/strings.xml b/stub/src/main/res/values-it/strings.xml similarity index 73% rename from shared/src/main/res/values-it/strings.xml rename to stub/src/main/res/values-it/strings.xml index 0c8c96c0e..81add81af 100644 --- a/shared/src/main/res/values-it/strings.xml +++ b/stub/src/main/res/values-it/strings.xml @@ -1,7 +1,4 @@ - No, grazie - - OK Aggiorna alla versione completa di Magisk Manager per completare l\'installazione. Vuoi procedere al download e all\'installazione? Controlla la connessione a Internet! È necessaria per l\'aggiornamento di Magisk Manager. diff --git a/stub/src/main/res/values-ja/strings.xml b/stub/src/main/res/values-ja/strings.xml new file mode 100644 index 000000000..7abc06d3b --- /dev/null +++ b/stub/src/main/res/values-ja/strings.xml @@ -0,0 +1 @@ + diff --git a/shared/src/main/res/values-ko/strings.xml b/stub/src/main/res/values-ko/strings.xml similarity index 70% rename from shared/src/main/res/values-ko/strings.xml rename to stub/src/main/res/values-ko/strings.xml index a47c15d35..5b79aa155 100644 --- a/shared/src/main/res/values-ko/strings.xml +++ b/stub/src/main/res/values-ko/strings.xml @@ -1,7 +1,4 @@ - 아니오, 괜찮습니다 - - 확인 완전한 Magisk Manager로 업데이트하여 설치를 마치십시오. 다운로드하고 설치하시겠습니까? 인터넷에 연결해 주시기 바랍니다! 완전한 Magisk Manager로 업데이트 해야 합니다. diff --git a/shared/src/main/res/values-lt/strings.xml b/stub/src/main/res/values-lt/strings.xml similarity index 71% rename from shared/src/main/res/values-lt/strings.xml rename to stub/src/main/res/values-lt/strings.xml index 2c5179804..db2eb98b0 100644 --- a/shared/src/main/res/values-lt/strings.xml +++ b/stub/src/main/res/values-lt/strings.xml @@ -1,7 +1,4 @@ - Ačiū, nereikia - Taip - Gerai Atsinaujinkite į pilną Magisk Manager versiją, kad baigtumėte pasiruošimą. Atsisiųsti ir instaliuoti? Prašome prisijungti prie interneto! Atsinaujinimas į pilną Magisk Manager versiją yra privalomas. diff --git a/shared/src/main/res/values-mk/strings.xml b/stub/src/main/res/values-mk/strings.xml similarity index 72% rename from shared/src/main/res/values-mk/strings.xml rename to stub/src/main/res/values-mk/strings.xml index ad7bece43..32664f06e 100644 --- a/shared/src/main/res/values-mk/strings.xml +++ b/stub/src/main/res/values-mk/strings.xml @@ -1,8 +1,4 @@ - - Не, благодарам - Да - ОК Надградете до целосната верзија на Magisk Manager за да го завршите поставувањето. Преземете и инсталирајте? Ве молиме поврзете се на интернет бидејќи е потребна надградба на целосната верзија на Magisk Manager. diff --git a/shared/src/main/res/values-nb/strings.xml b/stub/src/main/res/values-nb/strings.xml similarity index 70% rename from shared/src/main/res/values-nb/strings.xml rename to stub/src/main/res/values-nb/strings.xml index 322190c48..2d9139952 100644 --- a/shared/src/main/res/values-nb/strings.xml +++ b/stub/src/main/res/values-nb/strings.xml @@ -1,9 +1,5 @@ - - Nei takk - Ja - OK Oppgrader til den komplette versjonen av Magisk Manager for å fullføre oppsettet. Vil du laste ned og installere? Vennligst koble deg på internettet! Å oppgradere til den komplette versjonen av Magisk Manager er påkrevd. diff --git a/stub/src/main/res/values-nl/strings.xml b/stub/src/main/res/values-nl/strings.xml new file mode 100644 index 000000000..7abc06d3b --- /dev/null +++ b/stub/src/main/res/values-nl/strings.xml @@ -0,0 +1 @@ + diff --git a/shared/src/main/res/values-pl/strings.xml b/stub/src/main/res/values-pl/strings.xml similarity index 71% rename from shared/src/main/res/values-pl/strings.xml rename to stub/src/main/res/values-pl/strings.xml index d38b3c55a..c166e4125 100644 --- a/shared/src/main/res/values-pl/strings.xml +++ b/stub/src/main/res/values-pl/strings.xml @@ -1,7 +1,4 @@ - Nie dziękuję - Tak - OK Przejdź na pełną wersję programu Magisk Manager, aby ukończyć konfigurację. Ściągnąć i zainstalować? Połącz się z Internetem! Wymagana jest aktualizacja do pełnego programu Magisk Manager. diff --git a/stub/src/main/res/values-pt-rBR/strings.xml b/stub/src/main/res/values-pt-rBR/strings.xml new file mode 100644 index 000000000..7abc06d3b --- /dev/null +++ b/stub/src/main/res/values-pt-rBR/strings.xml @@ -0,0 +1 @@ + diff --git a/stub/src/main/res/values-pt-rPT/strings.xml b/stub/src/main/res/values-pt-rPT/strings.xml new file mode 100644 index 000000000..7abc06d3b --- /dev/null +++ b/stub/src/main/res/values-pt-rPT/strings.xml @@ -0,0 +1 @@ + diff --git a/shared/src/main/res/values-ro/strings.xml b/stub/src/main/res/values-ro/strings.xml similarity index 72% rename from shared/src/main/res/values-ro/strings.xml rename to stub/src/main/res/values-ro/strings.xml index 0e70bed77..c3f0469c9 100644 --- a/shared/src/main/res/values-ro/strings.xml +++ b/stub/src/main/res/values-ro/strings.xml @@ -1,7 +1,4 @@ - Nu, mulțumesc - Da - Ok Treci la versiunea completă Magisk Manager pentru a finaliza configurarea. Descarci și instalezi? Te rugăm să te conectezi la internet! Este necesară actualizarea la versiunea completă Magisk Manager. diff --git a/shared/src/main/res/values-ru/strings.xml b/stub/src/main/res/values-ru/strings.xml similarity index 75% rename from shared/src/main/res/values-ru/strings.xml rename to stub/src/main/res/values-ru/strings.xml index 4bb584099..71e5245ef 100644 --- a/shared/src/main/res/values-ru/strings.xml +++ b/stub/src/main/res/values-ru/strings.xml @@ -1,7 +1,5 @@ - Нет - Да - OK Обновите Magisk Manager для завершения установки. Загрузить и установить? Пожалуйста, подключитесь к интернету! Требуется обновление Magisk Manager. + Загрузка diff --git a/shared/src/main/res/values-sk/strings.xml b/stub/src/main/res/values-sk/strings.xml similarity index 61% rename from shared/src/main/res/values-sk/strings.xml rename to stub/src/main/res/values-sk/strings.xml index d93b29bde..d123d58b7 100644 --- a/shared/src/main/res/values-sk/strings.xml +++ b/stub/src/main/res/values-sk/strings.xml @@ -1,8 +1,4 @@ - - Nie, ďakujem - Áno - OK Pre dokončenie inštalácie sa vyžaduje upgrade Magisk Managera. Stiahnuť a nainštalovať? Pripojte sa na internet! Upgrade Magisk Managera je potrebný. diff --git a/stub/src/main/res/values-sr/strings.xml b/stub/src/main/res/values-sr/strings.xml new file mode 100644 index 000000000..7abc06d3b --- /dev/null +++ b/stub/src/main/res/values-sr/strings.xml @@ -0,0 +1 @@ + diff --git a/stub/src/main/res/values-sv/strings.xml b/stub/src/main/res/values-sv/strings.xml new file mode 100644 index 000000000..7abc06d3b --- /dev/null +++ b/stub/src/main/res/values-sv/strings.xml @@ -0,0 +1 @@ + diff --git a/stub/src/main/res/values-th/strings.xml b/stub/src/main/res/values-th/strings.xml new file mode 100644 index 000000000..3ea04e700 --- /dev/null +++ b/stub/src/main/res/values-th/strings.xml @@ -0,0 +1,2 @@ + + diff --git a/shared/src/main/res/values-tr/strings.xml b/stub/src/main/res/values-tr/strings.xml similarity index 66% rename from shared/src/main/res/values-tr/strings.xml rename to stub/src/main/res/values-tr/strings.xml index 24ae0185a..95b9bbe82 100644 --- a/shared/src/main/res/values-tr/strings.xml +++ b/stub/src/main/res/values-tr/strings.xml @@ -1,7 +1,4 @@ - Hayır, teşekkürler - Evet - Tamam Kurulumu tamamlamak için tam Magisk Manager\'a yükseltin. İndirip yüklensin mi? Lütfen internete bağlanın! Tam Magisk Manager\'a yükseltmek gerekiyor. diff --git a/shared/src/main/res/values-uk/strings.xml b/stub/src/main/res/values-uk/strings.xml similarity index 73% rename from shared/src/main/res/values-uk/strings.xml rename to stub/src/main/res/values-uk/strings.xml index 3d6f7ed57..9d6dfb76e 100644 --- a/shared/src/main/res/values-uk/strings.xml +++ b/stub/src/main/res/values-uk/strings.xml @@ -1,7 +1,6 @@ + - Ні, дякую - Так - OK Оновіть Magisk Manager для завершення встановлення. Завантажити і встановити? Будь ласка, підключіться до Інтернету! Потрібно оновити Magisk Manager. + Завантаження diff --git a/stub/src/main/res/values-vi/strings.xml b/stub/src/main/res/values-vi/strings.xml new file mode 100644 index 000000000..7abc06d3b --- /dev/null +++ b/stub/src/main/res/values-vi/strings.xml @@ -0,0 +1 @@ + diff --git a/shared/src/main/res/values-zh-rCN/strings.xml b/stub/src/main/res/values-zh-rCN/strings.xml similarity index 68% rename from shared/src/main/res/values-zh-rCN/strings.xml rename to stub/src/main/res/values-zh-rCN/strings.xml index 6a7ca6dd2..9c6d9763b 100644 --- a/shared/src/main/res/values-zh-rCN/strings.xml +++ b/stub/src/main/res/values-zh-rCN/strings.xml @@ -1,7 +1,5 @@ - 不,谢谢 - - 需要升级到完整的 Magisk Manager 来完成安装。 现在下载? 请连接到互联网! 这是升级到完整 Magisk Manager 所必需的。 + 正在下载 diff --git a/shared/src/main/res/values-zh-rTW/strings.xml b/stub/src/main/res/values-zh-rTW/strings.xml similarity index 66% rename from shared/src/main/res/values-zh-rTW/strings.xml rename to stub/src/main/res/values-zh-rTW/strings.xml index c330fec82..84bb1988f 100644 --- a/shared/src/main/res/values-zh-rTW/strings.xml +++ b/stub/src/main/res/values-zh-rTW/strings.xml @@ -1,7 +1,4 @@ - 不,謝謝 - - 需要升級到完整版 Magisk Manager。是否下載並安裝? 請連上網路!升級到完整版 Magisk Manager 是必須的。 diff --git a/stub/src/main/res/values/strings.xml b/stub/src/main/res/values/strings.xml new file mode 100644 index 000000000..3b6ec0284 --- /dev/null +++ b/stub/src/main/res/values/strings.xml @@ -0,0 +1,6 @@ + + + Upgrade to full Magisk Manager to finish the setup. Download and install? + Please connect to the Internet! Upgrading to full Magisk Manager is required. + Downloading +