@@ -1,24 +1,20 @@
|
||||
package be.mygod.vpnhotspot.net.wifi
|
||||
|
||||
import android.net.wifi.p2p.WifiP2pGroup
|
||||
import android.os.Build
|
||||
import be.mygod.vpnhotspot.App.Companion.app
|
||||
import be.mygod.vpnhotspot.RepeaterService
|
||||
import be.mygod.vpnhotspot.net.MacAddressCompat
|
||||
import be.mygod.vpnhotspot.util.RootSession
|
||||
import be.mygod.vpnhotspot.root.RepeaterCommands
|
||||
import be.mygod.vpnhotspot.root.RootManager
|
||||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* This parser is based on:
|
||||
* https://android.googlesource.com/platform/external/wpa_supplicant_8/+/d2986c2/wpa_supplicant/config.c#488
|
||||
* https://android.googlesource.com/platform/external/wpa_supplicant_8/+/6fa46df/wpa_supplicant/config_file.c#182
|
||||
*/
|
||||
class P2pSupplicantConfiguration(private val group: WifiP2pGroup? = null, ownerAddress: String? = null) {
|
||||
class P2pSupplicantConfiguration(private val group: WifiP2pGroup? = null) {
|
||||
companion object {
|
||||
private const val TAG = "P2pSupplicantConfiguration"
|
||||
private const val CONF_PATH_TREBLE = "/data/vendor/wifi/wpa/p2p_supplicant.conf"
|
||||
private const val CONF_PATH_LEGACY = "/data/misc/wifi/p2p_supplicant.conf"
|
||||
private const val PERSISTENT_MAC = "p2p_device_persistent_mac_addr="
|
||||
private val networkParser =
|
||||
"^(bssid=(([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2})|psk=(ext:|\"(.*)\"|[0-9a-fA-F]{64}\$)?)".toRegex()
|
||||
@@ -36,12 +32,11 @@ class P2pSupplicantConfiguration(private val group: WifiP2pGroup? = null, ownerA
|
||||
override fun toString() = joinToString("\n")
|
||||
}
|
||||
|
||||
private class Parser(val lines: List<String>) {
|
||||
private val iterator = lines.iterator()
|
||||
private class Parser(val lines: Iterator<String>) {
|
||||
lateinit var line: String
|
||||
lateinit var trimmed: String
|
||||
fun next() = if (iterator.hasNext()) {
|
||||
line = iterator.next().apply { trimmed = trimStart('\r', '\t', ' ') }
|
||||
fun next() = if (lines.hasNext()) {
|
||||
line = lines.next().apply { trimmed = trimStart('\r', '\t', ' ') }
|
||||
true
|
||||
} else false
|
||||
}
|
||||
@@ -49,14 +44,12 @@ class P2pSupplicantConfiguration(private val group: WifiP2pGroup? = null, ownerA
|
||||
private data class Content(val lines: ArrayList<Any>, var target: NetworkBlock, var persistentMacLine: Int?,
|
||||
var legacy: Boolean)
|
||||
|
||||
private val content = RootSession.use {
|
||||
private lateinit var content: Content
|
||||
suspend fun init(ownerAddress: String? = null) {
|
||||
val result = ArrayList<Any>()
|
||||
var target: NetworkBlock? = null
|
||||
var persistentMacLine: Int? = null
|
||||
val command = "cat $CONF_PATH_TREBLE || cat $CONF_PATH_LEGACY"
|
||||
val shell = it.execQuiet(command)
|
||||
RootSession.checkOutput(command, shell, false, false)
|
||||
val parser = Parser(shell.out)
|
||||
val (config, legacy) = RootManager.use { it.execute(RepeaterCommands.ReadP2pConfig()) }
|
||||
try {
|
||||
var bssids = listOfNotNull(group?.owner?.deviceAddress, ownerAddress)
|
||||
.distinct()
|
||||
@@ -68,6 +61,7 @@ class P2pSupplicantConfiguration(private val group: WifiP2pGroup? = null, ownerA
|
||||
false
|
||||
}
|
||||
}
|
||||
val parser = Parser(config.lineSequence().iterator())
|
||||
while (parser.next()) {
|
||||
if (parser.trimmed.startsWith("network={")) {
|
||||
val block = NetworkBlock()
|
||||
@@ -129,22 +123,22 @@ class P2pSupplicantConfiguration(private val group: WifiP2pGroup? = null, ownerA
|
||||
if (target == null) target = this
|
||||
})
|
||||
}
|
||||
Content(result, target!!.apply {
|
||||
content = Content(result, target!!.apply {
|
||||
RepeaterService.lastMac = bssid!!
|
||||
}, persistentMacLine, shell.err.isNotEmpty())
|
||||
} catch (e: RuntimeException) {
|
||||
}, persistentMacLine, legacy)
|
||||
} catch (e: Exception) {
|
||||
FirebaseCrashlytics.getInstance().apply {
|
||||
setCustomKey(TAG, parser.lines.joinToString("\n"))
|
||||
setCustomKey(TAG, config)
|
||||
setCustomKey("$TAG.ownerAddress", ownerAddress.toString())
|
||||
setCustomKey("$TAG.p2pGroup", group.toString())
|
||||
}
|
||||
throw e
|
||||
}
|
||||
}
|
||||
val psk = group?.passphrase ?: content.target.psk!!
|
||||
val bssid = MacAddressCompat.fromString(content.target.bssid!!)
|
||||
val psk by lazy { group?.passphrase ?: content.target.psk!! }
|
||||
val bssid by lazy { MacAddressCompat.fromString(content.target.bssid!!) }
|
||||
|
||||
fun update(ssid: String, psk: String, bssid: MacAddressCompat?) {
|
||||
suspend fun update(ssid: String, psk: String, bssid: MacAddressCompat?) {
|
||||
val (lines, block, persistentMacLine, legacy) = content
|
||||
block[block.ssidLine!!] = "\tssid=" + ssid.toByteArray()
|
||||
.joinToString("") { (it.toInt() and 255).toString(16).padStart(2, '0') }
|
||||
@@ -153,25 +147,6 @@ class P2pSupplicantConfiguration(private val group: WifiP2pGroup? = null, ownerA
|
||||
persistentMacLine?.let { lines[it] = PERSISTENT_MAC + bssid }
|
||||
block[block.bssidLine!!] = "\tbssid=$bssid"
|
||||
}
|
||||
val tempFile = File.createTempFile("vpnhotspot-", ".conf", app.deviceStorage.cacheDir)
|
||||
try {
|
||||
tempFile.printWriter().use { writer ->
|
||||
lines.forEach { writer.println(it) }
|
||||
}
|
||||
// pkill not available on Lollipop. Source: https://android.googlesource.com/platform/system/core/+/master/shell_and_utilities/README.md
|
||||
RootSession.use {
|
||||
it.exec("cat ${tempFile.absolutePath} > ${if (legacy) CONF_PATH_LEGACY else CONF_PATH_TREBLE}")
|
||||
if (Build.VERSION.SDK_INT >= 23) it.exec("pkill wpa_supplicant") else {
|
||||
val result = try {
|
||||
it.execOut("ps | grep wpa_supplicant").split(whitespaceMatcher).apply { check(size >= 2) }
|
||||
} catch (e: Exception) {
|
||||
throw IllegalStateException("wpa_supplicant not found, please toggle Airplane mode manually", e)
|
||||
}
|
||||
it.exec("kill ${result[1]}")
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if (!tempFile.delete()) tempFile.deleteOnExit()
|
||||
}
|
||||
RootManager.use { it.execute(RepeaterCommands.WriteP2pConfig(lines.joinToString("\n"), legacy)) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,10 +38,10 @@ data class SoftApConfigurationCompat(
|
||||
/**
|
||||
* TODO
|
||||
*/
|
||||
const val BAND_ANY = -1
|
||||
const val BAND_2GHZ = 0
|
||||
const val BAND_5GHZ = 1
|
||||
const val BAND_6GHZ = 2
|
||||
const val BAND_ANY = 0
|
||||
const val BAND_2GHZ = 1
|
||||
const val BAND_5GHZ = 2
|
||||
const val BAND_6GHZ = 3
|
||||
const val CH_INVALID = 0
|
||||
|
||||
// TODO: localize?
|
||||
@@ -144,7 +144,9 @@ data class SoftApConfigurationCompat(
|
||||
classBuilder.getDeclaredMethod("setBssid", MacAddress::class.java)
|
||||
}
|
||||
@get:RequiresApi(30)
|
||||
private val setChannel by lazy { classBuilder.getDeclaredMethod("setChannel", Int::class.java) }
|
||||
private val setChannel by lazy {
|
||||
classBuilder.getDeclaredMethod("setChannel", Int::class.java, Int::class.java)
|
||||
}
|
||||
@get:RequiresApi(30)
|
||||
private val setClientControlByUserEnabled by lazy {
|
||||
classBuilder.getDeclaredMethod("setClientControlByUserEnabled", Boolean::class.java)
|
||||
@@ -156,7 +158,9 @@ data class SoftApConfigurationCompat(
|
||||
classBuilder.getDeclaredMethod("setMaxNumberOfClients", Int::class.java)
|
||||
}
|
||||
@get:RequiresApi(30)
|
||||
private val setPassphrase by lazy { classBuilder.getDeclaredMethod("setPassphrase", String::class.java) }
|
||||
private val setPassphrase by lazy {
|
||||
classBuilder.getDeclaredMethod("setPassphrase", String::class.java, Int::class.java)
|
||||
}
|
||||
@get:RequiresApi(30)
|
||||
private val setShutdownTimeoutMillis by lazy {
|
||||
classBuilder.getDeclaredMethod("setShutdownTimeoutMillis", Long::class.java)
|
||||
@@ -186,7 +190,7 @@ data class SoftApConfigurationCompat(
|
||||
}
|
||||
},
|
||||
preSharedKey,
|
||||
if (Build.VERSION.SDK_INT >= 23) apBand.getInt(this) else BAND_ANY, // TODO
|
||||
if (Build.VERSION.SDK_INT >= 23) apBand.getInt(this) + 1 else BAND_ANY, // TODO
|
||||
if (Build.VERSION.SDK_INT >= 23) apChannel.getInt(this) else CH_INVALID, // TODO
|
||||
BSSID?.let { MacAddressCompat.fromString(it) }?.addr,
|
||||
0, // TODO: unsupported field should have @RequiresApi?
|
||||
@@ -275,10 +279,10 @@ data class SoftApConfigurationCompat(
|
||||
// TODO: can we always call copy constructor?
|
||||
val builder = if (sac == null) classBuilder.newInstance() else newBuilder.newInstance(sac)
|
||||
setSsid(builder, ssid)
|
||||
// TODO: setSecurityType
|
||||
setPassphrase(builder, passphrase)
|
||||
setBand(builder, band)
|
||||
setChannel(builder, channel)
|
||||
setPassphrase(builder, passphrase, securityType)
|
||||
// TODO: how to use these?
|
||||
// setBand(builder, band)
|
||||
// setChannel(builder, band, channel)
|
||||
setBssid(builder, bssid?.toPlatform())
|
||||
setMaxNumberOfClients(builder, maxNumberOfClients)
|
||||
setShutdownTimeoutMillis(builder, shutdownTimeoutMillis)
|
||||
|
||||
@@ -5,8 +5,8 @@ import android.net.wifi.SoftApConfiguration
|
||||
import android.net.wifi.WifiManager
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import be.mygod.vpnhotspot.App.Companion.app
|
||||
import be.mygod.vpnhotspot.net.wifi.SoftApConfigurationCompat.Companion.toCompat
|
||||
import be.mygod.vpnhotspot.util.Services
|
||||
|
||||
object WifiApManager {
|
||||
private val getWifiApConfiguration by lazy { WifiManager::class.java.getDeclaredMethod("getWifiApConfiguration") }
|
||||
@@ -22,22 +22,18 @@ object WifiApManager {
|
||||
WifiManager::class.java.getDeclaredMethod("setSoftApConfiguration", SoftApConfiguration::class.java)
|
||||
}
|
||||
|
||||
var configuration: SoftApConfigurationCompat
|
||||
get() = if (Build.VERSION.SDK_INT < 30) @Suppress("DEPRECATION") {
|
||||
(getWifiApConfiguration(app.wifi) as android.net.wifi.WifiConfiguration?)?.toCompat()
|
||||
?: SoftApConfigurationCompat.empty()
|
||||
} else (getSoftApConfiguration(app.wifi) as SoftApConfiguration).toCompat()
|
||||
set(value) = if (Build.VERSION.SDK_INT < 30) @Suppress("DEPRECATION") {
|
||||
require(setWifiApConfiguration(app.wifi,
|
||||
value.toWifiConfiguration()) as Boolean) { "setWifiApConfiguration failed" }
|
||||
} else require(setSoftApConfiguration(app.wifi, value.toPlatform()) as Boolean) {
|
||||
"setSoftApConfiguration failed"
|
||||
}
|
||||
val configuration get() = if (Build.VERSION.SDK_INT < 30) @Suppress("DEPRECATION") {
|
||||
(getWifiApConfiguration(Services.wifi) as android.net.wifi.WifiConfiguration?)?.toCompat()
|
||||
?: SoftApConfigurationCompat.empty()
|
||||
} else (getSoftApConfiguration(Services.wifi) as SoftApConfiguration).toCompat()
|
||||
fun setConfiguration(value: SoftApConfigurationCompat) = (if (Build.VERSION.SDK_INT < 30) @Suppress("DEPRECATION") {
|
||||
setWifiApConfiguration(Services.wifi, value.toWifiConfiguration())
|
||||
} else setSoftApConfiguration(Services.wifi, value.toPlatform())) as Boolean
|
||||
|
||||
private val cancelLocalOnlyHotspotRequest by lazy {
|
||||
WifiManager::class.java.getDeclaredMethod("cancelLocalOnlyHotspotRequest")
|
||||
}
|
||||
fun cancelLocalOnlyHotspotRequest() = cancelLocalOnlyHotspotRequest(app.wifi)
|
||||
fun cancelLocalOnlyHotspotRequest() = cancelLocalOnlyHotspotRequest(Services.wifi)
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
private val setWifiApEnabled by lazy {
|
||||
@@ -66,13 +62,13 @@ object WifiApManager {
|
||||
@Suppress("DEPRECATION")
|
||||
@Deprecated("Not usable since API 26, malfunctioning on API 25")
|
||||
fun start(wifiConfig: android.net.wifi.WifiConfiguration? = null) {
|
||||
app.wifi.isWifiEnabled = false
|
||||
app.wifi.setWifiApEnabled(wifiConfig, true)
|
||||
Services.wifi.isWifiEnabled = false
|
||||
Services.wifi.setWifiApEnabled(wifiConfig, true)
|
||||
}
|
||||
@Suppress("DEPRECATION")
|
||||
@Deprecated("Not usable since API 26")
|
||||
fun stop() {
|
||||
app.wifi.setWifiApEnabled(null, false)
|
||||
app.wifi.isWifiEnabled = true
|
||||
Services.wifi.setWifiApEnabled(null, false)
|
||||
Services.wifi.isWifiEnabled = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import androidx.core.content.getSystemService
|
||||
import androidx.lifecycle.DefaultLifecycleObserver
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import be.mygod.vpnhotspot.App.Companion.app
|
||||
import be.mygod.vpnhotspot.util.Services
|
||||
|
||||
/**
|
||||
* This mechanism is used to maximize profit. Source: https://stackoverflow.com/a/29657230/2245107
|
||||
@@ -91,7 +92,7 @@ class WifiDoubleLock(lockType: Int) : AutoCloseable {
|
||||
override fun onDestroy(owner: LifecycleOwner) = app.pref.unregisterOnSharedPreferenceChangeListener(this)
|
||||
}
|
||||
|
||||
private val wifi = app.wifi.createWifiLock(lockType, "vpnhotspot:wifi").apply { acquire() }
|
||||
private val wifi = Services.wifi.createWifiLock(lockType, "vpnhotspot:wifi").apply { acquire() }
|
||||
@SuppressLint("WakelockTimeout")
|
||||
private val power = service.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "vpnhotspot:power").apply { acquire() }
|
||||
|
||||
|
||||
Reference in New Issue
Block a user