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

@@ -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.MANAGE_USB`
* `android.permission.OVERRIDE_WIFI_CONFIG`
* `android.permission.READ_WIFI_CREDENTIAL`
* `android.permission.TETHER_PRIVILEGED`
* `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

View File

@@ -13,7 +13,7 @@ buildscript {
dependencies {
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.google.firebase:firebase-crashlytics-gradle:2.2.0")
classpath("com.google.android.gms:oss-licenses-plugin:0.10.2")

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,17 +200,24 @@ 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()
if (channel != null) launch(start = CoroutineStart.UNDISPATCHED) {
val reason = try {
// 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) {
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
}
if (reason == WifiP2pManager.ERROR && Build.VERSION.SDK_INT >= 30) {
val rootReason = try {
RootManager.use {
if (forceReinit) it.execute(RepeaterCommands.Deinit())
if (deinitPending.getAndSet(false)) it.execute(RepeaterCommands.Deinit())
it.execute(RepeaterCommands.SetChannel(oc))
}
} catch (e: Exception) {
@@ -217,21 +227,15 @@ class RepeaterService : Service(), CoroutineScope, WifiP2pManager.ChannelListene
} ?: 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()
}
})
} 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)
} 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,10 +265,9 @@ class RepeaterService : Service(), CoroutineScope, WifiP2pManager.ChannelListene
null
} ?: return@launch
val channel = channel ?: return@launch
try {
p2pManager.requestPersistentGroupInfo(channel) { groups ->
if (groups.isNotEmpty()) persistentSupported = true
val ownedGroups = groups.filter {
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
@@ -269,16 +276,33 @@ class RepeaterService : Service(), CoroutineScope, WifiP2pManager.ChannelListene
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()
})
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 {
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()
}
}

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 {
@@ -26,29 +28,23 @@ object RepeaterCommands {
}
@Parcelize
class SetChannel(private val oc: Int) : RootCommand<ParcelableInt?> {
data class DeletePersistentGroup(val netId: 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
deletePersistentGroup(obtainChannel(), netId)?.let { ParcelableInt(it) }
}
}
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)
@Parcelize
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_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
}
}
}