Fix requestPersistentGroupInfo and deletePersistentGroup permissions
This commit is contained in:
@@ -43,10 +43,12 @@ Installing as system app also has the side benefit of launching root daemon less
|
|||||||
|
|
||||||
* `android.permission.LOCAL_MAC_ADDRESS`
|
* `android.permission.LOCAL_MAC_ADDRESS`
|
||||||
* `android.permission.MANAGE_USB`
|
* `android.permission.MANAGE_USB`
|
||||||
|
* `android.permission.OVERRIDE_WIFI_CONFIG`
|
||||||
|
* `android.permission.READ_WIFI_CREDENTIAL`
|
||||||
* `android.permission.TETHER_PRIVILEGED`
|
* `android.permission.TETHER_PRIVILEGED`
|
||||||
* `android.permission.WRITE_SECURE_SETTINGS`
|
* `android.permission.WRITE_SECURE_SETTINGS`
|
||||||
|
|
||||||
Whenever you install an app update, if there was a new protected permission addition (last updated in v2.10.2), you should update the app installed in system as well to make the system grant the privileged permission.
|
Whenever you install an app update, if there was a new protected permission addition (last updated in v2.10.4), you should update the app installed in system as well to make the system grant the privileged permission.
|
||||||
|
|
||||||
## Settings and How to Use Them
|
## Settings and How to Use Them
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ buildscript {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath(kotlin("gradle-plugin", kotlinVersion))
|
classpath(kotlin("gradle-plugin", kotlinVersion))
|
||||||
classpath("com.android.tools.build:gradle:4.1.0-beta01")
|
classpath("com.android.tools.build:gradle:4.1.0-beta02")
|
||||||
classpath("com.github.ben-manes:gradle-versions-plugin:0.28.0")
|
classpath("com.github.ben-manes:gradle-versions-plugin:0.28.0")
|
||||||
classpath("com.google.firebase:firebase-crashlytics-gradle:2.2.0")
|
classpath("com.google.firebase:firebase-crashlytics-gradle:2.2.0")
|
||||||
classpath("com.google.android.gms:oss-licenses-plugin:0.10.2")
|
classpath("com.google.android.gms:oss-licenses-plugin:0.10.2")
|
||||||
|
|||||||
@@ -38,6 +38,8 @@
|
|||||||
tools:ignore="ProtectedPermissions"/>
|
tools:ignore="ProtectedPermissions"/>
|
||||||
<uses-permission android:name="android.permission.OVERRIDE_WIFI_CONFIG"
|
<uses-permission android:name="android.permission.OVERRIDE_WIFI_CONFIG"
|
||||||
tools:ignore="ProtectedPermissions"/>
|
tools:ignore="ProtectedPermissions"/>
|
||||||
|
<uses-permission android:name="android.permission.READ_WIFI_CREDENTIAL"
|
||||||
|
tools:ignore="ProtectedPermissions"/>
|
||||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
|
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
|
||||||
<uses-permission android:name="android.permission.TETHER_PRIVILEGED"
|
<uses-permission android:name="android.permission.TETHER_PRIVILEGED"
|
||||||
tools:ignore="ProtectedPermissions"/>
|
tools:ignore="ProtectedPermissions"/>
|
||||||
|
|||||||
@@ -179,6 +179,27 @@ data class ParcelableSize(val value: Size) : Parcelable
|
|||||||
@Parcelize
|
@Parcelize
|
||||||
data class ParcelableSizeF(val value: SizeF) : Parcelable
|
data class ParcelableSizeF(val value: SizeF) : Parcelable
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
data class ParcelableArray(val value: Array<Parcelable?>) : Parcelable {
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (javaClass != other?.javaClass) return false
|
||||||
|
|
||||||
|
other as ParcelableArray
|
||||||
|
|
||||||
|
if (!value.contentEquals(other.value)) return false
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
return value.contentHashCode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
data class ParcelableList(val value: List<Parcelable?>) : Parcelable
|
||||||
|
|
||||||
@SuppressLint("Recycle")
|
@SuppressLint("Recycle")
|
||||||
inline fun <T> useParcel(block: (Parcel) -> T) = Parcel.obtain().run {
|
inline fun <T> useParcel(block: (Parcel) -> T) = Parcel.obtain().run {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import android.annotation.TargetApi
|
|||||||
import android.app.Service
|
import android.app.Service
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
|
import android.content.pm.PackageManager
|
||||||
import android.net.wifi.WpsInfo
|
import android.net.wifi.WpsInfo
|
||||||
import android.net.wifi.p2p.*
|
import android.net.wifi.p2p.*
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
@@ -32,6 +33,7 @@ import kotlinx.coroutines.*
|
|||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.lang.reflect.InvocationTargetException
|
import java.lang.reflect.InvocationTargetException
|
||||||
import java.net.NetworkInterface
|
import java.net.NetworkInterface
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Service for handling Wi-Fi P2P. `supported` must be checked before this service is started otherwise it would crash.
|
* Service for handling Wi-Fi P2P. `supported` must be checked before this service is started otherwise it would crash.
|
||||||
@@ -168,6 +170,7 @@ class RepeaterService : Service(), CoroutineScope, WifiP2pManager.ChannelListene
|
|||||||
override val coroutineContext = dispatcher + Job()
|
override val coroutineContext = dispatcher + Job()
|
||||||
private var routingManager: RoutingManager? = null
|
private var routingManager: RoutingManager? = null
|
||||||
private var persistNextGroup = false
|
private var persistNextGroup = false
|
||||||
|
private val deinitPending = AtomicBoolean(true)
|
||||||
|
|
||||||
var status = Status.IDLE
|
var status = Status.IDLE
|
||||||
private set(value) {
|
private set(value) {
|
||||||
@@ -197,17 +200,24 @@ class RepeaterService : Service(), CoroutineScope, WifiP2pManager.ChannelListene
|
|||||||
|
|
||||||
override fun onBind(intent: Intent) = binder
|
override fun onBind(intent: Intent) = binder
|
||||||
|
|
||||||
private fun setOperatingChannel(forceReinit: Boolean = false, oc: Int = operatingChannel) = try {
|
private fun setOperatingChannel(oc: Int = operatingChannel) {
|
||||||
val channel = channel
|
val channel = channel
|
||||||
if (channel == null) SmartSnackbar.make(R.string.repeater_failure_disconnected).show()
|
if (channel != null) launch(start = CoroutineStart.UNDISPATCHED) {
|
||||||
|
val reason = try {
|
||||||
// we don't care about listening channel
|
// we don't care about listening channel
|
||||||
else p2pManager.setWifiP2pChannels(channel, 0, oc, object : WifiP2pManager.ActionListener {
|
p2pManager.setWifiP2pChannels(channel, 0, oc) ?: return@launch
|
||||||
override fun onSuccess() { }
|
} catch (e: InvocationTargetException) {
|
||||||
override fun onFailure(reason: Int) {
|
if (oc != 0) {
|
||||||
if (reason == WifiP2pManager.ERROR && Build.VERSION.SDK_INT >= 30) launch(start = CoroutineStart.UNDISPATCHED) {
|
val message = getString(R.string.repeater_set_oc_failure, e.message)
|
||||||
|
SmartSnackbar.make(message).show()
|
||||||
|
Timber.w(RuntimeException("Failed to set operating channel $oc", e))
|
||||||
|
} else Timber.w(e)
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
if (reason == WifiP2pManager.ERROR && Build.VERSION.SDK_INT >= 30) {
|
||||||
val rootReason = try {
|
val rootReason = try {
|
||||||
RootManager.use {
|
RootManager.use {
|
||||||
if (forceReinit) it.execute(RepeaterCommands.Deinit())
|
if (deinitPending.getAndSet(false)) it.execute(RepeaterCommands.Deinit())
|
||||||
it.execute(RepeaterCommands.SetChannel(oc))
|
it.execute(RepeaterCommands.SetChannel(oc))
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
@@ -217,21 +227,15 @@ class RepeaterService : Service(), CoroutineScope, WifiP2pManager.ChannelListene
|
|||||||
} ?: return@launch
|
} ?: return@launch
|
||||||
SmartSnackbar.make(formatReason(R.string.repeater_set_oc_failure, rootReason.value)).show()
|
SmartSnackbar.make(formatReason(R.string.repeater_set_oc_failure, rootReason.value)).show()
|
||||||
} else SmartSnackbar.make(formatReason(R.string.repeater_set_oc_failure, reason)).show()
|
} else SmartSnackbar.make(formatReason(R.string.repeater_set_oc_failure, reason)).show()
|
||||||
}
|
} else SmartSnackbar.make(R.string.repeater_failure_disconnected).show()
|
||||||
})
|
|
||||||
} catch (e: InvocationTargetException) {
|
|
||||||
if (oc != 0) {
|
|
||||||
val message = getString(R.string.repeater_set_oc_failure, e.message)
|
|
||||||
SmartSnackbar.make(message).show()
|
|
||||||
Timber.w(RuntimeException("Failed to set operating channel $oc", e))
|
|
||||||
} else Timber.w(e)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onChannelDisconnected() {
|
override fun onChannelDisconnected() {
|
||||||
channel = null
|
channel = null
|
||||||
|
deinitPending.set(true)
|
||||||
if (status != Status.DESTROYED) try {
|
if (status != Status.DESTROYED) try {
|
||||||
channel = p2pManager.initialize(this, Looper.getMainLooper(), this)
|
channel = p2pManager.initialize(this, Looper.getMainLooper(), this)
|
||||||
if (!safeMode) setOperatingChannel(true)
|
if (!safeMode) setOperatingChannel()
|
||||||
} catch (e: RuntimeException) {
|
} catch (e: RuntimeException) {
|
||||||
Timber.w(e)
|
Timber.w(e)
|
||||||
launch(Dispatchers.Main) {
|
launch(Dispatchers.Main) {
|
||||||
@@ -244,7 +248,11 @@ class RepeaterService : Service(), CoroutineScope, WifiP2pManager.ChannelListene
|
|||||||
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
|
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
|
||||||
if (!safeMode) when (key) {
|
if (!safeMode) when (key) {
|
||||||
KEY_OPERATING_CHANNEL -> setOperatingChannel()
|
KEY_OPERATING_CHANNEL -> setOperatingChannel()
|
||||||
KEY_SAFE_MODE -> setOperatingChannel(true)
|
KEY_SAFE_MODE -> {
|
||||||
|
deinitPending.set(true)
|
||||||
|
setOperatingChannel()
|
||||||
|
onPersistentGroupsChanged()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -257,10 +265,9 @@ class RepeaterService : Service(), CoroutineScope, WifiP2pManager.ChannelListene
|
|||||||
null
|
null
|
||||||
} ?: return@launch
|
} ?: return@launch
|
||||||
val channel = channel ?: return@launch
|
val channel = channel ?: return@launch
|
||||||
try {
|
fun Collection<WifiP2pGroup>.filterUselessGroups(): List<WifiP2pGroup> {
|
||||||
p2pManager.requestPersistentGroupInfo(channel) { groups ->
|
if (isNotEmpty()) persistentSupported = true
|
||||||
if (groups.isNotEmpty()) persistentSupported = true
|
val ownedGroups = filter {
|
||||||
val ownedGroups = groups.filter {
|
|
||||||
if (!it.isGroupOwner) return@filter false
|
if (!it.isGroupOwner) return@filter false
|
||||||
val address = MacAddressCompat.fromString(it.owner.deviceAddress)
|
val address = MacAddressCompat.fromString(it.owner.deviceAddress)
|
||||||
// WifiP2pServiceImpl only removes self address
|
// WifiP2pServiceImpl only removes self address
|
||||||
@@ -269,16 +276,33 @@ class RepeaterService : Service(), CoroutineScope, WifiP2pManager.ChannelListene
|
|||||||
val main = ownedGroups.minBy { it.networkId }
|
val main = ownedGroups.minBy { it.networkId }
|
||||||
// do not replace current group if it's better
|
// do not replace current group if it's better
|
||||||
if (binder.group?.passphrase == null) binder.group = main
|
if (binder.group?.passphrase == null) binder.group = main
|
||||||
if (main != null) ownedGroups.filter { it.networkId != main.networkId }.forEach {
|
return if (main != null) ownedGroups.filter { it.networkId != main.networkId } else emptyList()
|
||||||
p2pManager.deletePersistentGroup(channel, it.networkId, object : WifiP2pManager.ActionListener {
|
|
||||||
override fun onSuccess() = Timber.i("Removed redundant owned group: $it")
|
|
||||||
override fun onFailure(reason: Int) = SmartSnackbar.make(
|
|
||||||
formatReason(R.string.repeater_clean_pog_failure, reason)).show()
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
fun Int?.print(group: WifiP2pGroup) {
|
||||||
|
if (this == null) Timber.i("Removed redundant owned group: $group")
|
||||||
|
else SmartSnackbar.make(formatReason(R.string.repeater_clean_pog_failure, this)).show()
|
||||||
}
|
}
|
||||||
|
// we only get empty list on permission denial. Is there a better permission check?
|
||||||
|
if (Build.VERSION.SDK_INT < 30 || checkSelfPermission("android.permission.READ_WIFI_CREDENTIAL") ==
|
||||||
|
PackageManager.PERMISSION_GRANTED) try {
|
||||||
|
for (group in p2pManager.requestPersistentGroupInfo(channel).filterUselessGroups()) {
|
||||||
|
p2pManager.deletePersistentGroup(channel, group.networkId).print(group)
|
||||||
|
}
|
||||||
|
return@launch
|
||||||
} catch (e: ReflectiveOperationException) {
|
} catch (e: ReflectiveOperationException) {
|
||||||
Timber.w(e)
|
Timber.w(e)
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
RootManager.use { server ->
|
||||||
|
if (deinitPending.getAndSet(false)) server.execute(RepeaterCommands.Deinit())
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
val groups = server.execute(RepeaterCommands.RequestPersistentGroupInfo()).value as List<WifiP2pGroup>
|
||||||
|
for (group in groups.filterUselessGroups()) {
|
||||||
|
server.execute(RepeaterCommands.DeletePersistentGroup(group.networkId))?.value.print(group)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Timber.w(e)
|
||||||
SmartSnackbar.make(e).show()
|
SmartSnackbar.make(e).show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,9 @@ object WifiApManager {
|
|||||||
WifiManager::class.java.getDeclaredMethod("setSoftApConfiguration", SoftApConfiguration::class.java)
|
WifiManager::class.java.getDeclaredMethod("setSoftApConfiguration", SoftApConfiguration::class.java)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Requires NETWORK_SETTINGS permission (or root) on API 30+, and OVERRIDE_WIFI_CONFIG on API 29-.
|
||||||
|
*/
|
||||||
val configuration get() = if (Build.VERSION.SDK_INT < 30) @Suppress("DEPRECATION") {
|
val configuration get() = if (Build.VERSION.SDK_INT < 30) @Suppress("DEPRECATION") {
|
||||||
(getWifiApConfiguration(Services.wifi) as android.net.wifi.WifiConfiguration?)?.toCompat()
|
(getWifiApConfiguration(Services.wifi) as android.net.wifi.WifiConfiguration?)?.toCompat()
|
||||||
?: SoftApConfigurationCompat.empty()
|
?: SoftApConfigurationCompat.empty()
|
||||||
|
|||||||
@@ -7,12 +7,25 @@ import android.net.wifi.p2p.WifiP2pManager
|
|||||||
import android.os.Build
|
import android.os.Build
|
||||||
import be.mygod.vpnhotspot.App.Companion.app
|
import be.mygod.vpnhotspot.App.Companion.app
|
||||||
import be.mygod.vpnhotspot.util.callSuper
|
import be.mygod.vpnhotspot.util.callSuper
|
||||||
|
import kotlinx.coroutines.CompletableDeferred
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.lang.reflect.InvocationHandler
|
import java.lang.reflect.InvocationHandler
|
||||||
import java.lang.reflect.Method
|
import java.lang.reflect.Method
|
||||||
import java.lang.reflect.Proxy
|
import java.lang.reflect.Proxy
|
||||||
|
|
||||||
object WifiP2pManagerHelper {
|
object WifiP2pManagerHelper {
|
||||||
|
private class ResultListener : WifiP2pManager.ActionListener {
|
||||||
|
val future = CompletableDeferred<Int?>()
|
||||||
|
|
||||||
|
override fun onSuccess() {
|
||||||
|
future.complete(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailure(reason: Int) {
|
||||||
|
future.complete(reason)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const val UNSUPPORTED = -2
|
const val UNSUPPORTED = -2
|
||||||
val ACTION_WIFI_P2P_PERSISTENT_GROUPS_CHANGED = if (Build.VERSION.SDK_INT >= 30) {
|
val ACTION_WIFI_P2P_PERSISTENT_GROUPS_CHANGED = if (Build.VERSION.SDK_INT >= 30) {
|
||||||
"android.net.wifi.p2p.action.WIFI_P2P_PERSISTENT_GROUPS_CHANGED"
|
"android.net.wifi.p2p.action.WIFI_P2P_PERSISTENT_GROUPS_CHANGED"
|
||||||
@@ -28,14 +41,18 @@ object WifiP2pManagerHelper {
|
|||||||
WifiP2pManager::class.java.getDeclaredMethod("setWifiP2pChannels", WifiP2pManager.Channel::class.java,
|
WifiP2pManager::class.java.getDeclaredMethod("setWifiP2pChannels", WifiP2pManager.Channel::class.java,
|
||||||
Int::class.java, Int::class.java, WifiP2pManager.ActionListener::class.java)
|
Int::class.java, Int::class.java, WifiP2pManager.ActionListener::class.java)
|
||||||
}
|
}
|
||||||
fun WifiP2pManager.setWifiP2pChannels(c: WifiP2pManager.Channel, lc: Int, oc: Int,
|
/**
|
||||||
listener: WifiP2pManager.ActionListener) {
|
* Requires one of NETWORK_SETTING, NETWORK_STACK, or OVERRIDE_WIFI_CONFIG permission since API 30.
|
||||||
|
*/
|
||||||
|
suspend fun WifiP2pManager.setWifiP2pChannels(c: WifiP2pManager.Channel, lc: Int, oc: Int): Int? {
|
||||||
|
val result = ResultListener()
|
||||||
try {
|
try {
|
||||||
setWifiP2pChannels(this, c, lc, oc, listener)
|
setWifiP2pChannels(this, c, lc, oc, result)
|
||||||
} catch (_: NoSuchMethodException) {
|
} catch (_: NoSuchMethodException) {
|
||||||
app.logEvent("NoSuchMethod_setWifiP2pChannels")
|
app.logEvent("NoSuchMethod_setWifiP2pChannels")
|
||||||
listener.onFailure(UNSUPPORTED)
|
return UNSUPPORTED
|
||||||
}
|
}
|
||||||
|
return result.future.await()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -66,14 +83,18 @@ object WifiP2pManagerHelper {
|
|||||||
WifiP2pManager::class.java.getDeclaredMethod("deletePersistentGroup",
|
WifiP2pManager::class.java.getDeclaredMethod("deletePersistentGroup",
|
||||||
WifiP2pManager.Channel::class.java, Int::class.java, WifiP2pManager.ActionListener::class.java)
|
WifiP2pManager.Channel::class.java, Int::class.java, WifiP2pManager.ActionListener::class.java)
|
||||||
}
|
}
|
||||||
fun WifiP2pManager.deletePersistentGroup(c: WifiP2pManager.Channel, netId: Int,
|
/**
|
||||||
listener: WifiP2pManager.ActionListener) {
|
* Requires one of NETWORK_SETTING, NETWORK_STACK, or READ_WIFI_CREDENTIAL permission since API 30.
|
||||||
|
*/
|
||||||
|
suspend fun WifiP2pManager.deletePersistentGroup(c: WifiP2pManager.Channel, netId: Int): Int? {
|
||||||
|
val result = ResultListener()
|
||||||
try {
|
try {
|
||||||
deletePersistentGroup(this, c, netId, listener)
|
deletePersistentGroup(this, c, netId, result)
|
||||||
} catch (_: NoSuchMethodException) {
|
} catch (_: NoSuchMethodException) {
|
||||||
app.logEvent("NoSuchMethod_deletePersistentGroup")
|
app.logEvent("NoSuchMethod_deletePersistentGroup")
|
||||||
listener.onFailure(UNSUPPORTED)
|
return UNSUPPORTED
|
||||||
}
|
}
|
||||||
|
return result.future.await()
|
||||||
}
|
}
|
||||||
|
|
||||||
private val interfacePersistentGroupInfoListener by lazy @SuppressLint("PrivateApi") {
|
private val interfacePersistentGroupInfoListener by lazy @SuppressLint("PrivateApi") {
|
||||||
@@ -89,21 +110,24 @@ object WifiP2pManagerHelper {
|
|||||||
/**
|
/**
|
||||||
* Request a list of all the persistent p2p groups stored in system.
|
* Request a list of all the persistent p2p groups stored in system.
|
||||||
*
|
*
|
||||||
|
* Requires one of NETWORK_SETTING, NETWORK_STACK, or READ_WIFI_CREDENTIAL permission since API 30.
|
||||||
|
*
|
||||||
* @param c is the channel created at {@link #initialize}
|
* @param c is the channel created at {@link #initialize}
|
||||||
* @param listener for callback when persistent group info list is available. Can be null.
|
* @param listener for callback when persistent group info list is available. Can be null.
|
||||||
*/
|
*/
|
||||||
fun WifiP2pManager.requestPersistentGroupInfo(c: WifiP2pManager.Channel,
|
suspend fun WifiP2pManager.requestPersistentGroupInfo(c: WifiP2pManager.Channel): Collection<WifiP2pGroup> {
|
||||||
listener: (Collection<WifiP2pGroup>) -> Unit) {
|
val result = CompletableDeferred<Collection<WifiP2pGroup>>()
|
||||||
val proxy = Proxy.newProxyInstance(interfacePersistentGroupInfoListener.classLoader,
|
requestPersistentGroupInfo(this, c, Proxy.newProxyInstance(interfacePersistentGroupInfoListener.classLoader,
|
||||||
arrayOf(interfacePersistentGroupInfoListener), object : InvocationHandler {
|
arrayOf(interfacePersistentGroupInfoListener), object : InvocationHandler {
|
||||||
override fun invoke(proxy: Any, method: Method, args: Array<out Any?>?): Any? = when (method.name) {
|
override fun invoke(proxy: Any, method: Method, args: Array<out Any?>?): Any? = when (method.name) {
|
||||||
"onPersistentGroupInfoAvailable" -> {
|
"onPersistentGroupInfoAvailable" -> {
|
||||||
if (args?.size != 1) Timber.w(IllegalArgumentException("Unexpected args: $args"))
|
if (args?.size != 1) Timber.w(IllegalArgumentException("Unexpected args: $args"))
|
||||||
@Suppress("UNCHECKED_CAST") listener(getGroupList(args!![0]) as Collection<WifiP2pGroup>)
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
result.complete(getGroupList(args!![0]) as Collection<WifiP2pGroup>)
|
||||||
}
|
}
|
||||||
else -> callSuper(interfacePersistentGroupInfoListener, proxy, method, args)
|
else -> callSuper(interfacePersistentGroupInfoListener, proxy, method, args)
|
||||||
}
|
}
|
||||||
})
|
}))
|
||||||
requestPersistentGroupInfo(this, c, proxy)
|
return result.await()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,13 +7,15 @@ import android.system.Os
|
|||||||
import android.system.OsConstants
|
import android.system.OsConstants
|
||||||
import android.text.TextUtils
|
import android.text.TextUtils
|
||||||
import be.mygod.librootkotlinx.ParcelableInt
|
import be.mygod.librootkotlinx.ParcelableInt
|
||||||
|
import be.mygod.librootkotlinx.ParcelableList
|
||||||
import be.mygod.librootkotlinx.RootCommand
|
import be.mygod.librootkotlinx.RootCommand
|
||||||
import be.mygod.librootkotlinx.RootCommandNoResult
|
import be.mygod.librootkotlinx.RootCommandNoResult
|
||||||
|
import be.mygod.vpnhotspot.net.wifi.WifiP2pManagerHelper.deletePersistentGroup
|
||||||
|
import be.mygod.vpnhotspot.net.wifi.WifiP2pManagerHelper.requestPersistentGroupInfo
|
||||||
import be.mygod.vpnhotspot.net.wifi.WifiP2pManagerHelper.setWifiP2pChannels
|
import be.mygod.vpnhotspot.net.wifi.WifiP2pManagerHelper.setWifiP2pChannels
|
||||||
import be.mygod.vpnhotspot.util.Services
|
import be.mygod.vpnhotspot.util.Services
|
||||||
import eu.chainfire.librootjava.RootJava
|
import eu.chainfire.librootjava.RootJava
|
||||||
import kotlinx.android.parcel.Parcelize
|
import kotlinx.android.parcel.Parcelize
|
||||||
import kotlinx.coroutines.CompletableDeferred
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
object RepeaterCommands {
|
object RepeaterCommands {
|
||||||
@@ -26,29 +28,23 @@ object RepeaterCommands {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
class SetChannel(private val oc: Int) : RootCommand<ParcelableInt?> {
|
data class DeletePersistentGroup(val netId: Int) : RootCommand<ParcelableInt?> {
|
||||||
override suspend fun execute() = Services.p2p!!.run {
|
override suspend fun execute() = Services.p2p!!.run {
|
||||||
val uninitializer = object : WifiP2pManager.ChannelListener {
|
deletePersistentGroup(obtainChannel(), netId)?.let { ParcelableInt(it) }
|
||||||
var target: WifiP2pManager.Channel? = null
|
|
||||||
override fun onChannelDisconnected() {
|
|
||||||
if (target == channel) channel = null
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val channel = channel ?: initialize(RootJava.getSystemContext(),
|
|
||||||
Looper.getMainLooper(), uninitializer)
|
|
||||||
uninitializer.target = channel
|
|
||||||
RepeaterCommands.channel = channel // cache the instance until invalidated
|
|
||||||
val future = CompletableDeferred<Int?>()
|
|
||||||
setWifiP2pChannels(channel, 0, oc, object : WifiP2pManager.ActionListener {
|
|
||||||
override fun onSuccess() {
|
|
||||||
future.complete(null)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onFailure(reason: Int) {
|
@Parcelize
|
||||||
future.complete(reason)
|
class RequestPersistentGroupInfo : RootCommand<ParcelableList> {
|
||||||
|
override suspend fun execute() = Services.p2p!!.run {
|
||||||
|
ParcelableList(requestPersistentGroupInfo(obtainChannel()).toList())
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
future.await()?.let { ParcelableInt(it) }
|
|
||||||
|
@Parcelize
|
||||||
|
class SetChannel(private val oc: Int) : RootCommand<ParcelableInt?> {
|
||||||
|
override suspend fun execute() = Services.p2p!!.run {
|
||||||
|
setWifiP2pChannels(obtainChannel(), 0, oc)?.let { ParcelableInt(it) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,4 +78,18 @@ object RepeaterCommands {
|
|||||||
private const val CONF_PATH_TREBLE = "/data/vendor/wifi/wpa/p2p_supplicant.conf"
|
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 CONF_PATH_LEGACY = "/data/misc/wifi/p2p_supplicant.conf"
|
||||||
private var channel: WifiP2pManager.Channel? = null
|
private var channel: WifiP2pManager.Channel? = null
|
||||||
|
|
||||||
|
private fun WifiP2pManager.obtainChannel(): WifiP2pManager.Channel {
|
||||||
|
channel?.let { return it }
|
||||||
|
val uninitializer = object : WifiP2pManager.ChannelListener {
|
||||||
|
var target: WifiP2pManager.Channel? = null
|
||||||
|
override fun onChannelDisconnected() {
|
||||||
|
if (target == channel) channel = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return initialize(RootJava.getSystemContext(), Looper.getMainLooper(), uninitializer).also {
|
||||||
|
uninitializer.target = it
|
||||||
|
channel = it // cache the instance until invalidated
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user