diff --git a/README.MD b/README.MD index 83d542432..2a5be0cb5 100644 --- a/README.MD +++ b/README.MD @@ -33,7 +33,6 @@ Furthermore, Magisk provides a **Systemless Interface** to alter the system (or Default string resources for Magisk Manager are scattered throughout - `app/src/main/res/values/strings.xml` -- `stub/src/main/res/values/strings.xml` - `shared/src/main/res/values/strings.xml` Translate each and place them in the respective locations (`/src/main/res/values-/strings.xml`). diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 163ef7469..4d76d7753 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,4 +1,22 @@ + + + @@ -11,32 +29,37 @@ + tools:ignore="UnusedAttribute,GoogleAppIndexingWarning" + tools:replace="android:appComponentFactory"> - + - + + + + + + + + android:screenOrientation="nosensor" /> @@ -44,8 +67,7 @@ android:name="a.m" android:directBootAware="true" android:excludeFromRecents="true" - android:exported="false" - android:theme="@style/MagiskTheme.SU" /> + android:exported="false" /> @@ -64,9 +86,10 @@ - + - diff --git a/app/src/main/java/a/a.java b/app/src/main/java/a/a.java index ec5a4e698..5ccb6738b 100644 --- a/app/src/main/java/a/a.java +++ b/app/src/main/java/a/a.java @@ -1,13 +1,19 @@ package a; +import androidx.annotation.Keep; +import androidx.core.app.AppComponentFactory; + import com.topjohnwu.magisk.utils.PatchAPK; import com.topjohnwu.signing.BootSigner; -import androidx.annotation.Keep; - @Keep -public class a extends BootSigner { +public class a extends AppComponentFactory { + public static boolean patchAPK(String in, String out, String pkg) { return PatchAPK.patch(in, out, pkg); } + + public static void main(String[] args) throws Exception { + BootSigner.main(args); + } } diff --git a/app/src/main/java/a/w.java b/app/src/main/java/a/w.java index f852a8968..6fa3e50f6 100644 --- a/app/src/main/java/a/w.java +++ b/app/src/main/java/a/w.java @@ -7,6 +7,7 @@ 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; @@ -18,7 +19,7 @@ public abstract class w extends Worker { @SuppressWarnings("unchecked") w(@NonNull Context context, @NonNull WorkerParameters workerParams) { - super(context, workerParams); + super(ResourceMgrKt.wrap(context, false), 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 b742d5175..3a3f6961c 100644 --- a/app/src/main/java/com/topjohnwu/magisk/App.kt +++ b/app/src/main/java/com/topjohnwu/magisk/App.kt @@ -13,8 +13,11 @@ import com.topjohnwu.magisk.data.database.RepoDatabase_Impl import com.topjohnwu.magisk.di.ActivityTracker import com.topjohnwu.magisk.di.koinModules import com.topjohnwu.magisk.extensions.get -import com.topjohnwu.magisk.utils.LocaleManager -import com.topjohnwu.magisk.utils.RootUtils +import com.topjohnwu.magisk.extensions.unwrap +import com.topjohnwu.magisk.utils.ResourceMgr +import com.topjohnwu.magisk.utils.RootInit +import com.topjohnwu.magisk.utils.isRunningAsStub +import com.topjohnwu.magisk.utils.wrap import com.topjohnwu.superuser.Shell import org.koin.android.ext.koin.androidContext import org.koin.core.context.startKoin @@ -26,7 +29,7 @@ open class App : Application() { AppCompatDelegate.setCompatVectorFromResourcesEnabled(true) Shell.Config.setFlags(Shell.FLAG_MOUNT_MASTER or Shell.FLAG_USE_MAGISK_BUSYBOX) Shell.Config.verboseLogging(BuildConfig.DEBUG) - Shell.Config.addInitializers(RootUtils::class.java) + Shell.Config.addInitializers(RootInit::class.java) Shell.Config.setTimeout(2) Room.setFactory { when (it) { @@ -38,22 +41,42 @@ open class App : Application() { } override fun attachBaseContext(base: Context) { - super.attachBaseContext(base) + // Basic setup if (BuildConfig.DEBUG) MultiDex.install(base) Timber.plant(Timber.DebugTree()) + // Some context magic + val app: Application + val impl: Context + if (base is Application) { + isRunningAsStub = true + app = base + impl = base.baseContext + } else { + app = this + impl = base + } + ResourceMgr.init(impl) + super.attachBaseContext(impl.wrap()) + + // Normal startup startKoin { - androidContext(this@App) + androidContext(baseContext) modules(koinModules) } + ResourceMgr.reload() + app.registerActivityLifecycleCallbacks(get()) + } - registerActivityLifecycleCallbacks(get()) - LocaleManager.setLocale(this) + // This is required as some platforms expect ContextImpl + override fun getBaseContext(): Context { + return super.getBaseContext().unwrap() } override fun onConfigurationChanged(newConfig: Configuration) { - super.onConfigurationChanged(newConfig) - LocaleManager.setLocale(this) + ResourceMgr.reload(newConfig) + if (!isRunningAsStub) + super.onConfigurationChanged(newConfig) } } 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 04dad084f..0b8fed26f 100644 --- a/app/src/main/java/com/topjohnwu/magisk/base/BaseActivity.kt +++ b/app/src/main/java/com/topjohnwu/magisk/base/BaseActivity.kt @@ -15,12 +15,13 @@ import androidx.databinding.DataBindingUtil import androidx.databinding.ViewDataBinding import com.topjohnwu.magisk.BR import com.topjohnwu.magisk.Config +import com.topjohnwu.magisk.R import com.topjohnwu.magisk.base.viewmodel.BaseViewModel import com.topjohnwu.magisk.extensions.set import com.topjohnwu.magisk.model.events.EventHandler import com.topjohnwu.magisk.model.permissions.PermissionRequestBuilder -import com.topjohnwu.magisk.utils.LocaleManager import com.topjohnwu.magisk.utils.currentLocale +import com.topjohnwu.magisk.utils.wrap import kotlin.random.Random typealias RequestCallback = BaseActivity<*, *>.(Int, Intent?) -> Unit @@ -31,9 +32,8 @@ abstract class BaseActivity() } @@ -53,10 +53,11 @@ abstract class BaseActivity().packageName @@ -93,6 +97,105 @@ fun Context.readUri(uri: Uri) = fun Intent.startActivity(context: Context) = context.startActivity(this) +fun Intent.toCommand(args: MutableList) { + if (action != null) { + args.add("-a") + args.add(action!!) + } + if (component != null) { + args.add("-n") + args.add(component!!.flattenToString()) + } + if (data != null) { + args.add("-d") + args.add(dataString!!) + } + if (categories != null) { + for (cat in categories) { + args.add("-c") + args.add(cat) + } + } + if (type != null) { + args.add("-t") + args.add(type!!) + } + val extras = extras + if (extras != null) { + loop@ for (key in extras.keySet()) { + val v = extras.get(key) ?: continue + var value: Any = v + val arg: String + when { + v is String -> arg = "--es" + v is Boolean -> arg = "--ez" + v is Int -> arg = "--ei" + v is Long -> arg = "--el" + v is Float -> arg = "--ef" + v is Uri -> arg = "--eu" + v is ComponentName -> { + arg = "--ecn" + value = v.flattenToString() + } + v is ArrayList<*> -> { + if (v.size <= 0) + /* Impossible to know the type due to type erasure */ + continue@loop + + arg = if (v[0] is Int) + "--eial" + else if (v[0] is Long) + "--elal" + else if (v[0] is Float) + "--efal" + else if (v[0] is String) + "--esal" + else + continue@loop /* Unsupported */ + + val sb = StringBuilder() + for (o in v) { + sb.append(o.toString().replace(",", "\\,")) + sb.append(',') + } + // Remove trailing comma + sb.deleteCharAt(sb.length - 1) + value = sb + } + v.javaClass.isArray -> { + arg = if (v is IntArray) + "--eia" + else if (v is LongArray) + "--ela" + else if (v is FloatArray) + "--efa" + else if (v is Array<*> && v.isArrayOf()) + "--esa" + else + continue@loop /* Unsupported */ + + val sb = StringBuilder() + val len = java.lang.reflect.Array.getLength(v) + for (i in 0 until len) { + sb.append(java.lang.reflect.Array.get(v, i)!!.toString().replace(",", "\\,")) + sb.append(',') + } + // Remove trailing comma + sb.deleteCharAt(sb.length - 1) + value = sb + } + else -> continue@loop + } /* Unsupported */ + + args.add(arg) + args.add(key) + args.add(value.toString()) + } + } + args.add("-f") + args.add(flags.toString()) +} + fun File.provide(context: Context = get()): Uri { return FileProvider.getUriForFile(context, context.packageName + ".provider", this) } @@ -157,3 +260,18 @@ fun Context.startEndToLeftRight(start: Int, end: Int): Pair { } fun Context.openUrl(url: String) = Utils.openLink(this, url.toUri()) + +@Suppress("FunctionName") +inline fun T.DynamicClassLoader(apk: File) + = DynamicClassLoader(apk, T::class.java.classLoader) + +fun Context.unwrap() : Context { + var context = this + while (true) { + if (context is ContextWrapper) + context = context.baseContext + else + break + } + return context +} 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 4fdf3b9fe..278edafb0 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 @@ -5,13 +5,13 @@ import com.topjohnwu.magisk.BuildConfig import com.topjohnwu.magisk.ClassMap import com.topjohnwu.magisk.Config import com.topjohnwu.magisk.R +import com.topjohnwu.magisk.extensions.DynamicClassLoader import com.topjohnwu.magisk.model.entity.internal.Configuration.APK.Restore import com.topjohnwu.magisk.model.entity.internal.Configuration.APK.Upgrade import com.topjohnwu.magisk.model.entity.internal.DownloadSubject import com.topjohnwu.magisk.ui.SplashActivity -import com.topjohnwu.magisk.utils.DynamicClassLoader import com.topjohnwu.magisk.utils.PatchAPK -import com.topjohnwu.magisk.utils.RootUtils +import com.topjohnwu.magisk.utils.Utils import com.topjohnwu.superuser.Shell import timber.log.Timber import java.io.File @@ -54,7 +54,7 @@ private fun RemoteFileService.restore(apk: File, id: Int) { if (Shell.su("pm install $apk").exec().isSuccess) { val component = ComponentName(BuildConfig.APPLICATION_ID, ClassMap.get>(SplashActivity::class.java).name) - RootUtils.rmAndLaunch(packageName, component) + Utils.rmAndLaunch(packageName, component) } } diff --git a/app/src/main/java/com/topjohnwu/magisk/model/download/NotificationService.kt b/app/src/main/java/com/topjohnwu/magisk/model/download/NotificationService.kt index 716869fc3..f558265e3 100644 --- a/app/src/main/java/com/topjohnwu/magisk/model/download/NotificationService.kt +++ b/app/src/main/java/com/topjohnwu/magisk/model/download/NotificationService.kt @@ -1,16 +1,16 @@ package com.topjohnwu.magisk.model.download import android.app.Notification -import android.app.Service import android.content.Intent import android.os.IBinder import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat +import com.topjohnwu.magisk.base.BaseService import org.koin.core.KoinComponent import java.util.* import kotlin.random.Random.Default.nextInt -abstract class NotificationService : Service(), KoinComponent { +abstract class NotificationService : BaseService(), KoinComponent { abstract val defaultNotification: NotificationCompat.Builder 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 93e277780..587712370 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 @@ -1,15 +1,14 @@ package com.topjohnwu.magisk.model.receiver -import android.content.BroadcastReceiver -import android.content.Context +import android.content.ContextWrapper import android.content.Intent import com.topjohnwu.magisk.ClassMap import com.topjohnwu.magisk.Config import com.topjohnwu.magisk.Const import com.topjohnwu.magisk.Info +import com.topjohnwu.magisk.base.BaseReceiver import com.topjohnwu.magisk.data.database.PolicyDao import com.topjohnwu.magisk.data.database.base.su -import com.topjohnwu.magisk.extensions.inject import com.topjohnwu.magisk.extensions.reboot import com.topjohnwu.magisk.model.download.DownloadService import com.topjohnwu.magisk.model.entity.ManagerJson @@ -20,8 +19,9 @@ import com.topjohnwu.magisk.utils.SuLogger import com.topjohnwu.magisk.view.Notifications import com.topjohnwu.magisk.view.Shortcuts import com.topjohnwu.superuser.Shell +import org.koin.core.inject -open class GeneralReceiver : BroadcastReceiver() { +open class GeneralReceiver : BaseReceiver() { private val policyDB: PolicyDao by inject() @@ -36,7 +36,7 @@ open class GeneralReceiver : BroadcastReceiver() { return intent.data?.encodedSchemeSpecificPart.orEmpty() } - override fun onReceive(context: Context, intent: Intent?) { + override fun onReceive(context: ContextWrapper, intent: Intent?) { intent ?: return when (intent.action ?: return) { Intent.ACTION_REBOOT, Intent.ACTION_BOOT_COMPLETED -> { 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 8810447af..848192ca6 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/MainActivity.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ui/MainActivity.kt @@ -40,8 +40,8 @@ open class MainActivity : BaseActivity(), Na override val layoutRes: Int = R.layout.activity_main override val viewModel: MainViewModel by viewModel() - override val navHostId: Int = R.id.main_nav_host - override val defaultPosition: Int = 0 + private val navHostId: Int = R.id.main_nav_host + private val defaultPosition: Int = 0 private val navigationController by lazy { FragNavController(supportFragmentManager, navHostId) 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 7021b62ba..728720b1b 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/SplashActivity.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ui/SplashActivity.kt @@ -1,17 +1,23 @@ package com.topjohnwu.magisk.ui +import android.app.Activity +import android.content.Context import android.content.Intent import android.os.Bundle import android.text.TextUtils import androidx.appcompat.app.AlertDialog -import androidx.appcompat.app.AppCompatActivity import com.topjohnwu.magisk.* import com.topjohnwu.magisk.utils.Utils +import com.topjohnwu.magisk.utils.wrap import com.topjohnwu.magisk.view.Notifications import com.topjohnwu.magisk.view.Shortcuts import com.topjohnwu.superuser.Shell -open class SplashActivity : AppCompatActivity() { +open class SplashActivity : Activity() { + + override fun attachBaseContext(base: Context) { + super.attachBaseContext(base.wrap()) + } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) 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 2f46401a4..143db2a3e 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 @@ -23,6 +23,7 @@ import java.io.File open class FlashActivity : BaseActivity() { override val layoutRes: Int = R.layout.activity_flash + override val themeRes: Int = R.style.MagiskTheme_Flashing override val viewModel: FlashViewModel by viewModel { val uri = intent.data ?: let { finish(); Uri.EMPTY } val additionalUri = intent.getParcelableExtra(Const.Key.FLASH_DATA) ?: uri 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 2cee4cbd8..2d2b2161c 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 @@ -9,11 +9,11 @@ import com.topjohnwu.magisk.base.BaseActivity import com.topjohnwu.magisk.base.BaseFragment import com.topjohnwu.magisk.data.repository.MagiskRepository import com.topjohnwu.magisk.databinding.FragmentMagiskBinding +import com.topjohnwu.magisk.extensions.DynamicClassLoader import com.topjohnwu.magisk.extensions.openUrl import com.topjohnwu.magisk.extensions.subscribeK import com.topjohnwu.magisk.extensions.writeTo import com.topjohnwu.magisk.model.events.* -import com.topjohnwu.magisk.utils.DynamicClassLoader import com.topjohnwu.magisk.utils.SafetyNetHelper import com.topjohnwu.magisk.view.MarkDownWindow import com.topjohnwu.magisk.view.dialogs.* 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 8f4d2fced..a3457ddd0 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 @@ -201,7 +201,7 @@ class SettingsFragment : BasePreferenceFragment() { Shell.su("magiskhide --disable").submit() } Config.Key.LOCALE -> { - LocaleManager.setLocale(activity.application) + ResourceMgr.reload() activity.recreate() } Config.Key.CHECK_UPDATES -> Utils.scheduleUpdateCheck(activity) @@ -230,7 +230,7 @@ class SettingsFragment : BasePreferenceFragment() { val values = mutableListOf() names.add( - LocaleManager.getString(defaultLocale, R.string.system_default) + ResourceMgr.getString(defaultLocale, R.string.system_default) ) values.add("") 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 2fb93eeea..b55e108c2 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 @@ -18,6 +18,7 @@ import org.koin.androidx.viewmodel.ext.android.viewModel open class SuRequestActivity : BaseActivity() { override val layoutRes: Int = R.layout.activity_request + override val themeRes: Int = R.style.MagiskTheme_SU override val viewModel: SuRequestViewModel by viewModel() override fun onBackPressed() { diff --git a/app/src/main/java/com/topjohnwu/magisk/utils/DynamicClassLoader.kt b/app/src/main/java/com/topjohnwu/magisk/utils/DynamicClassLoader.kt deleted file mode 100644 index 9810c91bd..000000000 --- a/app/src/main/java/com/topjohnwu/magisk/utils/DynamicClassLoader.kt +++ /dev/null @@ -1,61 +0,0 @@ -package com.topjohnwu.magisk.utils - -import dalvik.system.DexClassLoader -import java.io.File -import java.io.IOException -import java.net.URL -import java.util.* - -@Suppress("FunctionName") -inline fun T.DynamicClassLoader(apk: File) = DynamicClassLoader(apk, T::class.java.classLoader) - -class DynamicClassLoader(apk: File, parent: ClassLoader?) - : DexClassLoader(apk.path, apk.parent, null, parent) { - - private val base by lazy { Any::class.java.classLoader!! } - - @Throws(ClassNotFoundException::class) - override fun loadClass(name: String, resolve: Boolean) : Class<*> - = findLoadedClass(name) ?: runCatching { - base.loadClass(name) - }.getOrElse { - runCatching { - findClass(name) - }.getOrElse { err -> - runCatching { - parent.loadClass(name) - }.getOrElse { throw err } - } - } - - override fun getResource(name: String) = base.getResource(name) - ?: findResource(name) - ?: parent?.getResource(name) - - @Throws(IOException::class) - override fun getResources(name: String): Enumeration { - val resources = mutableListOf( - base.getResources(name), - findResources(name), parent.getResources(name)) - return object : Enumeration { - override fun hasMoreElements(): Boolean { - while (true) { - if (resources.isEmpty()) - return false - if (!resources[0].hasMoreElements()) { - resources.removeAt(0) - } else { - return true - } - } - } - - override fun nextElement(): URL { - if (!hasMoreElements()) - throw NoSuchElementException() - return resources[0].nextElement() - } - } - } - -} diff --git a/app/src/main/java/com/topjohnwu/magisk/utils/LocaleManager.kt b/app/src/main/java/com/topjohnwu/magisk/utils/LocaleManager.kt deleted file mode 100644 index e89241b0d..000000000 --- a/app/src/main/java/com/topjohnwu/magisk/utils/LocaleManager.kt +++ /dev/null @@ -1,68 +0,0 @@ -package com.topjohnwu.magisk.utils - -import android.content.Context -import android.content.ContextWrapper -import android.content.res.Configuration -import android.content.res.Resources -import androidx.annotation.StringRes -import com.topjohnwu.magisk.Config -import com.topjohnwu.magisk.R -import com.topjohnwu.magisk.extensions.get -import com.topjohnwu.magisk.extensions.inject -import com.topjohnwu.magisk.extensions.langTagToLocale -import com.topjohnwu.superuser.internal.InternalUtils -import io.reactivex.Single -import java.util.* - -var currentLocale = Locale.getDefault()!! - private set - -val defaultLocale = Locale.getDefault()!! - -val availableLocales = Single.fromCallable { - val compareId = R.string.app_changelog - val res: Resources by inject() - mutableListOf().apply { - // Add default locale - add(Locale.ENGLISH) - - // Add some special locales - add(Locale.TAIWAN) - add(Locale("pt", "BR")) - - // Other locales - val otherLocales = res.assets.locales - .map { it.langTagToLocale() } - .distinctBy { LocaleManager.getString(it, compareId) } - - listOf("", "").toTypedArray() - - addAll(otherLocales) - }.sortedWith(Comparator { a, b -> - a.getDisplayName(a).toLowerCase(a) - .compareTo(b.getDisplayName(b).toLowerCase(b)) - }) -}.cache()!! - -object LocaleManager { - - fun setLocale(wrapper: ContextWrapper) { - val localeConfig = Config.locale - currentLocale = when { - localeConfig.isEmpty() -> defaultLocale - else -> localeConfig.langTagToLocale() - } - Locale.setDefault(currentLocale) - InternalUtils.replaceBaseContext(wrapper, getLocaleContext(wrapper, currentLocale)) - } - - fun getLocaleContext(context: Context, locale: Locale = currentLocale): Context { - val config = Configuration(context.resources.configuration) - config.setLocale(locale) - return context.createConfigurationContext(config) - } - - fun getString(locale: Locale, @StringRes id: Int): String { - return getLocaleContext(get(), locale).getString(id) - } -} 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 5f45dc8a7..1ed022ead 100644 --- a/app/src/main/java/com/topjohnwu/magisk/utils/PatchAPK.kt +++ b/app/src/main/java/com/topjohnwu/magisk/utils/PatchAPK.kt @@ -102,7 +102,7 @@ object PatchAPK { Config.suManager = pkg Config.export() - RootUtils.rmAndLaunch(BuildConfig.APPLICATION_ID, + Utils.rmAndLaunch(BuildConfig.APPLICATION_ID, ComponentName(pkg, ClassMap.get>(SplashActivity::class.java).name)) return true diff --git a/app/src/main/java/com/topjohnwu/magisk/utils/ResourceMgr.kt b/app/src/main/java/com/topjohnwu/magisk/utils/ResourceMgr.kt new file mode 100644 index 000000000..fb3d7d503 --- /dev/null +++ b/app/src/main/java/com/topjohnwu/magisk/utils/ResourceMgr.kt @@ -0,0 +1,126 @@ +@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 new file mode 100644 index 000000000..c4bbfde4e --- /dev/null +++ b/app/src/main/java/com/topjohnwu/magisk/utils/RootInit.kt @@ -0,0 +1,40 @@ +package com.topjohnwu.magisk.utils + +import android.content.Context +import com.topjohnwu.magisk.Const +import com.topjohnwu.magisk.Info +import com.topjohnwu.magisk.R +import com.topjohnwu.magisk.extensions.rawResource +import com.topjohnwu.superuser.Shell +import com.topjohnwu.superuser.ShellUtils +import com.topjohnwu.superuser.io.SuFile + +class RootInit : Shell.Initializer() { + + override fun onInit(context: Context, shell: Shell): Boolean { + return init(context.wrap(), shell) + } + + fun init(context: Context, shell: Shell): Boolean { + val job = shell.newJob() + if (shell.isRoot) { + job.add(context.rawResource(R.raw.util_functions)) + .add(context.rawResource(R.raw.utils)) + Const.MAGISK_DISABLE_FILE = SuFile("/cache/.disable_magisk") + Info.loadMagiskInfo() + } else { + job.add(context.rawResource(R.raw.nonroot_utils)) + } + + job.add("mount_partitions", + "get_flags", + "run_migrations", + "export BOOTMODE=true") + .exec() + + Info.keepVerity = ShellUtils.fastCmd("echo \$KEEPVERITY").toBoolean() + Info.keepEnc = ShellUtils.fastCmd("echo \$KEEPFORCEENCRYPT").toBoolean() + Info.recovery = ShellUtils.fastCmd("echo \$RECOVERYMODE").toBoolean() + return true + } +} diff --git a/app/src/main/java/com/topjohnwu/magisk/utils/RootUtils.kt b/app/src/main/java/com/topjohnwu/magisk/utils/RootUtils.kt deleted file mode 100644 index 11bd46695..000000000 --- a/app/src/main/java/com/topjohnwu/magisk/utils/RootUtils.kt +++ /dev/null @@ -1,158 +0,0 @@ -package com.topjohnwu.magisk.utils - -import android.content.ComponentName -import android.content.Context -import android.content.Intent -import android.net.Uri -import com.topjohnwu.magisk.Const -import com.topjohnwu.magisk.Info -import com.topjohnwu.magisk.R -import com.topjohnwu.magisk.extensions.rawResource -import com.topjohnwu.magisk.extensions.toShellCmd -import com.topjohnwu.superuser.Shell -import com.topjohnwu.superuser.ShellUtils -import com.topjohnwu.superuser.io.SuFile -import java.util.* -import java.lang.reflect.Array as RArray - -fun Intent.toCommand(args: MutableList) { - if (action != null) { - args.add("-a") - args.add(action!!) - } - if (component != null) { - args.add("-n") - args.add(component!!.flattenToString()) - } - if (data != null) { - args.add("-d") - args.add(dataString!!) - } - if (categories != null) { - for (cat in categories) { - args.add("-c") - args.add(cat) - } - } - if (type != null) { - args.add("-t") - args.add(type!!) - } - val extras = extras - if (extras != null) { - loop@ for (key in extras.keySet()) { - val v = extras.get(key) ?: continue - var value: Any = v - val arg: String - when { - v is String -> arg = "--es" - v is Boolean -> arg = "--ez" - v is Int -> arg = "--ei" - v is Long -> arg = "--el" - v is Float -> arg = "--ef" - v is Uri -> arg = "--eu" - v is ComponentName -> { - arg = "--ecn" - value = v.flattenToString() - } - v is ArrayList<*> -> { - if (v.size <= 0) - /* Impossible to know the type due to type erasure */ - continue@loop - - arg = if (v[0] is Int) - "--eial" - else if (v[0] is Long) - "--elal" - else if (v[0] is Float) - "--efal" - else if (v[0] is String) - "--esal" - else - continue@loop /* Unsupported */ - - val sb = StringBuilder() - for (o in v) { - sb.append(o.toString().replace(",", "\\,")) - sb.append(',') - } - // Remove trailing comma - sb.deleteCharAt(sb.length - 1) - value = sb - } - v.javaClass.isArray -> { - arg = if (v is IntArray) - "--eia" - else if (v is LongArray) - "--ela" - else if (v is FloatArray) - "--efa" - else if (v is Array<*> && v.isArrayOf()) - "--esa" - else - continue@loop /* Unsupported */ - - val sb = StringBuilder() - val len = RArray.getLength(v) - for (i in 0 until len) { - sb.append(RArray.get(v, i)!!.toString().replace(",", "\\,")) - sb.append(',') - } - // Remove trailing comma - sb.deleteCharAt(sb.length - 1) - value = sb - } - else -> continue@loop - } /* Unsupported */ - - args.add(arg) - args.add(key) - args.add(value.toString()) - } - } - args.add("-f") - args.add(flags.toString()) -} - -fun startActivity(intent: Intent) { - if (intent.component == null) - return - val args = ArrayList() - args.add("am") - args.add("start") - intent.toCommand(args) - Shell.su(args.toShellCmd()).exec() -} - -class RootUtils : Shell.Initializer() { - - override fun onInit(context: Context, shell: Shell): Boolean { - val job = shell.newJob() - if (shell.isRoot) { - job.add(context.rawResource(R.raw.util_functions)) - .add(context.rawResource(R.raw.utils)) - Const.MAGISK_DISABLE_FILE = SuFile("/cache/.disable_magisk") - Info.loadMagiskInfo() - } else { - job.add(context.rawResource(R.raw.nonroot_utils)) - } - - job.add("mount_partitions", - "get_flags", - "run_migrations", - "export BOOTMODE=true") - .exec() - - Info.keepVerity = ShellUtils.fastCmd("echo \$KEEPVERITY").toBoolean() - Info.keepEnc = ShellUtils.fastCmd("echo \$KEEPFORCEENCRYPT").toBoolean() - Info.recovery = ShellUtils.fastCmd("echo \$RECOVERYMODE").toBoolean() - return true - } - - companion object { - - fun rmAndLaunch(rm: String, component: ComponentName) { - Shell.su("(rm_launch $rm ${component.flattenToString()})").exec() - } - } -} 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 bf3758fe6..4e873aa8a 100644 --- a/app/src/main/java/com/topjohnwu/magisk/utils/Utils.kt +++ b/app/src/main/java/com/topjohnwu/magisk/utils/Utils.kt @@ -1,5 +1,6 @@ package com.topjohnwu.magisk.utils +import android.content.ComponentName import android.content.Context import android.content.Intent import android.content.res.Resources @@ -72,4 +73,8 @@ object Utils { if ((exists() && isDirectory) || mkdirs()) this else null } + fun rmAndLaunch(rm: String, component: ComponentName) { + Shell.su("(rm_launch $rm ${component.flattenToString()})").exec() + } + } diff --git a/app/src/main/res/values-v19/styles.xml b/app/src/main/res/values-v19/styles.xml index 785a6291e..2a7f9b3e4 100644 --- a/app/src/main/res/values-v19/styles.xml +++ b/app/src/main/res/values-v19/styles.xml @@ -1,9 +1,4 @@ - - + - + + + + +