mirror of
https://github.com/topjohnwu/Magisk.git
synced 2024-12-22 16:07:39 +00:00
Obfuscate WorkManager components
Remove unused components and hack the context sent into WorkManager
This commit is contained in:
parent
a910c8ccd8
commit
9f9de8c43b
@ -97,6 +97,12 @@
|
|||||||
android:name="com.google.android.gms.version"
|
android:name="com.google.android.gms.version"
|
||||||
android:value="12451000" />
|
android:value="12451000" />
|
||||||
|
|
||||||
|
<!-- Initialize WorkManager on-demand -->
|
||||||
|
<provider
|
||||||
|
android:name="androidx.work.impl.WorkManagerInitializer"
|
||||||
|
android:authorities="${applicationId}.workmanager-init"
|
||||||
|
tools:node="remove" />
|
||||||
|
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
@ -6,8 +6,8 @@ import androidx.annotation.NonNull;
|
|||||||
import androidx.work.Worker;
|
import androidx.work.Worker;
|
||||||
import androidx.work.WorkerParameters;
|
import androidx.work.WorkerParameters;
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.HacksKt;
|
||||||
import com.topjohnwu.magisk.base.DelegateWorker;
|
import com.topjohnwu.magisk.base.DelegateWorker;
|
||||||
import com.topjohnwu.magisk.utils.ResourceMgrKt;
|
|
||||||
|
|
||||||
import java.lang.reflect.ParameterizedType;
|
import java.lang.reflect.ParameterizedType;
|
||||||
|
|
||||||
@ -19,7 +19,7 @@ public abstract class w<T extends DelegateWorker> extends Worker {
|
|||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
w(@NonNull Context context, @NonNull WorkerParameters workerParams) {
|
w(@NonNull Context context, @NonNull WorkerParameters workerParams) {
|
||||||
super(ResourceMgrKt.wrap(context, false), workerParams);
|
super(HacksKt.wrap(context, false), workerParams);
|
||||||
try {
|
try {
|
||||||
base = ((Class<T>) ((ParameterizedType) getClass().getGenericSuperclass())
|
base = ((Class<T>) ((ParameterizedType) getClass().getGenericSuperclass())
|
||||||
.getActualTypeArguments()[0]).newInstance();
|
.getActualTypeArguments()[0]).newInstance();
|
||||||
|
@ -6,6 +6,7 @@ import android.content.res.Configuration
|
|||||||
import androidx.appcompat.app.AppCompatDelegate
|
import androidx.appcompat.app.AppCompatDelegate
|
||||||
import androidx.multidex.MultiDex
|
import androidx.multidex.MultiDex
|
||||||
import androidx.room.Room
|
import androidx.room.Room
|
||||||
|
import androidx.work.WorkManager
|
||||||
import androidx.work.impl.WorkDatabase
|
import androidx.work.impl.WorkDatabase
|
||||||
import androidx.work.impl.WorkDatabase_Impl
|
import androidx.work.impl.WorkDatabase_Impl
|
||||||
import com.topjohnwu.magisk.data.database.RepoDatabase
|
import com.topjohnwu.magisk.data.database.RepoDatabase
|
||||||
@ -14,10 +15,7 @@ import com.topjohnwu.magisk.di.ActivityTracker
|
|||||||
import com.topjohnwu.magisk.di.koinModules
|
import com.topjohnwu.magisk.di.koinModules
|
||||||
import com.topjohnwu.magisk.extensions.get
|
import com.topjohnwu.magisk.extensions.get
|
||||||
import com.topjohnwu.magisk.extensions.unwrap
|
import com.topjohnwu.magisk.extensions.unwrap
|
||||||
import com.topjohnwu.magisk.utils.ResourceMgr
|
|
||||||
import com.topjohnwu.magisk.utils.RootInit
|
import com.topjohnwu.magisk.utils.RootInit
|
||||||
import com.topjohnwu.magisk.utils.isRunningAsStub
|
|
||||||
import com.topjohnwu.magisk.utils.wrap
|
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
import org.koin.android.ext.koin.androidContext
|
import org.koin.android.ext.koin.androidContext
|
||||||
import org.koin.core.context.startKoin
|
import org.koin.core.context.startKoin
|
||||||
@ -72,6 +70,7 @@ open class App() : Application() {
|
|||||||
}
|
}
|
||||||
ResourceMgr.reload()
|
ResourceMgr.reload()
|
||||||
app.registerActivityLifecycleCallbacks(get<ActivityTracker>())
|
app.registerActivityLifecycleCallbacks(get<ActivityTracker>())
|
||||||
|
WorkManager.initialize(impl.wrapJob(), androidx.work.Configuration.Builder().build())
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is required as some platforms expect ContextImpl
|
// This is required as some platforms expect ContextImpl
|
||||||
|
@ -1,44 +0,0 @@
|
|||||||
package com.topjohnwu.magisk
|
|
||||||
|
|
||||||
import android.content.ComponentName
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
|
||||||
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
|
|
||||||
|
|
||||||
object ClassMap {
|
|
||||||
|
|
||||||
private val classMap = 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
|
|
||||||
)
|
|
||||||
|
|
||||||
// This will be set if running as guest app
|
|
||||||
var componentMap: Map<String, String>? = null
|
|
||||||
|
|
||||||
operator fun get(c: Class<*>) = classMap.getOrElse(c) { throw IllegalArgumentException() }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Class<*>.cmp(pkg: String = BuildConfig.APPLICATION_ID): ComponentName {
|
|
||||||
val name = ClassMap[this].name
|
|
||||||
return ComponentName(pkg, ClassMap.componentMap?.get(name) ?: name)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Context.intent(c: Class<*>): Intent {
|
|
||||||
val cls = ClassMap[c]
|
|
||||||
return ClassMap.componentMap?.let {
|
|
||||||
val className = it.getOrElse(cls.name) { cls.name }
|
|
||||||
Intent().setComponent(ComponentName(this, className))
|
|
||||||
} ?: Intent(this, cls)
|
|
||||||
}
|
|
223
app/src/main/java/com/topjohnwu/magisk/Hacks.kt
Normal file
223
app/src/main/java/com/topjohnwu/magisk/Hacks.kt
Normal file
@ -0,0 +1,223 @@
|
|||||||
|
@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.DynAPK
|
||||||
|
import com.topjohnwu.magisk.utils.currentLocale
|
||||||
|
import com.topjohnwu.magisk.utils.defaultLocale
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
var isRunningAsStub = false
|
||||||
|
|
||||||
|
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) ResContext(this) else GlobalResContext(this)
|
||||||
|
|
||||||
|
fun Context.wrapJob(): Context = object : GlobalResContext(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, ClassMap.componentMap?.get(name)
|
||||||
|
?: name)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Context.intent(c: Class<*>): Intent {
|
||||||
|
val cls = ClassMap[c]
|
||||||
|
return ClassMap.componentMap?.let {
|
||||||
|
val className = it.getOrElse(cls.name) { cls.name }
|
||||||
|
Intent().setComponent(ComponentName(this, className))
|
||||||
|
} ?: Intent(this, cls)
|
||||||
|
}
|
||||||
|
|
||||||
|
private open class GlobalResContext(base: Context) : ContextWrapper(base) {
|
||||||
|
open val mRes: Resources get() = ResourceMgr.resource
|
||||||
|
private val loader by lazy { javaClass.classLoader!! }
|
||||||
|
|
||||||
|
override fun getApplicationContext(): Context {
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
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<JobInfo> {
|
||||||
|
return base.allPendingJobs
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getPendingJob(jobId: Int): JobInfo? {
|
||||||
|
return base.getPendingJob(jobId)
|
||||||
|
}
|
||||||
|
|
||||||
|
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, ClassMap.componentMap?.get(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 classMap = 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
|
||||||
|
)
|
||||||
|
|
||||||
|
// This will be set if running as guest app
|
||||||
|
var componentMap: Map<String, String>? = null
|
||||||
|
|
||||||
|
operator fun get(c: Class<*>) = classMap.getOrElse(c) { throw IllegalArgumentException() }
|
||||||
|
}
|
@ -21,7 +21,7 @@ import com.topjohnwu.magisk.extensions.set
|
|||||||
import com.topjohnwu.magisk.model.events.EventHandler
|
import com.topjohnwu.magisk.model.events.EventHandler
|
||||||
import com.topjohnwu.magisk.model.permissions.PermissionRequestBuilder
|
import com.topjohnwu.magisk.model.permissions.PermissionRequestBuilder
|
||||||
import com.topjohnwu.magisk.utils.currentLocale
|
import com.topjohnwu.magisk.utils.currentLocale
|
||||||
import com.topjohnwu.magisk.utils.wrap
|
import com.topjohnwu.magisk.wrap
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
|
||||||
typealias RequestCallback = BaseActivity<*, *>.(Int, Intent?) -> Unit
|
typealias RequestCallback = BaseActivity<*, *>.(Int, Intent?) -> Unit
|
||||||
|
@ -4,7 +4,7 @@ import android.content.BroadcastReceiver
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.ContextWrapper
|
import android.content.ContextWrapper
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import com.topjohnwu.magisk.utils.wrap
|
import com.topjohnwu.magisk.wrap
|
||||||
import org.koin.core.KoinComponent
|
import org.koin.core.KoinComponent
|
||||||
|
|
||||||
abstract class BaseReceiver : BroadcastReceiver(), KoinComponent {
|
abstract class BaseReceiver : BroadcastReceiver(), KoinComponent {
|
||||||
|
@ -2,7 +2,7 @@ package com.topjohnwu.magisk.base
|
|||||||
|
|
||||||
import android.app.Service
|
import android.app.Service
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import com.topjohnwu.magisk.utils.wrap
|
import com.topjohnwu.magisk.wrap
|
||||||
import org.koin.core.KoinComponent
|
import org.koin.core.KoinComponent
|
||||||
|
|
||||||
abstract class BaseService : Service(), KoinComponent {
|
abstract class BaseService : Service(), KoinComponent {
|
||||||
|
@ -20,7 +20,7 @@ import com.topjohnwu.magisk.model.entity.internal.DownloadSubject.*
|
|||||||
import com.topjohnwu.magisk.ui.flash.FlashActivity
|
import com.topjohnwu.magisk.ui.flash.FlashActivity
|
||||||
import com.topjohnwu.magisk.utils.APKInstall
|
import com.topjohnwu.magisk.utils.APKInstall
|
||||||
import com.topjohnwu.magisk.utils.DynAPK
|
import com.topjohnwu.magisk.utils.DynAPK
|
||||||
import com.topjohnwu.magisk.utils.isRunningAsStub
|
import com.topjohnwu.magisk.isRunningAsStub
|
||||||
import org.koin.core.get
|
import org.koin.core.get
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import kotlin.random.Random.Default.nextInt
|
import kotlin.random.Random.Default.nextInt
|
||||||
|
@ -12,7 +12,7 @@ import com.topjohnwu.magisk.extensions.writeTo
|
|||||||
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject
|
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject
|
||||||
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject.*
|
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject.*
|
||||||
import com.topjohnwu.magisk.utils.ProgressInputStream
|
import com.topjohnwu.magisk.utils.ProgressInputStream
|
||||||
import com.topjohnwu.magisk.utils.isRunningAsStub
|
import com.topjohnwu.magisk.isRunningAsStub
|
||||||
import com.topjohnwu.magisk.view.Notifications
|
import com.topjohnwu.magisk.view.Notifications
|
||||||
import com.topjohnwu.superuser.ShellUtils
|
import com.topjohnwu.superuser.ShellUtils
|
||||||
import io.reactivex.Completable
|
import io.reactivex.Completable
|
||||||
|
@ -7,7 +7,7 @@ import android.text.TextUtils
|
|||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import com.topjohnwu.magisk.*
|
import com.topjohnwu.magisk.*
|
||||||
import com.topjohnwu.magisk.utils.Utils
|
import com.topjohnwu.magisk.utils.Utils
|
||||||
import com.topjohnwu.magisk.utils.wrap
|
import com.topjohnwu.magisk.wrap
|
||||||
import com.topjohnwu.magisk.view.Notifications
|
import com.topjohnwu.magisk.view.Notifications
|
||||||
import com.topjohnwu.magisk.view.Shortcuts
|
import com.topjohnwu.magisk.view.Shortcuts
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
|
@ -13,10 +13,7 @@ import androidx.preference.ListPreference
|
|||||||
import androidx.preference.Preference
|
import androidx.preference.Preference
|
||||||
import androidx.preference.PreferenceCategory
|
import androidx.preference.PreferenceCategory
|
||||||
import androidx.preference.SwitchPreferenceCompat
|
import androidx.preference.SwitchPreferenceCompat
|
||||||
import com.topjohnwu.magisk.BuildConfig
|
import com.topjohnwu.magisk.*
|
||||||
import com.topjohnwu.magisk.Config
|
|
||||||
import com.topjohnwu.magisk.Const
|
|
||||||
import com.topjohnwu.magisk.R
|
|
||||||
import com.topjohnwu.magisk.base.BasePreferenceFragment
|
import com.topjohnwu.magisk.base.BasePreferenceFragment
|
||||||
import com.topjohnwu.magisk.data.database.RepoDao
|
import com.topjohnwu.magisk.data.database.RepoDao
|
||||||
import com.topjohnwu.magisk.databinding.CustomDownloadDialogBinding
|
import com.topjohnwu.magisk.databinding.CustomDownloadDialogBinding
|
||||||
|
49
app/src/main/java/com/topjohnwu/magisk/utils/Locales.kt
Normal file
49
app/src/main/java/com/topjohnwu/magisk/utils/Locales.kt
Normal file
@ -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<Locale>().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()!!
|
@ -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<Locale>().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() }
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -5,6 +5,7 @@ import com.topjohnwu.magisk.Const
|
|||||||
import com.topjohnwu.magisk.Info
|
import com.topjohnwu.magisk.Info
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
import com.topjohnwu.magisk.extensions.rawResource
|
import com.topjohnwu.magisk.extensions.rawResource
|
||||||
|
import com.topjohnwu.magisk.wrap
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
import com.topjohnwu.superuser.ShellUtils
|
import com.topjohnwu.superuser.ShellUtils
|
||||||
import com.topjohnwu.superuser.io.SuFile
|
import com.topjohnwu.superuser.io.SuFile
|
||||||
|
@ -75,36 +75,14 @@
|
|||||||
android:name="com.google.android.gms.version"
|
android:name="com.google.android.gms.version"
|
||||||
android:value="12451000" />
|
android:value="12451000" />
|
||||||
|
|
||||||
<!-- WorkManager -->
|
<!-- WorkManager SystemJobService -->
|
||||||
|
|
||||||
<provider
|
|
||||||
android:name="androidx.work.impl.WorkManagerInitializer"
|
|
||||||
android:authorities="${applicationId}.workmanager-init"
|
|
||||||
android:directBootAware="false"
|
|
||||||
android:exported="false"
|
|
||||||
android:multiprocess="true"
|
|
||||||
tools:targetApi="n" />
|
|
||||||
|
|
||||||
<service
|
<service
|
||||||
android:name="androidx.work.impl.background.systemjob.SystemJobService"
|
android:name="a.j"
|
||||||
android:directBootAware="false"
|
android:directBootAware="false"
|
||||||
android:enabled="true"
|
android:enabled="true"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:permission="android.permission.BIND_JOB_SERVICE"
|
android:permission="android.permission.BIND_JOB_SERVICE" />
|
||||||
tools:targetApi="n" />
|
|
||||||
|
|
||||||
<receiver
|
|
||||||
android:name="androidx.work.impl.utils.ForceStopRunnable$BroadcastReceiver"
|
|
||||||
android:directBootAware="false"
|
|
||||||
android:enabled="true"
|
|
||||||
android:exported="false"
|
|
||||||
tools:targetApi="n" />
|
|
||||||
|
|
||||||
<!-- Room -->
|
|
||||||
|
|
||||||
<service
|
|
||||||
android:name="androidx.room.MultiInstanceInvalidationService"
|
|
||||||
android:exported="false" />
|
|
||||||
|
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ import java.util.HashMap;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
class ComponentMap {
|
class ComponentMap {
|
||||||
private static Map<String, String> map = new HashMap<>(6);
|
private static Map<String, String> map = new HashMap<>();
|
||||||
|
|
||||||
// This mapping will be sent into the guest app
|
// This mapping will be sent into the guest app
|
||||||
static Map<String, String> inverseMap;
|
static Map<String, String> inverseMap;
|
||||||
@ -16,7 +16,7 @@ class ComponentMap {
|
|||||||
map.put("a.g", "a.m");
|
map.put("a.g", "a.m");
|
||||||
map.put(a.w.class.getName(), "a.h");
|
map.put(a.w.class.getName(), "a.h");
|
||||||
map.put("a.v", "a.j");
|
map.put("a.v", "a.j");
|
||||||
map.put("a.s", "androidx.work.impl.WorkManagerInitializer");
|
map.put("a.j", "androidx.work.impl.background.systemjob.SystemJobService");
|
||||||
|
|
||||||
inverseMap = new HashMap<>(map.size());
|
inverseMap = new HashMap<>(map.size());
|
||||||
for (Map.Entry<String, String> e : map.entrySet()) {
|
for (Map.Entry<String, String> e : map.entrySet()) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user