Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions EnableDataSaverTethering/Readme.md
Original file line number Diff line number Diff line change
@@ -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.
17 changes: 17 additions & 0 deletions EnableDataSaverTethering/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -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)
}
24 changes: 24 additions & 0 deletions EnableDataSaverTethering/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android">

<application android:label="EnableDataSaverTethering">
<meta-data
android:name="xposedmodule"
android:value="true"
/>
<meta-data
android:name="xposeddescription"
android:value="Enable tethering settings even when data saver is active"
/>
<meta-data
android:name="xposedminversion"
android:value="93"
/>
<meta-data
android:name="xposedscope"
android:resource="@array/scope"
/>
</application>

</manifest>
2 changes: 2 additions & 0 deletions EnableDataSaverTethering/src/main/assets/xposed_init
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
com.programminghoch10.EnableDataSaverTethering.SettingsHook
com.programminghoch10.EnableDataSaverTethering.SystemUIHook
Original file line number Diff line number Diff line change
@@ -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),
)
}
}
Original file line number Diff line number Diff line change
@@ -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)
}
}
})
}
}
7 changes: 7 additions & 0 deletions EnableDataSaverTethering/src/main/res/values/arrays.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="scope">
<item>com.android.settings</item>
<item>com.android.systemui</item>
</string-array>
</resources>
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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) |
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Enable tething while data saver is active.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
EnableDataSaverTethering
1 change: 1 addition & 0 deletions modules.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ include(":CodecMod")
include(":DisableSounds")
include(":DontResetIfBootedAndConnected")
include(":EnableCallRecording")
include(":EnableDataSaverTethering")
include(":FreeNotifications")
include(":GalaxyWearable")
include(":HideAbi")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package de.binarynoise.reflection

import java.lang.reflect.Method

val currentStackTrace: Array<out StackTraceElement> 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) }
Loading