Add more information to Wi-Fi hotspot display
This commit is contained in:
@@ -2,6 +2,7 @@ package be.mygod.vpnhotspot.manage
|
|||||||
|
|
||||||
import android.annotation.TargetApi
|
import android.annotation.TargetApi
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.net.MacAddress
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
import android.view.View
|
import android.view.View
|
||||||
@@ -114,12 +115,17 @@ sealed class TetherManager(protected val parent: TetheringFragment) : Manager(),
|
|||||||
e.readableMessage
|
e.readableMessage
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
data.notifyChange()
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(24)
|
@RequiresApi(24)
|
||||||
class Wifi(parent: TetheringFragment) : TetherManager(parent), DefaultLifecycleObserver,
|
class Wifi(parent: TetheringFragment) : TetherManager(parent), DefaultLifecycleObserver,
|
||||||
WifiApManager.SoftApCallbackCompat {
|
WifiApManager.SoftApCallbackCompat {
|
||||||
private var failureReason: Int? = null
|
private var failureReason: Int? = null
|
||||||
|
private var numClients: Int? = null
|
||||||
|
private var frequency = 0
|
||||||
|
private var bandwidth = WifiApManager.CHANNEL_WIDTH_INVALID
|
||||||
|
private var capability: Pair<Int, Long>? = null
|
||||||
|
|
||||||
init {
|
init {
|
||||||
if (Build.VERSION.SDK_INT >= 28) parent.viewLifecycleOwner.lifecycle.addObserver(this)
|
if (Build.VERSION.SDK_INT >= 28) parent.viewLifecycleOwner.lifecycle.addObserver(this)
|
||||||
@@ -139,17 +145,41 @@ sealed class TetherManager(protected val parent: TetheringFragment) : Manager(),
|
|||||||
Timber.w(Exception("Unknown state $state"))
|
Timber.w(Exception("Unknown state $state"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
val newReason = if (state == 14) failureReason else null
|
this.failureReason = if (state == 14) failureReason else null // WIFI_AP_STATE_FAILED
|
||||||
if (this.failureReason != newReason) {
|
data.notifyChange()
|
||||||
this.failureReason = newReason
|
}
|
||||||
data.notifyChange()
|
override fun onNumClientsChanged(numClients: Int) {
|
||||||
}
|
this.numClients = numClients
|
||||||
|
if (Build.VERSION.SDK_INT >= 30) data.notifyChange() // only emits when onCapabilityChanged can be called
|
||||||
|
}
|
||||||
|
override fun onInfoChanged(frequency: Int, bandwidth: Int) {
|
||||||
|
this.frequency = frequency
|
||||||
|
this.bandwidth = bandwidth
|
||||||
|
data.notifyChange()
|
||||||
|
}
|
||||||
|
override fun onCapabilityChanged(maxSupportedClients: Int, supportedFeatures: Long) {
|
||||||
|
capability = maxSupportedClients to supportedFeatures
|
||||||
|
data.notifyChange()
|
||||||
}
|
}
|
||||||
|
|
||||||
override val title get() = parent.getString(R.string.tethering_manage_wifi)
|
override val title get() = parent.getString(R.string.tethering_manage_wifi)
|
||||||
override val tetherType get() = TetherType.WIFI
|
override val tetherType get() = TetherType.WIFI
|
||||||
override val type get() = VIEW_TYPE_WIFI
|
override val type get() = VIEW_TYPE_WIFI
|
||||||
override val text get() = listOfNotNull(failureReason?.let { WifiApManager.failureReasonLookup(it) },
|
override val text get() = listOfNotNull(failureReason?.let { WifiApManager.failureReasonLookup(it) },
|
||||||
|
if (frequency != 0 || bandwidth != WifiApManager.CHANNEL_WIDTH_INVALID) {
|
||||||
|
"$frequency MHz, ${WifiApManager.channelWidthLookup(bandwidth, true)}"
|
||||||
|
} else null,
|
||||||
|
capability?.let { (maxSupportedClients, supportedFeatures) ->
|
||||||
|
"${numClients ?: "?"}/$maxSupportedClients clients connected\nSupported features: " + sequence {
|
||||||
|
var features = supportedFeatures
|
||||||
|
while (features != 0L) {
|
||||||
|
@OptIn(ExperimentalStdlibApi::class)
|
||||||
|
val bit = features.takeLowestOneBit()
|
||||||
|
yield(WifiApManager.featureLookup(bit, true))
|
||||||
|
features = features and bit.inv()
|
||||||
|
}
|
||||||
|
}.joinToString()
|
||||||
|
},
|
||||||
baseError).joinToString("\n")
|
baseError).joinToString("\n")
|
||||||
|
|
||||||
override fun start() = TetheringManager.startTethering(TetheringManager.TETHERING_WIFI, true, this)
|
override fun start() = TetheringManager.startTethering(TetheringManager.TETHERING_WIFI, true, this)
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import androidx.annotation.RequiresApi
|
|||||||
import be.mygod.vpnhotspot.App.Companion.app
|
import be.mygod.vpnhotspot.App.Companion.app
|
||||||
import be.mygod.vpnhotspot.net.wifi.SoftApConfigurationCompat.Companion.toCompat
|
import be.mygod.vpnhotspot.net.wifi.SoftApConfigurationCompat.Companion.toCompat
|
||||||
import be.mygod.vpnhotspot.util.ConstantLookup
|
import be.mygod.vpnhotspot.util.ConstantLookup
|
||||||
|
import be.mygod.vpnhotspot.util.LongConstantLookup
|
||||||
import be.mygod.vpnhotspot.util.Services
|
import be.mygod.vpnhotspot.util.Services
|
||||||
import be.mygod.vpnhotspot.util.callSuper
|
import be.mygod.vpnhotspot.util.callSuper
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
@@ -93,6 +94,7 @@ object WifiApManager {
|
|||||||
@RequiresApi(30)
|
@RequiresApi(30)
|
||||||
fun onBlockedClientConnecting(client: MacAddress, blockedReason: Int) { }
|
fun onBlockedClientConnecting(client: MacAddress, blockedReason: Int) { }
|
||||||
}
|
}
|
||||||
|
@RequiresApi(28)
|
||||||
val failureReasonLookup = ConstantLookup<WifiManager>("SAP_START_FAILURE_",
|
val failureReasonLookup = ConstantLookup<WifiManager>("SAP_START_FAILURE_",
|
||||||
"SAP_START_FAILURE_GENERAL", "SAP_START_FAILURE_NO_CHANNEL")
|
"SAP_START_FAILURE_GENERAL", "SAP_START_FAILURE_NO_CHANNEL")
|
||||||
|
|
||||||
@@ -106,17 +108,26 @@ object WifiApManager {
|
|||||||
private val unregisterSoftApCallback by lazy {
|
private val unregisterSoftApCallback by lazy {
|
||||||
WifiManager::class.java.getDeclaredMethod("unregisterSoftApCallback", interfaceSoftApCallback)
|
WifiManager::class.java.getDeclaredMethod("unregisterSoftApCallback", interfaceSoftApCallback)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val getMacAddress by lazy {
|
private val getMacAddress by lazy {
|
||||||
Class.forName("android.net.wifi.WifiClient").getDeclaredMethod("getMacAddress")
|
Class.forName("android.net.wifi.WifiClient").getDeclaredMethod("getMacAddress")
|
||||||
}
|
}
|
||||||
|
|
||||||
private val classSoftApInfo by lazy { Class.forName("android.net.wifi.SoftApInfo") }
|
private val classSoftApInfo by lazy { Class.forName("android.net.wifi.SoftApInfo") }
|
||||||
private val getFrequency by lazy { classSoftApInfo.getDeclaredMethod("getFrequency") }
|
private val getFrequency by lazy { classSoftApInfo.getDeclaredMethod("getFrequency") }
|
||||||
private val getBandwidth by lazy { classSoftApInfo.getDeclaredMethod("getBandwidth") }
|
private val getBandwidth by lazy { classSoftApInfo.getDeclaredMethod("getBandwidth") }
|
||||||
|
@RequiresApi(30)
|
||||||
|
val channelWidthLookup = ConstantLookup(classSoftApInfo, "CHANNEL_WIDTH_")
|
||||||
|
const val CHANNEL_WIDTH_INVALID = 0
|
||||||
|
|
||||||
private val classSoftApCapability by lazy { Class.forName("android.net.wifi.SoftApCapability") }
|
private val classSoftApCapability by lazy { Class.forName("android.net.wifi.SoftApCapability") }
|
||||||
private val getMaxSupportedClients by lazy { classSoftApCapability.getDeclaredMethod("getMaxSupportedClients") }
|
private val getMaxSupportedClients by lazy { classSoftApCapability.getDeclaredMethod("getMaxSupportedClients") }
|
||||||
private val areFeaturesSupported by lazy {
|
private val areFeaturesSupported by lazy {
|
||||||
classSoftApCapability.getDeclaredMethod("areFeaturesSupported", Long::class.java)
|
classSoftApCapability.getDeclaredMethod("areFeaturesSupported", Long::class.java)
|
||||||
}
|
}
|
||||||
|
@RequiresApi(30)
|
||||||
|
val featureLookup = LongConstantLookup(classSoftApCapability, "SOFTAP_FEATURE_")
|
||||||
|
|
||||||
@RequiresApi(28)
|
@RequiresApi(28)
|
||||||
fun registerSoftApCallback(callback: SoftApCallbackCompat, executor: Executor): Any {
|
fun registerSoftApCallback(callback: SoftApCallbackCompat, executor: Executor): Any {
|
||||||
val proxy = Proxy.newProxyInstance(interfaceSoftApCallback.classLoader,
|
val proxy = Proxy.newProxyInstance(interfaceSoftApCallback.classLoader,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package be.mygod.vpnhotspot.util
|
package be.mygod.vpnhotspot.util
|
||||||
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
import androidx.collection.LongSparseArray
|
||||||
import androidx.collection.SparseArrayCompat
|
import androidx.collection.SparseArrayCompat
|
||||||
import be.mygod.vpnhotspot.App.Companion.app
|
import be.mygod.vpnhotspot.App.Companion.app
|
||||||
import be.mygod.vpnhotspot.R
|
import be.mygod.vpnhotspot.R
|
||||||
@@ -10,19 +11,21 @@ class ConstantLookup(private val clazz: Class<*>, private val prefix: String, pr
|
|||||||
private val lookup by lazy {
|
private val lookup by lazy {
|
||||||
SparseArrayCompat<String>().apply {
|
SparseArrayCompat<String>().apply {
|
||||||
for (field in clazz.declaredFields) try {
|
for (field in clazz.declaredFields) try {
|
||||||
if (field.name.startsWith(prefix)) put(field.get(null) as Int, field.name)
|
if (field.name.startsWith(prefix)) put(field.getInt(null), field.name)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Timber.w(e)
|
Timber.w(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
operator fun invoke(reason: Int): String {
|
|
||||||
|
operator fun invoke(reason: Int, trimPrefix: Boolean = false): String {
|
||||||
if (Build.VERSION.SDK_INT >= 30) try {
|
if (Build.VERSION.SDK_INT >= 30) try {
|
||||||
lookup.get(reason)?.let { return it }
|
lookup.get(reason)?.let { return if (trimPrefix) it.substring(prefix.length) else it }
|
||||||
} catch (e: ReflectiveOperationException) {
|
} catch (e: ReflectiveOperationException) {
|
||||||
Timber.w(e)
|
Timber.w(e)
|
||||||
}
|
}
|
||||||
return lookup29.getOrNull(reason) ?: app.getString(R.string.failure_reason_unknown, reason)
|
return lookup29.getOrNull(reason)?.let { if (trimPrefix) it.substring(prefix.length) else it }
|
||||||
|
?: app.getString(R.string.failure_reason_unknown, reason)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,3 +34,22 @@ fun ConstantLookup(clazz: Class<*>, prefix: String, vararg lookup29: String) = C
|
|||||||
@Suppress("FunctionName")
|
@Suppress("FunctionName")
|
||||||
inline fun <reified T> ConstantLookup(prefix: String, vararg lookup29: String) =
|
inline fun <reified T> ConstantLookup(prefix: String, vararg lookup29: String) =
|
||||||
ConstantLookup(T::class.java, prefix, lookup29)
|
ConstantLookup(T::class.java, prefix, lookup29)
|
||||||
|
|
||||||
|
class LongConstantLookup(private val clazz: Class<*>, private val prefix: String) {
|
||||||
|
private val lookup = LongSparseArray<String>().apply {
|
||||||
|
for (field in clazz.declaredFields) try {
|
||||||
|
if (field.name.startsWith(prefix)) put(field.getLong(null), field.name)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Timber.w(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
operator fun invoke(reason: Long, trimPrefix: Boolean = false): String {
|
||||||
|
try {
|
||||||
|
lookup.get(reason)?.let { return if (trimPrefix) it.substring(prefix.length) else it }
|
||||||
|
} catch (e: ReflectiveOperationException) {
|
||||||
|
Timber.w(e)
|
||||||
|
}
|
||||||
|
return app.getString(R.string.failure_reason_unknown, reason)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user