Fix requestPersistentGroupInfo and deletePersistentGroup permissions

This commit is contained in:
Mygod
2020-07-01 15:37:21 -04:00
parent 600a99cd13
commit febf7f1c61
8 changed files with 170 additions and 84 deletions

View File

@@ -38,6 +38,8 @@
tools:ignore="ProtectedPermissions"/>
<uses-permission android:name="android.permission.OVERRIDE_WIFI_CONFIG"
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.TETHER_PRIVILEGED"
tools:ignore="ProtectedPermissions"/>

View File

@@ -179,6 +179,27 @@ data class ParcelableSize(val value: Size) : Parcelable
@Parcelize
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")
inline fun <T> useParcel(block: (Parcel) -> T) = Parcel.obtain().run {
try {

View File

@@ -5,6 +5,7 @@ import android.annotation.TargetApi
import android.app.Service
import android.content.Intent
import android.content.SharedPreferences
import android.content.pm.PackageManager
import android.net.wifi.WpsInfo
import android.net.wifi.p2p.*
import android.os.Build
@@ -32,6 +33,7 @@ import kotlinx.coroutines.*
import timber.log.Timber
import java.lang.reflect.InvocationTargetException
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.
@@ -168,6 +170,7 @@ class RepeaterService : Service(), CoroutineScope, WifiP2pManager.ChannelListene
override val coroutineContext = dispatcher + Job()
private var routingManager: RoutingManager? = null
private var persistNextGroup = false
private val deinitPending = AtomicBoolean(true)
var status = Status.IDLE
private set(value) {
@@ -197,41 +200,42 @@ class RepeaterService : Service(), CoroutineScope, WifiP2pManager.ChannelListene
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
if (channel == null) SmartSnackbar.make(R.string.repeater_failure_disconnected).show()
// we don't care about listening channel
else p2pManager.setWifiP2pChannels(channel, 0, oc, object : WifiP2pManager.ActionListener {
override fun onSuccess() { }
override fun onFailure(reason: Int) {
if (reason == WifiP2pManager.ERROR && Build.VERSION.SDK_INT >= 30) launch(start = CoroutineStart.UNDISPATCHED) {
val rootReason = try {
RootManager.use {
if (forceReinit) it.execute(RepeaterCommands.Deinit())
it.execute(RepeaterCommands.SetChannel(oc))
}
} catch (e: Exception) {
Timber.w(e)
SmartSnackbar.make(e).show()
null
} ?: return@launch
SmartSnackbar.make(formatReason(R.string.repeater_set_oc_failure, rootReason.value)).show()
} else SmartSnackbar.make(formatReason(R.string.repeater_set_oc_failure, reason)).show()
if (channel != null) launch(start = CoroutineStart.UNDISPATCHED) {
val reason = try {
// we don't care about listening channel
p2pManager.setWifiP2pChannels(channel, 0, oc) ?: return@launch
} 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)
return@launch
}
})
} 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)
if (reason == WifiP2pManager.ERROR && Build.VERSION.SDK_INT >= 30) {
val rootReason = try {
RootManager.use {
if (deinitPending.getAndSet(false)) it.execute(RepeaterCommands.Deinit())
it.execute(RepeaterCommands.SetChannel(oc))
}
} catch (e: Exception) {
Timber.w(e)
SmartSnackbar.make(e).show()
null
} ?: return@launch
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(R.string.repeater_failure_disconnected).show()
}
override fun onChannelDisconnected() {
channel = null
deinitPending.set(true)
if (status != Status.DESTROYED) try {
channel = p2pManager.initialize(this, Looper.getMainLooper(), this)
if (!safeMode) setOperatingChannel(true)
if (!safeMode) setOperatingChannel()
} catch (e: RuntimeException) {
Timber.w(e)
launch(Dispatchers.Main) {
@@ -244,7 +248,11 @@ class RepeaterService : Service(), CoroutineScope, WifiP2pManager.ChannelListene
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
if (!safeMode) when (key) {
KEY_OPERATING_CHANNEL -> setOperatingChannel()
KEY_SAFE_MODE -> setOperatingChannel(true)
KEY_SAFE_MODE -> {
deinitPending.set(true)
setOperatingChannel()
onPersistentGroupsChanged()
}
}
}
@@ -257,27 +265,43 @@ class RepeaterService : Service(), CoroutineScope, WifiP2pManager.ChannelListene
null
} ?: return@launch
val channel = channel ?: return@launch
fun Collection<WifiP2pGroup>.filterUselessGroups(): List<WifiP2pGroup> {
if (isNotEmpty()) persistentSupported = true
val ownedGroups = filter {
if (!it.isGroupOwner) return@filter false
val address = MacAddressCompat.fromString(it.owner.deviceAddress)
// WifiP2pServiceImpl only removes self address
Build.VERSION.SDK_INT >= 29 && address == MacAddressCompat.ANY_ADDRESS || address == ownerAddress
}
val main = ownedGroups.minBy { it.networkId }
// do not replace current group if it's better
if (binder.group?.passphrase == null) binder.group = main
return if (main != null) ownedGroups.filter { it.networkId != main.networkId } else emptyList()
}
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) {
Timber.w(e)
}
try {
p2pManager.requestPersistentGroupInfo(channel) { groups ->
if (groups.isNotEmpty()) persistentSupported = true
val ownedGroups = groups.filter {
if (!it.isGroupOwner) return@filter false
val address = MacAddressCompat.fromString(it.owner.deviceAddress)
// WifiP2pServiceImpl only removes self address
Build.VERSION.SDK_INT >= 29 && address == MacAddressCompat.ANY_ADDRESS || address == ownerAddress
}
val main = ownedGroups.minBy { it.networkId }
// do not replace current group if it's better
if (binder.group?.passphrase == null) binder.group = main
if (main != null) ownedGroups.filter { it.networkId != main.networkId }.forEach {
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()
})
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: ReflectiveOperationException) {
} catch (e: Exception) {
Timber.w(e)
SmartSnackbar.make(e).show()
}

View File

@@ -22,6 +22,9 @@ object WifiApManager {
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") {
(getWifiApConfiguration(Services.wifi) as android.net.wifi.WifiConfiguration?)?.toCompat()
?: SoftApConfigurationCompat.empty()

View File

@@ -7,12 +7,25 @@ import android.net.wifi.p2p.WifiP2pManager
import android.os.Build
import be.mygod.vpnhotspot.App.Companion.app
import be.mygod.vpnhotspot.util.callSuper
import kotlinx.coroutines.CompletableDeferred
import timber.log.Timber
import java.lang.reflect.InvocationHandler
import java.lang.reflect.Method
import java.lang.reflect.Proxy
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
val ACTION_WIFI_P2P_PERSISTENT_GROUPS_CHANGED = if (Build.VERSION.SDK_INT >= 30) {
"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,
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 {
setWifiP2pChannels(this, c, lc, oc, listener)
setWifiP2pChannels(this, c, lc, oc, result)
} catch (_: NoSuchMethodException) {
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.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 {
deletePersistentGroup(this, c, netId, listener)
deletePersistentGroup(this, c, netId, result)
} catch (_: NoSuchMethodException) {
app.logEvent("NoSuchMethod_deletePersistentGroup")
listener.onFailure(UNSUPPORTED)
return UNSUPPORTED
}
return result.future.await()
}
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.
*
* 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 listener for callback when persistent group info list is available. Can be null.
*/
fun WifiP2pManager.requestPersistentGroupInfo(c: WifiP2pManager.Channel,
listener: (Collection<WifiP2pGroup>) -> Unit) {
val proxy = Proxy.newProxyInstance(interfacePersistentGroupInfoListener.classLoader,
suspend fun WifiP2pManager.requestPersistentGroupInfo(c: WifiP2pManager.Channel): Collection<WifiP2pGroup> {
val result = CompletableDeferred<Collection<WifiP2pGroup>>()
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.name) {
"onPersistentGroupInfoAvailable" -> {
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)
}
})
requestPersistentGroupInfo(this, c, proxy)
}))
return result.await()
}
}

View File

@@ -7,13 +7,15 @@ import android.system.Os
import android.system.OsConstants
import android.text.TextUtils
import be.mygod.librootkotlinx.ParcelableInt
import be.mygod.librootkotlinx.ParcelableList
import be.mygod.librootkotlinx.RootCommand
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.util.Services
import eu.chainfire.librootjava.RootJava
import kotlinx.android.parcel.Parcelize
import kotlinx.coroutines.CompletableDeferred
import java.io.File
object RepeaterCommands {
@@ -25,30 +27,24 @@ object RepeaterCommands {
}
}
@Parcelize
data class DeletePersistentGroup(val netId: Int) : RootCommand<ParcelableInt?> {
override suspend fun execute() = Services.p2p!!.run {
deletePersistentGroup(obtainChannel(), netId)?.let { ParcelableInt(it) }
}
}
@Parcelize
class RequestPersistentGroupInfo : RootCommand<ParcelableList> {
override suspend fun execute() = Services.p2p!!.run {
ParcelableList(requestPersistentGroupInfo(obtainChannel()).toList())
}
}
@Parcelize
class SetChannel(private val oc: Int) : RootCommand<ParcelableInt?> {
override suspend fun execute() = Services.p2p!!.run {
val uninitializer = object : WifiP2pManager.ChannelListener {
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) {
future.complete(reason)
}
})
future.await()?.let { ParcelableInt(it) }
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_LEGACY = "/data/misc/wifi/p2p_supplicant.conf"
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
}
}
}