From d1e90fb695ffff182e50b5c86314ef4c406bf14f Mon Sep 17 00:00:00 2001 From: programminghoch10 <16062290+programminghoch10@users.noreply.github.com> Date: Mon, 21 Sep 2026 13:34:29 +0200 Subject: [PATCH] implement EnableDataSaverTethering --- EnableDataSaverTethering/Readme.md | 9 +++ EnableDataSaverTethering/build.gradle.kts | 17 +++++ .../src/main/AndroidManifest.xml | 24 ++++++ .../src/main/assets/xposed_init | 2 + .../EnableDataSaverTethering/SettingsHook.kt | 74 +++++++++++++++++++ .../EnableDataSaverTethering/SystemUIHook.kt | 32 ++++++++ .../src/main/res/values/arrays.xml | 7 ++ README.md | 1 + .../en-US/full_description.txt | 5 ++ .../en-US/short_description.txt | 1 + .../en-US/title.txt | 1 + modules.gradle.kts | 1 + .../binarynoise/reflection/CurrentThread.kt | 21 ++++++ 13 files changed, 195 insertions(+) create mode 100644 EnableDataSaverTethering/Readme.md create mode 100644 EnableDataSaverTethering/build.gradle.kts create mode 100644 EnableDataSaverTethering/src/main/AndroidManifest.xml create mode 100644 EnableDataSaverTethering/src/main/assets/xposed_init create mode 100644 EnableDataSaverTethering/src/main/java/com/programminghoch10/EnableDataSaverTethering/SettingsHook.kt create mode 100644 EnableDataSaverTethering/src/main/java/com/programminghoch10/EnableDataSaverTethering/SystemUIHook.kt create mode 100644 EnableDataSaverTethering/src/main/res/values/arrays.xml create mode 100644 metadata/com.programminghoch10.EnableDataSaverTethering/en-US/full_description.txt create mode 100644 metadata/com.programminghoch10.EnableDataSaverTethering/en-US/short_description.txt create mode 100644 metadata/com.programminghoch10.EnableDataSaverTethering/en-US/title.txt create mode 100644 reflection/src/main/java/de/binarynoise/reflection/CurrentThread.kt diff --git a/EnableDataSaverTethering/Readme.md b/EnableDataSaverTethering/Readme.md new file mode 100644 index 0000000..869cdbb --- /dev/null +++ b/EnableDataSaverTethering/Readme.md @@ -0,0 +1,9 @@ +# EnableDataSaverTethering + +Enable tething while data saver is active. + +Android automatically disables all tethering options while the data saver is activated. +Since tethering is a manual user decision, this is not very useful. +It is especially annoying when using tethering while connected to WiFi. +This module makes the tethering settings ignore the data saver, +allowing them to be toggled on and off regardless. diff --git a/EnableDataSaverTethering/build.gradle.kts b/EnableDataSaverTethering/build.gradle.kts new file mode 100644 index 0000000..ec6b6b8 --- /dev/null +++ b/EnableDataSaverTethering/build.gradle.kts @@ -0,0 +1,17 @@ +plugins { + alias(libs.plugins.buildlogic.android.application) + alias(libs.plugins.buildlogic.kotlin.android) +} + +android { + namespace = "com.programminghoch10.EnableDataSaverTethering" + + defaultConfig { + minSdk = 36 + targetSdk = 36 + } +} + +dependencies { + implementation(projects.reflection) +} diff --git a/EnableDataSaverTethering/src/main/AndroidManifest.xml b/EnableDataSaverTethering/src/main/AndroidManifest.xml new file mode 100644 index 0000000..dc989f5 --- /dev/null +++ b/EnableDataSaverTethering/src/main/AndroidManifest.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + diff --git a/EnableDataSaverTethering/src/main/assets/xposed_init b/EnableDataSaverTethering/src/main/assets/xposed_init new file mode 100644 index 0000000..346b6b4 --- /dev/null +++ b/EnableDataSaverTethering/src/main/assets/xposed_init @@ -0,0 +1,2 @@ +com.programminghoch10.EnableDataSaverTethering.SettingsHook +com.programminghoch10.EnableDataSaverTethering.SystemUIHook diff --git a/EnableDataSaverTethering/src/main/java/com/programminghoch10/EnableDataSaverTethering/SettingsHook.kt b/EnableDataSaverTethering/src/main/java/com/programminghoch10/EnableDataSaverTethering/SettingsHook.kt new file mode 100644 index 0000000..26e1f2f --- /dev/null +++ b/EnableDataSaverTethering/src/main/java/com/programminghoch10/EnableDataSaverTethering/SettingsHook.kt @@ -0,0 +1,74 @@ +package com.programminghoch10.EnableDataSaverTethering + +import android.content.Context +import android.widget.LinearLayout +import de.robv.android.xposed.IXposedHookLoadPackage +import de.robv.android.xposed.XC_MethodHook +import de.robv.android.xposed.XC_MethodReplacement +import de.robv.android.xposed.XposedBridge +import de.robv.android.xposed.XposedHelpers +import de.robv.android.xposed.callbacks.XC_LoadPackage + +class SettingsHook : IXposedHookLoadPackage { + override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam) { + if (lpparam.packageName != "com.android.settings") return + + val TetherSettingsClass = XposedHelpers.findClass("com.android.settings.network.tether.TetherSettings", lpparam.classLoader) + XposedBridge.hookAllMethods(TetherSettingsClass, "onCreate", object : XC_MethodHook() { + override fun afterHookedMethod(param: MethodHookParam) { + XposedHelpers.setBooleanField(param.thisObject, "mDataSaverEnabled", false) + } + }) + XposedHelpers.findAndHookMethod( + TetherSettingsClass, + "onDataSaverChanged", + Boolean::class.java, + XC_MethodReplacement.DO_NOTHING, + ) + + // all the following is for Wi-Fi tethering only 👀 + + val WifiTetherPreferenceControllerClass = + XposedHelpers.findClass("com.android.settings.wifi.tether.WifiTetherPreferenceController", lpparam.classLoader) + XposedHelpers.findAndHookMethod( + WifiTetherPreferenceControllerClass, + "setDataSaverEnabled", + Boolean::class.java, + XC_MethodReplacement.DO_NOTHING, + ) + XposedBridge.hookAllConstructors(WifiTetherPreferenceControllerClass, object : XC_MethodHook() { + override fun afterHookedMethod(param: MethodHookParam) { + XposedHelpers.setBooleanField(param.thisObject, "mDataSaverEnabled", false) + } + }) + XposedHelpers.findAndHookMethod(WifiTetherPreferenceControllerClass, "canEnabled", object : XC_MethodHook() { + override fun afterHookedMethod(param: MethodHookParam) { + param.result = true + } + }) + + val WifiTetherSwitchBarControllerClass = + XposedHelpers.findClass("com.android.settings.wifi.tether.WifiTetherSwitchBarController", lpparam.classLoader) + XposedHelpers.findAndHookMethod( + WifiTetherSwitchBarControllerClass, + "onDataSaverChanged", + Boolean::class.java, + XC_MethodReplacement.DO_NOTHING, + ) + XposedHelpers.findAndHookMethod(WifiTetherSwitchBarControllerClass, "updateWifiSwitch", object : XC_MethodHook() { + override fun afterHookedMethod(param: MethodHookParam) { + val mSwitchBar = XposedHelpers.getObjectField(param.thisObject, "mSwitchBar") as LinearLayout + mSwitchBar.isEnabled = true + } + }) + + val WifiHotspotScreenClass = XposedHelpers.findClass("com.android.settings.wifi.tether.WifiHotspotScreen", lpparam.classLoader) + // we are ignoring super.isEnabled here because I can't find a good way to implement that + XposedHelpers.findAndHookMethod( + WifiHotspotScreenClass, + "isEnabled", + Context::class.java, + XC_MethodReplacement.returnConstant(true), + ) + } +} diff --git a/EnableDataSaverTethering/src/main/java/com/programminghoch10/EnableDataSaverTethering/SystemUIHook.kt b/EnableDataSaverTethering/src/main/java/com/programminghoch10/EnableDataSaverTethering/SystemUIHook.kt new file mode 100644 index 0000000..e6bc68c --- /dev/null +++ b/EnableDataSaverTethering/src/main/java/com/programminghoch10/EnableDataSaverTethering/SystemUIHook.kt @@ -0,0 +1,32 @@ +package com.programminghoch10.EnableDataSaverTethering + +import de.binarynoise.reflection.isCurrentThreadCalledFromClass +import de.robv.android.xposed.IXposedHookLoadPackage +import de.robv.android.xposed.XC_MethodHook +import de.robv.android.xposed.XposedBridge +import de.robv.android.xposed.XposedHelpers +import de.robv.android.xposed.callbacks.XC_LoadPackage + +class SystemUIHook : IXposedHookLoadPackage { + override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam) { + if (lpparam.packageName != "com.android.systemui") return + + val NetworkPolicyManagerClass = XposedHelpers.findClass("android.net.NetworkPolicyManager", lpparam.classLoader) + val HotspotTileClass = XposedHelpers.findClass("com.android.systemui.qs.tiles.HotspotTile", lpparam.classLoader) + XposedHelpers.findAndHookMethod(NetworkPolicyManagerClass, "getRestrictBackground", object : XC_MethodHook() { + override fun beforeHookedMethod(param: MethodHookParam) { + if (isCurrentThreadCalledFromClass(HotspotTileClass)) param.result = false + } + }) + + val CallbackInfoClass = XposedHelpers.findClass(HotspotTileClass.name + $$"$CallbackInfo", lpparam.classLoader) + XposedBridge.hookMethod(HotspotTileClass.methods.single { it.name == "handleUpdateState" }, object : XC_MethodHook() { + override fun beforeHookedMethod(param: MethodHookParam) { + val obj = param.args[1] ?: return + if (obj::class.java == CallbackInfoClass) { + XposedHelpers.setBooleanField(obj, "isDataSaverEnabled", false) + } + } + }) + } +} diff --git a/EnableDataSaverTethering/src/main/res/values/arrays.xml b/EnableDataSaverTethering/src/main/res/values/arrays.xml new file mode 100644 index 0000000..ee9708d --- /dev/null +++ b/EnableDataSaverTethering/src/main/res/values/arrays.xml @@ -0,0 +1,7 @@ + + + + com.android.settings + com.android.systemui + + diff --git a/README.md b/README.md index f6b362a..d620c9e 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ A collection of small Xposed Modules. | [CodecMod](CodecMod) | [@programminghoch10](https://github.com/programminghoch10) | Selectively disable audio/video hardware/software encoders/decoders. | [GitHub](https://github.com/binarynoise/XposedModulets/releases?q=CodecMod) | | [DisableSounds](DisableSounds) | [@programminghoch10](https://github.com/programminghoch10) | Disable various system sounds | [GitHub](https://github.com/binarynoise/XposedModulets/releases?q=DisableSounds) | | [Don'tResetIfBootedAndConnected](DontResetIfBootedAndConnected) | [@binarynoise](https://github.com/binarynoise) | | [GitHub](https://github.com/binarynoise/XposedModulets/releases?q=dontResetIfBootedAndConnected) | +| [EnableDataSaverTethering](EnableDataSaverTethering) | [@programminghoch10](https://github.com/programminghoch10) | Enable tething while data saver is active. | [GitHub](https://github.com/binarynoise/XposedModulets/releases?q=EnableDataSaverTethering) | | [FreeNotifications](FreeNotifications) | [@binarynoise](https://github.com/binarynoise) & [@programminghoch10](https://github.com/programminghoch10) | Enables customization for all Notification Channels again | [GitHub](https://github.com/binarynoise/XposedModulets/releases?q=freeNotifications) [IzzyOnDroid](https://apt.izzysoft.de/fdroid/index/apk/de.binarynoise.freeNotifications) | | [GalaxyWearable](GalaxyWearable) | [@programminghoch10](https://github.com/programminghoch10) | Enables running Samsung's GalaxyWearable app and compantions on modded Samsung devices. | [GitHub](https://github.com/binarynoise/XposedModulets/releases?q=GalaxyWearable) | | [HideAbi](HideAbi) | [@binarynoise](https://github.com/binarynoise) | Hide specific CPU ABIs from apps. | [GitHub](https://github.com/binarynoise/XposedModulets/releases?q=HideAbi) | diff --git a/metadata/com.programminghoch10.EnableDataSaverTethering/en-US/full_description.txt b/metadata/com.programminghoch10.EnableDataSaverTethering/en-US/full_description.txt new file mode 100644 index 0000000..257d281 --- /dev/null +++ b/metadata/com.programminghoch10.EnableDataSaverTethering/en-US/full_description.txt @@ -0,0 +1,5 @@ +Android automatically disables all tethering options while the data saver is activated. +Since tethering is a manual user decision, this is not very useful. +It is especially annoying when using tethering while connected to WiFi. +This module makes the tethering settings ignore the data saver, +allowing them to be toggled on and off regardless. diff --git a/metadata/com.programminghoch10.EnableDataSaverTethering/en-US/short_description.txt b/metadata/com.programminghoch10.EnableDataSaverTethering/en-US/short_description.txt new file mode 100644 index 0000000..e2df568 --- /dev/null +++ b/metadata/com.programminghoch10.EnableDataSaverTethering/en-US/short_description.txt @@ -0,0 +1 @@ +Enable tething while data saver is active. diff --git a/metadata/com.programminghoch10.EnableDataSaverTethering/en-US/title.txt b/metadata/com.programminghoch10.EnableDataSaverTethering/en-US/title.txt new file mode 100644 index 0000000..8d18fb6 --- /dev/null +++ b/metadata/com.programminghoch10.EnableDataSaverTethering/en-US/title.txt @@ -0,0 +1 @@ +EnableDataSaverTethering diff --git a/modules.gradle.kts b/modules.gradle.kts index 42077d6..145d951 100644 --- a/modules.gradle.kts +++ b/modules.gradle.kts @@ -10,6 +10,7 @@ include(":CodecMod") include(":DisableSounds") include(":DontResetIfBootedAndConnected") include(":EnableCallRecording") +include(":EnableDataSaverTethering") include(":FreeNotifications") include(":GalaxyWearable") include(":HideAbi") diff --git a/reflection/src/main/java/de/binarynoise/reflection/CurrentThread.kt b/reflection/src/main/java/de/binarynoise/reflection/CurrentThread.kt new file mode 100644 index 0000000..499c14a --- /dev/null +++ b/reflection/src/main/java/de/binarynoise/reflection/CurrentThread.kt @@ -0,0 +1,21 @@ +package de.binarynoise.reflection + +import java.lang.reflect.Method + +val currentStackTrace: Array get() = Thread.currentThread().stackTrace + +fun StackTraceElement.isFromClass(className: String): Boolean = this.className == className +fun StackTraceElement.isFromClass(cls: Class<*>): Boolean = isFromClass(cls.name) +fun StackTraceElement.isFromMethod(method: String): Boolean = methodName == method +fun StackTraceElement.isFromMethod(method: Method): Boolean = isFromClass(method.declaringClass) && isFromMethod(method.name) + + +/** + * returns true if the given class is present anywhere in the current thread's stack trace + */ +fun isCurrentThreadCalledFromClass(cls: Class<*>): Boolean = currentStackTrace.any { it.isFromClass(cls) } + +/** + * returns true if the given method is present anywhere in the current thread's stack trace + */ +fun isCurrentThreadCalledFromMethod(method: Method): Boolean = currentStackTrace.any { it.isFromMethod(method) }