Bump to minapi 28
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
|
||||
@@ -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>()
|
||||
|
||||
@@ -61,7 +61,7 @@ object VpnMonitor : UpstreamMonitor() {
|
||||
callback.onAvailable(currentLinkProperties)
|
||||
}
|
||||
} else {
|
||||
Services.registerNetworkCallbackCompat(request, networkCallback)
|
||||
Services.registerNetworkCallback(request, networkCallback)
|
||||
registered = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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') }
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user