Commit 9f9de8c4 authored by topjohnwu's avatar topjohnwu

Obfuscate WorkManager components

Remove unused components and hack the context sent into WorkManager
parent a910c8cc
......@@ -97,6 +97,12 @@
android:name="com.google.android.gms.version"
android:value="12451000" />
<!-- Initialize WorkManager on-demand -->
<provider
android:name="androidx.work.impl.WorkManagerInitializer"
android:authorities="${applicationId}.workmanager-init"
tools:node="remove" />
</application>
</manifest>
......@@ -6,8 +6,8 @@ import androidx.annotation.NonNull;
import androidx.work.Worker;
import androidx.work.WorkerParameters;
import com.topjohnwu.magisk.HacksKt;
import com.topjohnwu.magisk.base.DelegateWorker;
import com.topjohnwu.magisk.utils.ResourceMgrKt;
import java.lang.reflect.ParameterizedType;
......@@ -19,7 +19,7 @@ public abstract class w<T extends DelegateWorker> extends Worker {
@SuppressWarnings("unchecked")
w(@NonNull Context context, @NonNull WorkerParameters workerParams) {
super(ResourceMgrKt.wrap(context, false), workerParams);
super(HacksKt.wrap(context, false), workerParams);
try {
base = ((Class<T>) ((ParameterizedType) getClass().getGenericSuperclass())
.getActualTypeArguments()[0]).newInstance();
......
......@@ -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,10 +15,7 @@ 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
......@@ -72,6 +70,7 @@ open class App() : Application() {
}
ResourceMgr.reload()
app.registerActivityLifecycleCallbacks(get<ActivityTracker>())
WorkManager.initialize(impl.wrapJob(), androidx.work.Configuration.Builder().build())
}
// This is required as some platforms expect ContextImpl
......
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)
}
@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.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
......
......@@ -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 {
......
......@@ -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 {
......
......@@ -20,7 +20,7 @@ import com.topjohnwu.magisk.model.entity.internal.DownloadSubject.*
import com.topjohnwu.magisk.ui.flash.FlashActivity
import com.topjohnwu.magisk.utils.APKInstall
import com.topjohnwu.magisk.utils.DynAPK
import com.topjohnwu.magisk.utils.isRunningAsStub
import com.topjohnwu.magisk.isRunningAsStub
import org.koin.core.get
import java.io.File
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.utils.ProgressInputStream
import com.topjohnwu.magisk.utils.isRunningAsStub
import com.topjohnwu.magisk.isRunningAsStub
import com.topjohnwu.magisk.view.Notifications
import com.topjohnwu.superuser.ShellUtils
import io.reactivex.Completable
......
......@@ -7,7 +7,7 @@ import android.text.TextUtils
import androidx.appcompat.app.AlertDialog
import com.topjohnwu.magisk.*
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.Shortcuts
import com.topjohnwu.superuser.Shell
......
......@@ -13,10 +13,7 @@ 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
......
@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.ResourceMgr
import com.topjohnwu.magisk.extensions.langTagToLocale
import io.reactivex.Single
import java.util.*
var isRunningAsStub = false
import kotlin.Comparator
var currentLocale: Locale = Locale.getDefault()
private set
@SuppressLint("ConstantLocale")
val defaultLocale: Locale = Locale.getDefault()
@Suppress("DEPRECATION")
val availableLocales = Single.fromCallable {
val compareId = R.string.app_changelog
mutableListOf<Locale>().apply {
......@@ -54,73 +47,3 @@ val availableLocales = Single.fromCallable {
.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.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
......
......@@ -75,36 +75,14 @@
android:name="com.google.android.gms.version"
android:value="12451000" />
<!-- WorkManager -->
<provider
android:name="androidx.work.impl.WorkManagerInitializer"
android:authorities="${applicationId}.workmanager-init"
android:directBootAware="false"
android:exported="false"
android:multiprocess="true"
tools:targetApi="n" />
<!-- WorkManager SystemJobService -->
<service
android:name="androidx.work.impl.background.systemjob.SystemJobService"
android:name="a.j"
android:directBootAware="false"
android:enabled="true"
android:exported="true"
android:permission="android.permission.BIND_JOB_SERVICE"
tools:targetApi="n" />
<receiver
android:name="androidx.work.impl.utils.ForceStopRunnable$BroadcastReceiver"
android:directBootAware="false"
android:enabled="true"
android:exported="false"
tools:targetApi="n" />
<!-- Room -->
<service
android:name="androidx.room.MultiInstanceInvalidationService"
android:exported="false" />
android:permission="android.permission.BIND_JOB_SERVICE" />
</application>
......
......@@ -4,7 +4,7 @@ import java.util.HashMap;
import java.util.Map;
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
static Map<String, String> inverseMap;
......@@ -16,7 +16,7 @@ class ComponentMap {
map.put("a.g", "a.m");
map.put(a.w.class.getName(), "a.h");
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());
for (Map.Entry<String, String> e : map.entrySet()) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment