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) }