Bump to minapi 28

This commit is contained in:
Mygod
2023-02-08 18:45:28 -05:00
parent 8ba727c1b8
commit 15c3c5a6d7
89 changed files with 361 additions and 809 deletions

View File

@@ -1,5 +1,6 @@
package be.mygod.vpnhotspot.net
import android.net.MacAddress
import android.os.Build
import android.system.ErrnoException
import android.system.Os
@@ -15,7 +16,7 @@ import java.io.IOException
import java.net.Inet4Address
import java.net.InetAddress
data class IpNeighbour(val ip: InetAddress, val dev: String, val lladdr: MacAddressCompat, val state: State) {
data class IpNeighbour(val ip: InetAddress, val dev: String, val lladdr: MacAddress, val state: State) {
enum class State {
INCOMPLETE, VALID, FAILED, DELETING
}
@@ -65,7 +66,7 @@ data class IpNeighbour(val ip: InetAddress, val dev: String, val lladdr: MacAddr
return devs.map { IpNeighbour(ip, it, lladdr, State.DELETING) }
}
if (match.groups[4] != null) try {
lladdr = MacAddressCompat.fromString(match.groupValues[4])
lladdr = MacAddress.fromString(match.groupValues[4])
} catch (e: IllegalArgumentException) {
if (state != State.INCOMPLETE && state != State.DELETING) {
Timber.w(IOException("Failed to find MAC address for $line", e))
@@ -79,7 +80,7 @@ data class IpNeighbour(val ip: InetAddress, val dev: String, val lladdr: MacAddr
val list = arp()
.asSequence()
.filter { parseNumericAddress(it[ARP_IP_ADDRESS]) == ip && it[ARP_DEVICE] in devs }
.map { MacAddressCompat.fromString(it[ARP_HW_ADDRESS]) }
.map { MacAddress.fromString(it[ARP_HW_ADDRESS]) }
.filter { it != MacAddressCompat.ALL_ZEROS_ADDRESS }
.distinct()
.toList()
@@ -138,5 +139,4 @@ data class IpNeighbour(val ip: InetAddress, val dev: String, val lladdr: MacAddr
data class IpDev(val ip: InetAddress, val dev: String) {
override fun toString() = "$ip%$dev"
}
@Suppress("FunctionName")
fun IpDev(neighbour: IpNeighbour) = IpDev(neighbour.ip, neighbour.dev)

View File

@@ -1,95 +1,34 @@
package be.mygod.vpnhotspot.net
import android.net.MacAddress
import androidx.annotation.RequiresApi
import java.nio.ByteBuffer
import java.nio.ByteOrder
/**
* Compat support class for [MacAddress].
* This used to be a compat support class for [MacAddress].
* Now it is just a convenient class for backwards compatibility.
*/
@JvmInline
value class MacAddressCompat(val addr: Long) {
companion object {
private const val ETHER_ADDR_LEN = 6
/**
* The MacAddress zero MAC address.
*
* Not publicly exposed or treated specially since the OUI 00:00:00 is registered.
* @hide
*/
val ALL_ZEROS_ADDRESS = MacAddressCompat(0)
val ANY_ADDRESS = MacAddressCompat(2)
val ALL_ZEROS_ADDRESS = MacAddress.fromBytes(byteArrayOf(0, 0, 0, 0, 0, 0))
val ANY_ADDRESS = MacAddress.fromBytes(byteArrayOf(2, 0, 0, 0, 0, 0))
/**
* Creates a MacAddress from the given byte array representation.
* A valid byte array representation for a MacAddress is a non-null array of length 6.
*
* @param addr a byte array representation of a MAC address.
* @return the MacAddress corresponding to the given byte array representation.
* @throws IllegalArgumentException if the given byte array is not a valid representation.
*/
fun fromBytes(addr: ByteArray): MacAddressCompat {
val buffer = when (addr.size) {
ETHER_ADDR_LEN -> ByteBuffer.allocate(Long.SIZE_BYTES).order(ByteOrder.LITTLE_ENDIAN).put(addr)
8 -> {
require(addr.take(2).all { it == 0.toByte() }) {
"Unrecognized padding " + addr.joinToString(":") { "%02x".format(it) }
}
ByteBuffer.allocate(Long.SIZE_BYTES).order(ByteOrder.LITTLE_ENDIAN).put(addr, 2, ETHER_ADDR_LEN)
}
else -> return fromString(String(addr))
}
buffer.rewind()
return MacAddressCompat(buffer.long)
}
/**
* Creates a MacAddress from the given String representation. A valid String representation
* for a MacAddress is a series of 6 values in the range [0,ff] printed in hexadecimal
* and joined by ':' characters.
*
* @param addr a String representation of a MAC address.
* @return the MacAddress corresponding to the given String representation.
* @throws IllegalArgumentException if the given String is not a valid representation.
*/
fun fromString(addr: String) = ByteBuffer.allocate(Long.SIZE_BYTES).run {
fun MacAddress.toLong() = ByteBuffer.allocate(Long.SIZE_BYTES).apply {
order(ByteOrder.LITTLE_ENDIAN)
var start = 0
var i = 0
while (position() < ETHER_ADDR_LEN && start < addr.length) {
val end = i
if (addr.getOrElse(i) { ':' } == ':') ++i else if (i < start + 2) {
++i
continue
}
put(if (start == end) 0 else try {
Integer.parseInt(addr.substring(start, end), 16).toByte()
} catch (e: NumberFormatException) {
throw IllegalArgumentException(e)
})
start = i
}
require(position() == ETHER_ADDR_LEN) { "MAC address too short" }
put(toByteArray())
rewind()
MacAddressCompat(long)
}
@RequiresApi(28)
fun MacAddress.toCompat() = fromBytes(toByteArray())
}.long
}
fun validate() = require(addr and ((1L shl 48) - 1).inv() == 0L)
fun toList() = ByteBuffer.allocate(8).run {
fun toPlatform() = MacAddress.fromBytes(ByteBuffer.allocate(8).run {
order(ByteOrder.LITTLE_ENDIAN)
putLong(addr)
array().take(6)
}
@RequiresApi(28)
fun toPlatform() = MacAddress.fromBytes(toList().toByteArray())
override fun toString() = toList().joinToString(":") { "%02x".format(it) }
fun toOui() = toList().joinToString("") { "%02x".format(it) }.substring(0, 9)
}.toByteArray())
}

View File

@@ -1,11 +1,9 @@
package be.mygod.vpnhotspot.net
import android.annotation.SuppressLint
import android.annotation.TargetApi
import android.net.LinkProperties
import android.net.MacAddress
import android.net.RouteInfo
import android.os.Build
import androidx.annotation.RequiresApi
import android.system.Os
import be.mygod.vpnhotspot.App.Companion.app
import be.mygod.vpnhotspot.R
import be.mygod.vpnhotspot.net.monitor.FallbackUpstreamMonitor
@@ -15,7 +13,9 @@ import be.mygod.vpnhotspot.net.monitor.UpstreamMonitor
import be.mygod.vpnhotspot.room.AppDatabase
import be.mygod.vpnhotspot.root.RootManager
import be.mygod.vpnhotspot.root.RoutingCommands
import be.mygod.vpnhotspot.util.*
import be.mygod.vpnhotspot.util.RootSession
import be.mygod.vpnhotspot.util.allInterfaceNames
import be.mygod.vpnhotspot.util.allRoutes
import be.mygod.vpnhotspot.widget.SmartSnackbar
import kotlinx.coroutines.CancellationException
import timber.log.Timber
@@ -125,7 +125,6 @@ class Routing(private val caller: Any, private val downstream: String) : IpNeigh
*
* Source: https://android.googlesource.com/platform/system/netd/+/3b47c793ff7ade843b1d85a9be8461c3b4dc693e
*/
@RequiresApi(28)
Netd,
}
@@ -155,14 +154,14 @@ class Routing(private val caller: Any, private val downstream: String) : IpNeigh
* The only case when upstream is null is on API 23- and we are using system default rules.
*/
inner class Subrouting(priority: Int, val upstream: String) {
val ifindex = if (upstream.isEmpty()) 0 else if_nametoindex(upstream).also {
val ifindex = if (upstream.isEmpty()) 0 else Os.if_nametoindex(upstream).also {
if (it <= 0) throw InterfaceGoneException(upstream)
}
val transaction = RootSession.beginTransaction().safeguard {
if (upstream.isEmpty()) {
ipRule("goto $RULE_PRIORITY_TETHERING", priority) // skip unreachable rule
} else ipRuleLookup(ifindex, priority)
@TargetApi(28) when (masqueradeMode) {
when (masqueradeMode) {
MasqueradeMode.None -> { } // nothing to be done here
MasqueradeMode.Simple -> {
// note: specifying -i wouldn't work for POSTROUTING
@@ -225,16 +224,10 @@ class Routing(private val caller: Any, private val downstream: String) : IpNeigh
updateDnsRoute()
}
}
private val fallbackUpstream = object : Upstream(RULE_PRIORITY_UPSTREAM_FALLBACK) {
@SuppressLint("NewApi")
override fun onFallback() = onAvailable(LinkProperties().apply {
interfaceName = ""
setDnsServers(listOf(parseNumericAddress("8.8.8.8")))
})
}
private val fallbackUpstream = Upstream(RULE_PRIORITY_UPSTREAM_FALLBACK)
private val upstream = Upstream(RULE_PRIORITY_UPSTREAM)
private inner class Client(private val ip: Inet4Address, mac: MacAddressCompat) : AutoCloseable {
private inner class Client(private val ip: Inet4Address, mac: MacAddress) : AutoCloseable {
private val transaction = RootSession.beginTransaction().safeguard {
val address = ip.hostAddress
iptablesInsert("vpnhotspot_acl -i $downstream -s $address -j ACCEPT")
@@ -287,9 +280,9 @@ class Routing(private val caller: Any, private val downstream: String) : IpNeigh
* but may be broken when system tethering shutdown before local-only interfaces.
*/
fun ipForward() {
if (Build.VERSION.SDK_INT >= 23) try {
try {
transaction.ndc("ipfwd", "ndc ipfwd enable vpnhotspot_$downstream",
"ndc ipfwd disable vpnhotspot_$downstream")
"ndc ipfwd disable vpnhotspot_$downstream")
return
} catch (e: RoutingCommands.UnexpectedOutputException) {
Timber.w(IOException("ndc ipfwd enable failure", e))

View File

@@ -1,10 +1,8 @@
package be.mygod.vpnhotspot.net
import android.os.Build
import android.provider.Settings
import be.mygod.vpnhotspot.App.Companion.app
import be.mygod.vpnhotspot.root.SettingsGlobalPut
import timber.log.Timber
/**
* It's hard to change tethering rules with Tethering hardware acceleration enabled for now.
@@ -15,19 +13,6 @@ import timber.log.Timber
* https://android.googlesource.com/platform/hardware/qcom/data/ipacfg-mgr/+/master/msm8998/ipacm/src/IPACM_OffloadManager.cpp
*/
object TetherOffloadManager {
val supported by lazy {
Build.VERSION.SDK_INT >= 27 || try {
Settings.Global::class.java.getDeclaredField("TETHER_OFFLOAD_DISABLED").get(null).let {
require(it == TETHER_OFFLOAD_DISABLED) { "Unknown field $it" }
}
true
} catch (_: NoSuchFieldException) {
false
} catch (e: Exception) {
Timber.w(e)
false
}
}
private const val TETHER_OFFLOAD_DISABLED = "tether_offload_disabled"
val enabled get() = Settings.Global.getInt(app.contentResolver, TETHER_OFFLOAD_DISABLED, 0) == 0
suspend fun setEnabled(value: Boolean) = SettingsGlobalPut.int(TETHER_OFFLOAD_DISABLED, if (value) 0 else 1)

View File

@@ -94,9 +94,7 @@ object TetheringManager {
* https://android.googlesource.com/platform/frameworks/base.git/+/2a091d7aa0c174986387e5d56bf97a87fe075bdb%5E%21/services/java/com/android/server/connectivity/Tethering.java
*/
const val ACTION_TETHER_STATE_CHANGED = "android.net.conn.TETHER_STATE_CHANGED"
@RequiresApi(26)
private const val EXTRA_ACTIVE_LOCAL_ONLY_LEGACY = "localOnlyArray"
private const val EXTRA_ACTIVE_TETHER_LEGACY = "activeArray"
/**
* gives a String[] listing all the interfaces currently in local-only
* mode (ie, has DHCPv4+IPv6-ULA support and no packet forwarding)
@@ -107,7 +105,6 @@ object TetheringManager {
* gives a String[] listing all the interfaces currently tethered
* (ie, has DHCPv4 support and packets potentially forwarded/NATed)
*/
@RequiresApi(26)
private const val EXTRA_ACTIVE_TETHER = "tetherArray"
/**
* gives a String[] listing all the interfaces we tried to tether and
@@ -131,7 +128,6 @@ object TetheringManager {
* Wifi tethering type.
* @see [startTethering].
*/
@RequiresApi(24)
const val TETHERING_WIFI = 0
/**
* USB tethering type.
@@ -141,7 +137,6 @@ object TetheringManager {
* Source: https://android.googlesource.com/platform/frameworks/base/+/7ca5d3a/services/usb/java/com/android/server/usb/UsbService.java#389
* @see startTethering
*/
@RequiresApi(24)
const val TETHERING_USB = 1
/**
* Bluetooth tethering type.
@@ -149,7 +144,6 @@ object TetheringManager {
* Requires BLUETOOTH permission.
* @see startTethering
*/
@RequiresApi(24)
const val TETHERING_BLUETOOTH = 2
/**
* Ethernet tethering type.
@@ -178,16 +172,13 @@ object TetheringManager {
}
}.first()
@get:RequiresApi(24)
private val classOnStartTetheringCallback by lazy {
Class.forName("android.net.ConnectivityManager\$OnStartTetheringCallback")
}
@get:RequiresApi(24)
private val startTetheringLegacy by lazy @TargetApi(24) {
private val startTetheringLegacy by lazy {
ConnectivityManager::class.java.getDeclaredMethod("startTethering",
Int::class.java, Boolean::class.java, classOnStartTetheringCallback, Handler::class.java)
}
@get:RequiresApi(24)
private val stopTetheringLegacy by lazy {
ConnectivityManager::class.java.getDeclaredMethod("stopTethering", Int::class.java)
}
@@ -232,7 +223,6 @@ object TetheringManager {
private val stopTethering by lazy @TargetApi(30) { clazz.getDeclaredMethod("stopTethering", Int::class.java) }
@Deprecated("Legacy API")
@RequiresApi(24)
fun startTetheringLegacy(type: Int, showProvisioningUi: Boolean, callback: StartTetheringCallback,
handler: Handler? = null, cacheDir: File = app.deviceStorage.codeCacheDir) {
val reference = WeakReference(callback)
@@ -299,7 +289,6 @@ object TetheringManager {
* configures tethering with the preferred local IPv4 link address to use.
* *@see setStaticIpv4Addresses
*/
@RequiresApi(24)
fun startTethering(type: Int, showProvisioningUi: Boolean, callback: StartTetheringCallback,
handler: Handler? = null, cacheDir: File = app.deviceStorage.codeCacheDir) {
if (Build.VERSION.SDK_INT >= 30) try {
@@ -371,12 +360,10 @@ object TetheringManager {
* {@link ConnectivityManager.TETHERING_USB}, or
* {@link ConnectivityManager.TETHERING_BLUETOOTH}.
*/
@RequiresApi(24)
fun stopTethering(type: Int) {
if (Build.VERSION.SDK_INT >= 30) stopTethering(instance, type)
else stopTetheringLegacy(Services.connectivity, type)
}
@RequiresApi(24)
fun stopTethering(type: Int, callback: (Exception) -> Unit) {
try {
stopTethering(type)
@@ -625,10 +612,7 @@ object TetheringManager {
@RequiresApi(30)
const val TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION = 14
val Intent.tetheredIfaces get() = getStringArrayListExtra(
if (Build.VERSION.SDK_INT >= 26) EXTRA_ACTIVE_TETHER else EXTRA_ACTIVE_TETHER_LEGACY)
val Intent.localOnlyTetheredIfaces get() = if (Build.VERSION.SDK_INT >= 26) {
getStringArrayListExtra(
if (Build.VERSION.SDK_INT >= 30) EXTRA_ACTIVE_LOCAL_ONLY else EXTRA_ACTIVE_LOCAL_ONLY_LEGACY)
} else emptyList<String>()
val Intent.tetheredIfaces get() = getStringArrayListExtra(EXTRA_ACTIVE_TETHER)
val Intent.localOnlyTetheredIfaces get() = getStringArrayListExtra(
if (Build.VERSION.SDK_INT >= 30) EXTRA_ACTIVE_LOCAL_ONLY else EXTRA_ACTIVE_LOCAL_ONLY_LEGACY)
}

View File

@@ -1,6 +1,5 @@
package be.mygod.vpnhotspot.net.monitor
import android.annotation.TargetApi
import android.net.ConnectivityManager
import android.net.LinkProperties
import android.net.Network
@@ -52,29 +51,10 @@ object DefaultNetworkMonitor : UpstreamMonitor() {
callback.onAvailable(currentLinkProperties)
}
} else {
when (Build.VERSION.SDK_INT) {
in 31..Int.MAX_VALUE -> @TargetApi(31) {
Services.connectivity.registerBestMatchingNetworkCallback(networkRequest, networkCallback,
Services.mainHandler)
}
in 28..30 -> @TargetApi(28) {
Services.connectivity.requestNetwork(networkRequest, networkCallback, Services.mainHandler)
}
in 26..27 -> @TargetApi(26) {
Services.connectivity.registerDefaultNetworkCallback(networkCallback, Services.mainHandler)
}
in 24..25 -> @TargetApi(24) {
Services.connectivity.registerDefaultNetworkCallback(networkCallback)
}
else -> try {
Services.connectivity.requestNetwork(networkRequest, networkCallback)
} catch (e: RuntimeException) {
// SecurityException would be thrown in requestNetwork on Android 6.0 thanks to Google's stupid bug
if (Build.VERSION.SDK_INT != 23) throw e
GlobalScope.launch { callback.onFallback() }
return
}
}
if (Build.VERSION.SDK_INT >= 31) {
Services.connectivity.registerBestMatchingNetworkCallback(networkRequest, networkCallback,
Services.mainHandler)
} else Services.connectivity.requestNetwork(networkRequest, networkCallback, Services.mainHandler)
registered = true
}
}

View File

@@ -77,7 +77,7 @@ class InterfaceMonitor(private val ifaceRegex: String) : UpstreamMonitor() {
callback.onAvailable(currentLinkProperties)
}
} else {
Services.registerNetworkCallbackCompat(request, networkCallback)
Services.registerNetworkCallback(request, networkCallback)
registered = true
}
}

View File

@@ -5,7 +5,6 @@ import androidx.core.content.edit
import be.mygod.librootkotlinx.RootServer
import be.mygod.librootkotlinx.isEBADF
import be.mygod.vpnhotspot.App.Companion.app
import be.mygod.vpnhotspot.BuildConfig
import be.mygod.vpnhotspot.R
import be.mygod.vpnhotspot.net.Routing
import be.mygod.vpnhotspot.root.ProcessData

View File

@@ -33,13 +33,12 @@ class TetherTimeoutMonitor(private val timeout: Long = 0,
private const val MIN_SOFT_AP_TIMEOUT_DELAY_MS = 600_000 // 10 minutes
@Deprecated("Use SoftApConfigurationCompat instead")
@get:RequiresApi(28)
val enabled get() = Settings.Global.getInt(app.contentResolver, SOFT_AP_TIMEOUT_ENABLED, 1) == 1
@Deprecated("Use SoftApConfigurationCompat instead")
suspend fun setEnabled(value: Boolean) = SettingsGlobalPut.int(SOFT_AP_TIMEOUT_ENABLED, if (value) 1 else 0)
val defaultTimeout: Int get() {
val delay = if (Build.VERSION.SDK_INT >= 28) try {
val delay = try {
if (Build.VERSION.SDK_INT < 30) Resources.getSystem().run {
getInteger(getIdentifier("config_wifi_framework_soft_ap_timeout_delay", "integer", "android"))
} else {
@@ -52,7 +51,7 @@ class TetherTimeoutMonitor(private val timeout: Long = 0,
} catch (e: RuntimeException) {
Timber.w(e)
MIN_SOFT_AP_TIMEOUT_DELAY_MS
} else MIN_SOFT_AP_TIMEOUT_DELAY_MS
}
return if (Build.VERSION.SDK_INT < 30 && delay < MIN_SOFT_AP_TIMEOUT_DELAY_MS) {
Timber.w("Overriding timeout delay with minimum limit value: $delay < $MIN_SOFT_AP_TIMEOUT_DELAY_MS")
MIN_SOFT_AP_TIMEOUT_DELAY_MS

View File

@@ -1,9 +1,9 @@
package be.mygod.vpnhotspot.net.monitor
import android.net.MacAddress
import androidx.collection.LongSparseArray
import androidx.collection.set
import be.mygod.vpnhotspot.net.IpDev
import be.mygod.vpnhotspot.net.MacAddressCompat
import be.mygod.vpnhotspot.net.Routing.Companion.IPTABLES
import be.mygod.vpnhotspot.room.AppDatabase
import be.mygod.vpnhotspot.room.TrafficRecord
@@ -11,7 +11,12 @@ import be.mygod.vpnhotspot.util.Event2
import be.mygod.vpnhotspot.util.RootSession
import be.mygod.vpnhotspot.util.parseNumericAddress
import be.mygod.vpnhotspot.widget.SmartSnackbar
import kotlinx.coroutines.*
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import timber.log.Timber
import java.net.InetAddress
import java.util.concurrent.TimeUnit
@@ -23,8 +28,8 @@ object TrafficRecorder {
private val records = mutableMapOf<IpDev, TrafficRecord>()
val foregroundListeners = Event2<Collection<TrafficRecord>, LongSparseArray<TrafficRecord>>()
fun register(ip: InetAddress, downstream: String, mac: MacAddressCompat) {
val record = TrafficRecord(mac = mac.addr, ip = ip, downstream = downstream)
fun register(ip: InetAddress, downstream: String, mac: MacAddress) {
val record = TrafficRecord(mac = mac, ip = ip, downstream = downstream)
AppDatabase.instance.trafficRecordDao.insert(record)
synchronized(this) {
val key = IpDev(ip, downstream)
@@ -156,5 +161,5 @@ object TrafficRecorder {
/**
* Possibly inefficient. Don't call this too often.
*/
fun isWorking(mac: MacAddressCompat) = records.values.any { it.mac == mac.addr }
fun isWorking(mac: MacAddress) = records.values.any { it.mac == mac }
}

View File

@@ -46,15 +46,6 @@ abstract class UpstreamMonitor {
* Called if some possibly stacked interface is available
*/
fun onAvailable(properties: LinkProperties? = null)
/**
* Called on API 23- from DefaultNetworkMonitor. This indicates that there isn't a good way of telling the
* default network (see DefaultNetworkMonitor) and we are using rules at priority 22000
* (RULE_PRIORITY_DEFAULT_NETWORK) as our fallback rules, which would work fine until Android 9.0 broke it in
* commit: https://android.googlesource.com/platform/system/netd/+/758627c4d93392190b08e9aaea3bbbfb92a5f364
*/
fun onFallback() {
throw UnsupportedOperationException()
}
}
val callbacks = mutableSetOf<Callback>()

View File

@@ -61,7 +61,7 @@ object VpnMonitor : UpstreamMonitor() {
callback.onAvailable(currentLinkProperties)
}
} else {
Services.registerNetworkCallbackCompat(request, networkCallback)
Services.registerNetworkCallback(request, networkCallback)
registered = true
}
}

View File

@@ -1,5 +1,6 @@
package be.mygod.vpnhotspot.net.wifi
import android.net.MacAddress
import android.net.wifi.p2p.WifiP2pGroup
import be.mygod.vpnhotspot.RepeaterService
import be.mygod.vpnhotspot.net.MacAddressCompat
@@ -53,8 +54,8 @@ class P2pSupplicantConfiguration(private val group: WifiP2pGroup? = null) {
var bssids = listOfNotNull(group?.owner?.deviceAddress, ownerAddress)
.distinct()
.filter {
val mac = MacAddress.fromString(it)
try {
val mac = MacAddressCompat.fromString(it)
mac != MacAddressCompat.ALL_ZEROS_ADDRESS && mac != MacAddressCompat.ANY_ADDRESS
} catch (_: IllegalArgumentException) {
false
@@ -126,7 +127,7 @@ class P2pSupplicantConfiguration(private val group: WifiP2pGroup? = null) {
add("\tmode=3")
add("\tdisabled=2")
add("}")
if (target == null) target = this
target = this
})
}
content = Content(result, target!!, persistentMacLine, legacy)
@@ -141,10 +142,10 @@ class P2pSupplicantConfiguration(private val group: WifiP2pGroup? = null) {
}
val psk by lazy { group?.passphrase ?: content.target.psk!! }
val bssid by lazy {
content.target.bssid?.let { MacAddressCompat.fromString(it) }
content.target.bssid?.let { MacAddress.fromString(it) }
}
suspend fun update(ssid: String, psk: String, bssid: MacAddressCompat?) {
suspend fun update(ssid: String, psk: String, bssid: MacAddress?) {
val (lines, block, persistentMacLine, legacy) = content
block[block.ssidLine!!] = "\tssid=" + ssid.toByteArray()
.joinToString("") { (it.toInt() and 255).toString(16).padStart(2, '0') }

View File

@@ -9,10 +9,9 @@ import android.os.Build
import android.os.Parcelable
import android.util.SparseIntArray
import androidx.annotation.RequiresApi
import be.mygod.vpnhotspot.BuildConfig
import be.mygod.vpnhotspot.net.MacAddressCompat
import be.mygod.vpnhotspot.net.MacAddressCompat.Companion.toCompat
import be.mygod.vpnhotspot.net.monitor.TetherTimeoutMonitor
import be.mygod.vpnhotspot.net.wifi.SoftApConfigurationCompat.Companion.requireSingleBand
import be.mygod.vpnhotspot.net.wifi.SoftApConfigurationCompat.Companion.setChannel
import be.mygod.vpnhotspot.util.ConstantLookup
import be.mygod.vpnhotspot.util.UnblockCentral
import kotlinx.parcelize.Parcelize
@@ -22,8 +21,7 @@ import java.lang.reflect.InvocationTargetException
@Parcelize
data class SoftApConfigurationCompat(
var ssid: String? = null,
@Deprecated("Workaround for using inline class with Parcelize, use bssid")
var bssidAddr: Long? = null,
var bssid: MacAddress? = null,
var passphrase: String? = null,
var isHiddenSsid: Boolean = false,
/**
@@ -31,14 +29,11 @@ data class SoftApConfigurationCompat(
* see also [android.net.wifi.WifiManager.isBridgedApConcurrencySupported].
* Otherwise, use [requireSingleBand] and [setChannel].
*/
@TargetApi(23)
var channels: SparseIntArray = SparseIntArray(1).apply { append(BAND_2GHZ, 0) },
var securityType: Int = SoftApConfiguration.SECURITY_TYPE_OPEN,
@TargetApi(30)
var maxNumberOfClients: Int = 0,
@TargetApi(28)
var isAutoShutdownEnabled: Boolean = true,
@TargetApi(28)
var shutdownTimeoutMillis: Long = 0,
@TargetApi(30)
var isClientControlByUserEnabled: Boolean = false,
@@ -166,7 +161,6 @@ data class SoftApConfigurationCompat(
*
* https://android.googlesource.com/platform/frameworks/base/+/android-6.0.0_r1/wifi/java/android/net/wifi/WifiConfiguration.java#242
*/
@get:RequiresApi(23)
@Suppress("DEPRECATION")
/**
* The band which AP resides on
@@ -174,7 +168,6 @@ data class SoftApConfigurationCompat(
* By default, 2G is chosen
*/
private val apBand by lazy { android.net.wifi.WifiConfiguration::class.java.getDeclaredField("apBand") }
@get:RequiresApi(23)
@Suppress("DEPRECATION")
/**
* The channel which AP resides on
@@ -356,17 +349,17 @@ data class SoftApConfigurationCompat(
@Suppress("DEPRECATION")
fun android.net.wifi.WifiConfiguration.toCompat() = SoftApConfigurationCompat(
SSID,
BSSID?.let { MacAddressCompat.fromString(it) }?.addr,
BSSID?.let { MacAddress.fromString(it) },
preSharedKey,
hiddenSSID,
// https://cs.android.com/android/platform/superproject/+/master:frameworks/base/wifi/java/android/net/wifi/SoftApConfToXmlMigrationUtil.java;l=87;drc=aa6527cf41671d1ed417b8ebdb6b3aa614f62344
SparseIntArray(1).also {
if (Build.VERSION.SDK_INT >= 23) it.append(when (val band = apBand.getInt(this)) {
it.append(when (val band = apBand.getInt(this)) {
0 -> BAND_2GHZ
1 -> BAND_5GHZ
-1 -> BAND_LEGACY
else -> throw IllegalArgumentException("Unexpected band $band")
}, apChannel.getInt(this)) else it.append(BAND_LEGACY, 0)
}, apChannel.getInt(this))
},
allowedKeyManagement.nextSetBit(0).let { selected ->
require(allowedKeyManagement.nextSetBit(selected + 1) < 0) {
@@ -389,14 +382,14 @@ data class SoftApConfigurationCompat(
}
}
},
isAutoShutdownEnabled = if (Build.VERSION.SDK_INT >= 28) TetherTimeoutMonitor.enabled else false,
isAutoShutdownEnabled = TetherTimeoutMonitor.enabled,
underlying = this)
@RequiresApi(30)
@Suppress("UNCHECKED_CAST")
fun SoftApConfiguration.toCompat() = SoftApConfigurationCompat(
ssid,
bssid?.toCompat()?.addr,
bssid,
passphrase,
isHiddenSsid,
if (Build.VERSION.SDK_INT >= 31) getChannels(this) as SparseIntArray else SparseIntArray(1).also {
@@ -466,13 +459,6 @@ data class SoftApConfigurationCompat(
setBridgedModeOpportunisticShutdownTimeoutMillis(staticBuilder, timeout)
}
@Suppress("DEPRECATION")
inline var bssid: MacAddressCompat?
get() = bssidAddr?.let { MacAddressCompat(it) }
set(value) {
bssidAddr = value?.addr
}
fun setChannel(channel: Int, band: Int = BAND_LEGACY) {
channels = SparseIntArray(1).apply {
append(when {
@@ -500,18 +486,15 @@ data class SoftApConfigurationCompat(
result.SSID = ssid
result.preSharedKey = passphrase
result.hiddenSSID = isHiddenSsid
if (Build.VERSION.SDK_INT >= 23) {
apBand.setInt(result, when (band) {
BAND_2GHZ -> 0
BAND_5GHZ -> 1
else -> {
require(Build.VERSION.SDK_INT >= 28) { "A band must be specified on this platform" }
require(isLegacyEitherBand(band)) { "Convert fail, unsupported band setting :$band" }
-1
}
})
apChannel.setInt(result, channel)
} else require(isLegacyEitherBand(band)) { "Specifying band is unsupported on this platform" }
apBand.setInt(result, when (band) {
BAND_2GHZ -> 0
BAND_5GHZ -> 1
else -> {
require(isLegacyEitherBand(band)) { "Convert fail, unsupported band setting :$band" }
-1
}
})
apChannel.setInt(result, channel)
if (original?.securityType != securityType) {
result.allowedKeyManagement.clear()
result.allowedKeyManagement.set(when (securityType) {
@@ -545,9 +528,8 @@ data class SoftApConfigurationCompat(
else -> passphrase
}, securityType)
setChannelsCompat(builder, channels)
setBssid(builder, bssid?.run {
if (Build.VERSION.SDK_INT >= 31 && macRandomizationSetting != RANDOMIZATION_NONE) null else toPlatform()
})
setBssid(builder,
if (Build.VERSION.SDK_INT < 31 || macRandomizationSetting == RANDOMIZATION_NONE) bssid else null)
setMaxNumberOfClients(builder, maxNumberOfClients)
try {
setShutdownTimeoutMillis(builder, shutdownTimeoutMillis)

View File

@@ -1,10 +1,10 @@
package be.mygod.vpnhotspot.net.wifi
import android.annotation.SuppressLint
import android.annotation.TargetApi
import android.content.ClipData
import android.content.ClipDescription
import android.content.DialogInterface
import android.net.MacAddress
import android.net.wifi.SoftApConfiguration
import android.os.Build
import android.os.Parcelable
@@ -31,7 +31,6 @@ import be.mygod.vpnhotspot.App.Companion.app
import be.mygod.vpnhotspot.R
import be.mygod.vpnhotspot.RepeaterService
import be.mygod.vpnhotspot.databinding.DialogWifiApBinding
import be.mygod.vpnhotspot.net.MacAddressCompat
import be.mygod.vpnhotspot.net.monitor.TetherTimeoutMonitor
import be.mygod.vpnhotspot.util.QRCodeDialog
import be.mygod.vpnhotspot.util.RangeInput
@@ -68,17 +67,12 @@ class WifiApDialogFragment : AlertDialogFragment<WifiApDialogFragment.Arg, WifiA
}
private val p2pSafeOptions by lazy { genAutoOptions(SoftApConfigurationCompat.BAND_LEGACY) + channels5G }
private val softApOptions by lazy {
when (Build.VERSION.SDK_INT) {
in 30..Int.MAX_VALUE -> {
genAutoOptions(SoftApConfigurationCompat.BAND_ANY_31) +
channels5G +
(1..253).map { ChannelOption(SoftApConfigurationCompat.BAND_6GHZ, it) } +
(1..6).map { ChannelOption(SoftApConfigurationCompat.BAND_60GHZ, it) }
}
in 28 until 30 -> p2pSafeOptions
else -> listOf(ChannelOption(SoftApConfigurationCompat.BAND_2GHZ),
ChannelOption(SoftApConfigurationCompat.BAND_5GHZ)) + channels5G
}
if (Build.VERSION.SDK_INT >= 30) {
genAutoOptions(SoftApConfigurationCompat.BAND_ANY_31) +
channels5G +
(1..253).map { ChannelOption(SoftApConfigurationCompat.BAND_6GHZ, it) } +
(1..6).map { ChannelOption(SoftApConfigurationCompat.BAND_60GHZ, it) }
} else p2pSafeOptions
}
@get:RequiresApi(30)
@@ -149,24 +143,24 @@ class WifiApDialogFragment : AlertDialogFragment<WifiApDialogFragment.Arg, WifiA
securityType = dialogView.security.selectedItemPosition
isHiddenSsid = dialogView.hiddenSsid.isChecked
}
if (full) @TargetApi(28) {
if (full) {
isAutoShutdownEnabled = dialogView.autoShutdown.isChecked
shutdownTimeoutMillis = dialogView.timeout.text.let { text ->
if (text.isNullOrEmpty()) 0 else text.toString().toLong()
}
if (Build.VERSION.SDK_INT >= 23 || arg.p2pMode) channels = generateChannels()
channels = generateChannels()
maxNumberOfClients = dialogView.maxClient.text.let { text ->
if (text.isNullOrEmpty()) 0 else text.toString().toInt()
}
isClientControlByUserEnabled = dialogView.clientUserControl.isChecked
allowedClientList = (dialogView.allowedList.text ?: "").split(nonMacChars)
.filter { it.isNotEmpty() }.map { MacAddressCompat.fromString(it).toPlatform() }
.filter { it.isNotEmpty() }.map(MacAddress::fromString)
blockedClientList = (dialogView.blockedList.text ?: "").split(nonMacChars)
.filter { it.isNotEmpty() }.map { MacAddressCompat.fromString(it).toPlatform() }
.filter { it.isNotEmpty() }.map(MacAddress::fromString)
macRandomizationSetting = dialogView.macRandomization.selectedItemPosition
bssid = if ((arg.p2pMode || Build.VERSION.SDK_INT < 31 && macRandomizationSetting ==
SoftApConfigurationCompat.RANDOMIZATION_NONE) && dialogView.bssid.length() != 0) {
MacAddressCompat.fromString(dialogView.bssid.text.toString())
MacAddress.fromString(dialogView.bssid.text.toString())
} else null
isBridgedModeOpportunisticShutdownEnabled = dialogView.bridgedModeOpportunisticShutdown.isChecked
isIeee80211axEnabled = dialogView.ieee80211ax.isChecked
@@ -177,7 +171,7 @@ class WifiApDialogFragment : AlertDialogFragment<WifiApDialogFragment.Arg, WifiA
}
vendorElements = VendorElements.deserialize(dialogView.vendorElements.text)
persistentRandomizedMacAddress = if (dialogView.persistentRandomizedMac.length() != 0) {
MacAddressCompat.fromString(dialogView.persistentRandomizedMac.text.toString()).toPlatform()
MacAddress.fromString(dialogView.persistentRandomizedMac.text.toString())
} else null
allowedAcsChannels = acsList.associate { (band, text, _) -> band to RangeInput.fromString(text.text) }
if (!arg.p2pMode && Build.VERSION.SDK_INT >= 33) {
@@ -227,7 +221,6 @@ class WifiApDialogFragment : AlertDialogFragment<WifiApDialogFragment.Arg, WifiA
}
}
if (!arg.readOnly) dialogView.password.addTextChangedListener(this@WifiApDialogFragment)
if (!arg.p2pMode && Build.VERSION.SDK_INT < 28) dialogView.autoShutdown.isGone = true
if (arg.p2pMode || Build.VERSION.SDK_INT >= 30) {
dialogView.timeoutWrapper.helperText = getString(R.string.wifi_hotspot_timeout_default,
TetherTimeoutMonitor.defaultTimeout)
@@ -239,12 +232,10 @@ class WifiApDialogFragment : AlertDialogFragment<WifiApDialogFragment.Arg, WifiA
}
if (!arg.readOnly) onItemSelectedListener = this@WifiApDialogFragment
}
if (Build.VERSION.SDK_INT >= 23 || arg.p2pMode) {
dialogView.bandPrimary.configure(currentChannels)
if (Build.VERSION.SDK_INT >= 31 && !arg.p2pMode) {
dialogView.bandSecondary.configure(listOf(ChannelOption.Disabled) + currentChannels)
} else dialogView.bandSecondary.isGone = true
} else dialogView.bandGroup.isGone = true
dialogView.bandPrimary.configure(currentChannels)
if (Build.VERSION.SDK_INT >= 31 && !arg.p2pMode) {
dialogView.bandSecondary.configure(listOf(ChannelOption.Disabled) + currentChannels)
} else dialogView.bandSecondary.isGone = true
if (arg.p2pMode || Build.VERSION.SDK_INT < 30) dialogView.accessControlGroup.isGone = true
else if (!arg.readOnly) {
dialogView.maxClient.addTextChangedListener(this@WifiApDialogFragment)
@@ -307,11 +298,9 @@ class WifiApDialogFragment : AlertDialogFragment<WifiApDialogFragment.Arg, WifiA
dialogView.password.setText(base.passphrase)
dialogView.autoShutdown.isChecked = base.isAutoShutdownEnabled
dialogView.timeout.setText(base.shutdownTimeoutMillis.let { if (it <= 0) "" else it.toString() })
if (Build.VERSION.SDK_INT >= 23 || arg.p2pMode) {
dialogView.bandPrimary.setSelection(locate(0))
if (Build.VERSION.SDK_INT >= 31 && !arg.p2pMode) {
dialogView.bandSecondary.setSelection(if (base.channels.size() > 1) locate(1) + 1 else 0)
}
dialogView.bandPrimary.setSelection(locate(0))
if (Build.VERSION.SDK_INT >= 31 && !arg.p2pMode) {
dialogView.bandSecondary.setSelection(if (base.channels.size() > 1) locate(1) + 1 else 0)
}
dialogView.bssid.setText(base.bssid?.toString())
dialogView.hiddenSsid.isChecked = base.isHiddenSsid
@@ -343,7 +332,6 @@ class WifiApDialogFragment : AlertDialogFragment<WifiApDialogFragment.Arg, WifiA
validate()
}
@TargetApi(28)
private fun validate() {
if (!started) return
val ssidLength = dialogView.ssid.text.toString().toByteArray().size
@@ -389,10 +377,8 @@ class WifiApDialogFragment : AlertDialogFragment<WifiApDialogFragment.Arg, WifiA
dialogView.bssidWrapper.isGone = hideBssid
dialogView.bssidWrapper.error = null
val bssidValid = hideBssid || dialogView.bssid.length() == 0 || try {
val mac = MacAddressCompat.fromString(dialogView.bssid.text.toString())
if (Build.VERSION.SDK_INT >= 30 && !arg.p2pMode) {
SoftApConfigurationCompat.testPlatformValidity(mac.toPlatform())
}
val mac = MacAddress.fromString(dialogView.bssid.text.toString())
if (Build.VERSION.SDK_INT >= 30 && !arg.p2pMode) SoftApConfigurationCompat.testPlatformValidity(mac)
true
} catch (e: Exception) {
dialogView.bssidWrapper.error = e.readableMessage
@@ -409,15 +395,15 @@ class WifiApDialogFragment : AlertDialogFragment<WifiApDialogFragment.Arg, WifiA
dialogView.maxClientWrapper.error = maxClientError
val listsNoError = if (Build.VERSION.SDK_INT >= 30) {
val (blockedList, blockedListError) = try {
(dialogView.blockedList.text ?: "").split(nonMacChars)
.filter { it.isNotEmpty() }.map { MacAddressCompat.fromString(it).toPlatform() }.toSet() to null
(dialogView.blockedList.text ?: "").split(nonMacChars).filter { it.isNotEmpty() }
.map(MacAddress::fromString).toSet() to null
} catch (e: IllegalArgumentException) {
null to e.readableMessage
}
dialogView.blockedListWrapper.error = blockedListError
val allowedListError = try {
(dialogView.allowedList.text ?: "").split(nonMacChars).filter { it.isNotEmpty() }.forEach {
val mac = MacAddressCompat.fromString(it).toPlatform()
val mac = MacAddress.fromString(it)
require(blockedList?.contains(mac) != true) { "A MAC address exists in both client lists" }
}
null
@@ -449,9 +435,9 @@ class WifiApDialogFragment : AlertDialogFragment<WifiApDialogFragment.Arg, WifiA
dialogView.vendorElementsWrapper.error = vendorElementsError
dialogView.persistentRandomizedMacWrapper.error = null
val persistentRandomizedMacValid = dialogView.persistentRandomizedMac.length() == 0 || try {
MacAddressCompat.fromString(dialogView.persistentRandomizedMac.text.toString())
MacAddress.fromString(dialogView.persistentRandomizedMac.text.toString())
true
} catch (e: Exception) {
} catch (e: IllegalArgumentException) {
dialogView.persistentRandomizedMacWrapper.error = e.readableMessage
false
}
@@ -496,9 +482,7 @@ class WifiApDialogFragment : AlertDialogFragment<WifiApDialogFragment.Arg, WifiA
android.R.id.copy -> try {
app.clipboard.setPrimaryClip(ClipData.newPlainText(null,
Base64.encodeToString(generateConfig().toByteArray(), BASE64_FLAGS)).apply {
if (Build.VERSION.SDK_INT >= 24) {
description.extras = persistableBundleOf(ClipDescription.EXTRA_IS_SENSITIVE to true)
}
description.extras = persistableBundleOf(ClipDescription.EXTRA_IS_SENSITIVE to true)
})
true
} catch (e: RuntimeException) {

View File

@@ -88,7 +88,6 @@ object WifiApManager {
*
* Source: https://android.googlesource.com/platform/frameworks/base/+/android-6.0.0_r1/wifi/java/android/net/wifi/WifiManager.java#210
*/
@get:RequiresApi(23)
val EXTRA_WIFI_AP_FAILURE_REASON get() =
if (Build.VERSION.SDK_INT >= 30) "android.net.wifi.extra.WIFI_AP_FAILURE_REASON" else "wifi_ap_error_code"
/**
@@ -98,7 +97,6 @@ object WifiApManager {
*
* Source: https://android.googlesource.com/platform/frameworks/base/+/android-8.0.0_r1/wifi/java/android/net/wifi/WifiManager.java#413
*/
@get:RequiresApi(26)
val EXTRA_WIFI_AP_INTERFACE_NAME get() =
if (Build.VERSION.SDK_INT >= 30) "android.net.wifi.extra.WIFI_AP_INTERFACE_NAME" else "wifi_ap_interface_name"
@@ -177,7 +175,6 @@ object WifiApManager {
setWifiApConfiguration(Services.wifi, value) as Boolean
fun setConfiguration(value: SoftApConfiguration) = setSoftApConfiguration(Services.wifi, value) as Boolean
@RequiresApi(28)
interface SoftApCallbackCompat {
/**
* Called when soft AP state changes.
@@ -239,7 +236,6 @@ object WifiApManager {
@RequiresApi(30)
fun onBlockedClientConnecting(client: Parcelable, blockedReason: Int) { }
}
@RequiresApi(23)
val failureReasonLookup = ConstantLookup<WifiManager>("SAP_START_FAILURE_", "GENERAL", "NO_CHANNEL")
@get:RequiresApi(30)
val clientBlockLookup by lazy { ConstantLookup<WifiManager>("SAP_CLIENT_") }
@@ -255,7 +251,6 @@ object WifiApManager {
WifiManager::class.java.getDeclaredMethod("unregisterSoftApCallback", interfaceSoftApCallback)
}
@RequiresApi(28)
fun registerSoftApCallback(callback: SoftApCallbackCompat, executor: Executor): Any {
val proxy = Proxy.newProxyInstance(interfaceSoftApCallback.classLoader,
arrayOf(interfaceSoftApCallback), object : InvocationHandler {
@@ -270,7 +265,7 @@ object WifiApManager {
method.matches("onStateChanged", Integer.TYPE, Integer.TYPE) -> {
callback.onStateChanged(args!![0] as Int, args[1] as Int)
}
method.matches("onNumClientsChanged", Integer.TYPE) -> @Suppress("DEPRECATION") {
method.matches("onNumClientsChanged", Integer.TYPE) -> {
if (Build.VERSION.SDK_INT >= 30) Timber.w(Exception("Unexpected onNumClientsChanged"))
callback.onNumClientsChanged(args!![0] as Int)
}
@@ -307,7 +302,6 @@ object WifiApManager {
} else registerSoftApCallback(Services.wifi, proxy, null)
return proxy
}
@RequiresApi(28)
fun unregisterSoftApCallback(key: Any) = unregisterSoftApCallback(Services.wifi, key)
private val cancelLocalOnlyHotspotRequest by lazy {
@@ -317,43 +311,5 @@ object WifiApManager {
* This is the only way to unregister requests besides app exiting.
* Therefore, we are happy with crashing the app if reflection fails.
*/
@RequiresApi(26)
fun cancelLocalOnlyHotspotRequest() = cancelLocalOnlyHotspotRequest(Services.wifi)
@Suppress("DEPRECATION")
private val setWifiApEnabled by lazy {
WifiManager::class.java.getDeclaredMethod("setWifiApEnabled",
android.net.wifi.WifiConfiguration::class.java, Boolean::class.java)
}
/**
* Start AccessPoint mode with the specified
* configuration. If the radio is already running in
* AP mode, update the new configuration
* Note that starting in access point mode disables station
* mode operation
* @param wifiConfig SSID, security and channel details as
* part of WifiConfiguration
* @return {@code true} if the operation succeeds, {@code false} otherwise
*/
@Suppress("DEPRECATION")
private fun WifiManager.setWifiApEnabled(wifiConfig: android.net.wifi.WifiConfiguration?, enabled: Boolean) =
setWifiApEnabled(this, wifiConfig, enabled) as Boolean
/**
* Although the functionalities were removed in API 26, it is already not functioning correctly on API 25.
*
* See also: https://android.googlesource.com/platform/frameworks/base/+/5c0b10a4a9eecc5307bb89a271221f2b20448797%5E%21/
*/
@Suppress("DEPRECATION")
@Deprecated("Not usable since API 26, malfunctioning on API 25")
fun start(wifiConfig: android.net.wifi.WifiConfiguration? = null) {
Services.wifi.isWifiEnabled = false
Services.wifi.setWifiApEnabled(wifiConfig, true)
}
@Suppress("DEPRECATION")
@Deprecated("Not usable since API 26")
fun stop() {
Services.wifi.setWifiApEnabled(null, false)
Services.wifi.isWifiEnabled = true
}
}

View File

@@ -1,6 +1,7 @@
package be.mygod.vpnhotspot.net.wifi
import android.annotation.SuppressLint
import android.net.MacAddress
import android.net.wifi.ScanResult
import android.net.wifi.WpsInfo
import android.net.wifi.p2p.WifiP2pGroup
@@ -9,9 +10,8 @@ import android.net.wifi.p2p.WifiP2pManager
import androidx.annotation.RequiresApi
import be.mygod.vpnhotspot.App.Companion.app
import be.mygod.vpnhotspot.net.MacAddressCompat
import be.mygod.vpnhotspot.net.wifi.WifiP2pManagerHelper.setWifiP2pChannels
import be.mygod.vpnhotspot.util.callSuper
import be.mygod.vpnhotspot.util.matchesCompat
import be.mygod.vpnhotspot.util.matches
import kotlinx.coroutines.CompletableDeferred
import java.lang.reflect.InvocationHandler
import java.lang.reflect.Method
@@ -128,7 +128,7 @@ object WifiP2pManagerHelper {
requestPersistentGroupInfo(this, c, Proxy.newProxyInstance(interfacePersistentGroupInfoListener.classLoader,
arrayOf(interfacePersistentGroupInfoListener), object : InvocationHandler {
override fun invoke(proxy: Any, method: Method, args: Array<out Any?>?): Any? = when {
method.matchesCompat("onPersistentGroupInfoAvailable", args, classWifiP2pGroupList) -> {
method.matches("onPersistentGroupInfoAvailable", classWifiP2pGroupList) -> {
@Suppress("UNCHECKED_CAST")
result.complete(getGroupList(args!![0]) as Collection<WifiP2pGroup>)
}
@@ -142,11 +142,11 @@ object WifiP2pManagerHelper {
CompletableDeferred<WifiP2pInfo?>().apply { requestConnectionInfo(c) { complete(it) } }.await()
@SuppressLint("MissingPermission") // missing permission simply leads to null result
@RequiresApi(29)
suspend fun WifiP2pManager.requestDeviceAddress(c: WifiP2pManager.Channel): MacAddressCompat? {
suspend fun WifiP2pManager.requestDeviceAddress(c: WifiP2pManager.Channel): MacAddress? {
val future = CompletableDeferred<String?>()
requestDeviceInfo(c) { future.complete(it?.deviceAddress) }
return future.await()?.let {
val address = if (it.isEmpty()) null else MacAddressCompat.fromString(it)
val address = if (it.isEmpty()) null else MacAddress.fromString(it)
if (address == MacAddressCompat.ANY_ADDRESS) null else address
}
}