Implement client limiting and MAC address filtering
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
package be.mygod.vpnhotspot.manage
|
||||
|
||||
import android.annotation.TargetApi
|
||||
import android.content.ClipData
|
||||
import android.content.Intent
|
||||
import android.net.MacAddress
|
||||
import android.os.Build
|
||||
import android.provider.Settings
|
||||
import android.view.View
|
||||
@@ -161,6 +163,14 @@ sealed class TetherManager(protected val parent: TetheringFragment) : Manager(),
|
||||
capability = maxSupportedClients to supportedFeatures
|
||||
data.notifyChange()
|
||||
}
|
||||
override fun onBlockedClientConnecting(client: MacAddress, blockedReason: Int) {
|
||||
SmartSnackbar.make(parent.getString(R.string.tethering_manage_wifi_client_blocked, client,
|
||||
WifiApManager.clientBlockLookup(blockedReason, true))).apply {
|
||||
action(R.string.tethering_manage_wifi_copy_mac) {
|
||||
app.clipboard.setPrimaryClip(ClipData.newPlainText(null, client.toString()))
|
||||
}
|
||||
}.show()
|
||||
}
|
||||
|
||||
override val title get() = parent.getString(R.string.tethering_manage_wifi)
|
||||
override val tetherType get() = TetherType.WIFI
|
||||
|
||||
@@ -33,9 +33,9 @@ data class SoftApConfigurationCompat(
|
||||
@TargetApi(30)
|
||||
var isClientControlByUserEnabled: Boolean = false,
|
||||
@RequiresApi(30)
|
||||
var blockedClientList: List<MacAddress?> = emptyList(),
|
||||
var blockedClientList: List<MacAddress> = emptyList(),
|
||||
@RequiresApi(30)
|
||||
var allowedClientList: List<MacAddress?> = emptyList(),
|
||||
var allowedClientList: List<MacAddress> = emptyList(),
|
||||
var underlying: Parcelable? = null) : Parcelable {
|
||||
companion object {
|
||||
const val BAND_2GHZ = 1
|
||||
@@ -238,8 +238,8 @@ data class SoftApConfigurationCompat(
|
||||
isAutoShutdownEnabled(this) as Boolean,
|
||||
getShutdownTimeoutMillis(this) as Long,
|
||||
isClientControlByUserEnabled(this) as Boolean,
|
||||
getBlockedClientList(this) as List<MacAddress?>,
|
||||
getAllowedClientList(this) as List<MacAddress?>,
|
||||
getBlockedClientList(this) as List<MacAddress>,
|
||||
getAllowedClientList(this) as List<MacAddress>,
|
||||
this)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
package be.mygod.vpnhotspot.net.wifi
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.annotation.TargetApi
|
||||
import android.content.ClipData
|
||||
import android.content.DialogInterface
|
||||
import android.net.MacAddress
|
||||
import android.net.wifi.SoftApConfiguration
|
||||
import android.os.Build
|
||||
import android.os.Parcelable
|
||||
@@ -42,6 +44,7 @@ class WifiApDialogFragment : AlertDialogFragment<WifiApDialogFragment.Arg, WifiA
|
||||
Toolbar.OnMenuItemClickListener {
|
||||
companion object {
|
||||
private const val BASE64_FLAGS = Base64.NO_PADDING or Base64.NO_WRAP
|
||||
private val nonMacChars = "[^0-9a-fA-F:]+".toRegex()
|
||||
private val channels by lazy {
|
||||
val list = ArrayList<BandOption.Channel>()
|
||||
for (chan in 1..14) list.add(BandOption.Channel(SoftApConfigurationCompat.BAND_2GHZ, chan))
|
||||
@@ -109,7 +112,7 @@ class WifiApDialogFragment : AlertDialogFragment<WifiApDialogFragment.Arg, WifiA
|
||||
securityType = dialogView.security.selectedItemPosition
|
||||
isHiddenSsid = dialogView.hiddenSsid.isChecked
|
||||
}
|
||||
if (full) {
|
||||
if (full) @TargetApi(28) {
|
||||
isAutoShutdownEnabled = dialogView.autoShutdown.isChecked
|
||||
shutdownTimeoutMillis = dialogView.timeout.text.let { text ->
|
||||
if (text.isNullOrEmpty()) 0 else text.toString().toLong()
|
||||
@@ -120,6 +123,14 @@ class WifiApDialogFragment : AlertDialogFragment<WifiApDialogFragment.Arg, WifiA
|
||||
bssid = if (dialogView.bssid.length() != 0) {
|
||||
MacAddressCompat.fromString(dialogView.bssid.text.toString())
|
||||
} else null
|
||||
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 { MacAddress.fromString(it) }
|
||||
blockedClientList = (dialogView.blockedList.text ?: "").split(nonMacChars)
|
||||
.filter { it.isNotEmpty() }.map { MacAddress.fromString(it) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,7 +161,7 @@ class WifiApDialogFragment : AlertDialogFragment<WifiApDialogFragment.Arg, WifiA
|
||||
if (arg.p2pMode || Build.VERSION.SDK_INT >= 30) {
|
||||
dialogView.timeoutWrapper.helperText = getString(R.string.wifi_hotspot_timeout_default,
|
||||
TetherTimeoutMonitor.defaultTimeout)
|
||||
if (!arg.readOnly) dialogView.timeout.addTextChangedListener(this@WifiApDialogFragment)
|
||||
dialogView.timeout.addTextChangedListener(this@WifiApDialogFragment)
|
||||
} else dialogView.timeoutWrapper.isGone = true
|
||||
if (Build.VERSION.SDK_INT >= 23 || arg.p2pMode) dialogView.band.apply {
|
||||
bandOptions = mutableListOf<BandOption>().apply {
|
||||
@@ -175,6 +186,16 @@ class WifiApDialogFragment : AlertDialogFragment<WifiApDialogFragment.Arg, WifiA
|
||||
} else dialogView.bandWrapper.isGone = true
|
||||
dialogView.bssid.addTextChangedListener(this@WifiApDialogFragment)
|
||||
if (arg.p2pMode) dialogView.hiddenSsid.isGone = true
|
||||
if (arg.p2pMode || Build.VERSION.SDK_INT < 30) {
|
||||
dialogView.maxClientWrapper.isGone = true
|
||||
dialogView.clientUserControl.isGone = true
|
||||
dialogView.blockedListWrapper.isGone = true
|
||||
dialogView.allowedListWrapper.isGone = true
|
||||
} else {
|
||||
dialogView.maxClient.addTextChangedListener(this@WifiApDialogFragment)
|
||||
dialogView.blockedList.addTextChangedListener(this@WifiApDialogFragment)
|
||||
dialogView.allowedList.addTextChangedListener(this@WifiApDialogFragment)
|
||||
}
|
||||
base = arg.configuration
|
||||
populateFromConfiguration()
|
||||
}
|
||||
@@ -193,18 +214,19 @@ class WifiApDialogFragment : AlertDialogFragment<WifiApDialogFragment.Arg, WifiA
|
||||
}
|
||||
dialogView.bssid.setText(base.bssid?.toString())
|
||||
dialogView.hiddenSsid.isChecked = base.isHiddenSsid
|
||||
// TODO support more fields from SACC
|
||||
dialogView.maxClient.setText(base.maxNumberOfClients.let { if (it == 0) "" else it.toString() })
|
||||
dialogView.clientUserControl.isChecked = base.isClientControlByUserEnabled
|
||||
dialogView.blockedList.setText(base.blockedClientList.joinToString("\n"))
|
||||
dialogView.allowedList.setText(base.allowedClientList.joinToString("\n"))
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
started = true
|
||||
if (!arg.readOnly) validate()
|
||||
validate()
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is reached only if not arg.readOnly.
|
||||
*/
|
||||
@TargetApi(28)
|
||||
private fun validate() {
|
||||
if (!started) return
|
||||
val ssidLength = dialogView.ssid.text.toString().toByteArray().size
|
||||
@@ -239,9 +261,36 @@ class WifiApDialogFragment : AlertDialogFragment<WifiApDialogFragment.Arg, WifiA
|
||||
dialogView.bssidWrapper.error = e.readableMessage
|
||||
false
|
||||
}
|
||||
val maxClientError = dialogView.maxClient.text.let { text ->
|
||||
if (text.isNullOrEmpty()) null else try {
|
||||
text.toString().toInt()
|
||||
null
|
||||
} catch (e: NumberFormatException) {
|
||||
e.readableMessage
|
||||
}
|
||||
}
|
||||
dialogView.maxClientWrapper.error = maxClientError
|
||||
val blockedListError = try {
|
||||
(dialogView.blockedList.text ?: "").split(nonMacChars)
|
||||
.filter { it.isNotEmpty() }.forEach { MacAddress.fromString(it) }
|
||||
null
|
||||
} catch (e: IllegalArgumentException) {
|
||||
e.readableMessage
|
||||
}
|
||||
dialogView.blockedListWrapper.error = blockedListError
|
||||
val allowedListError = try {
|
||||
(dialogView.allowedList.text ?: "").split(nonMacChars)
|
||||
.filter { it.isNotEmpty() }.forEach { MacAddress.fromString(it) }
|
||||
null
|
||||
} catch (e: IllegalArgumentException) {
|
||||
e.readableMessage
|
||||
}
|
||||
dialogView.allowedListWrapper.error = allowedListError
|
||||
val canCopy = timeoutError == null && bssidValid && maxClientError == null && blockedListError == null &&
|
||||
allowedListError == null
|
||||
(dialog as? AlertDialog)?.getButton(DialogInterface.BUTTON_POSITIVE)?.isEnabled =
|
||||
ssidLength in 1..32 && passwordValid && timeoutError == null && bssidValid
|
||||
dialogView.toolbar.menu.findItem(android.R.id.copy).isEnabled = timeoutError == null && bssidValid
|
||||
ssidLength in 1..32 && passwordValid && canCopy
|
||||
dialogView.toolbar.menu.findItem(android.R.id.copy).isEnabled = canCopy
|
||||
}
|
||||
|
||||
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) { }
|
||||
|
||||
@@ -97,6 +97,8 @@ object WifiApManager {
|
||||
@RequiresApi(28)
|
||||
val failureReasonLookup = ConstantLookup<WifiManager>("SAP_START_FAILURE_",
|
||||
"SAP_START_FAILURE_GENERAL", "SAP_START_FAILURE_NO_CHANNEL")
|
||||
@get:RequiresApi(30)
|
||||
val clientBlockLookup by lazy { ConstantLookup<WifiManager>("SAP_CLIENT_") }
|
||||
|
||||
private val interfaceSoftApCallback by lazy { Class.forName("android.net.wifi.WifiManager\$SoftApCallback") }
|
||||
private val registerSoftApCallback by lazy {
|
||||
|
||||
@@ -143,6 +143,7 @@
|
||||
android:layout_marginTop="8dip"
|
||||
app:counterEnabled="true"
|
||||
app:counterMaxLength="17"
|
||||
app:errorEnabled="true"
|
||||
android:hint="@string/wifi_advanced_mac_address_title">
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/bssid"
|
||||
@@ -159,6 +160,58 @@
|
||||
android:layout_marginTop="8dip"
|
||||
style="@style/wifi_item_label"
|
||||
android:text="@string/wifi_hidden_network"/>
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/max_client_wrapper"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dip"
|
||||
app:counterEnabled="true"
|
||||
app:counterMaxLength="10"
|
||||
app:errorEnabled="true"
|
||||
android:hint="@string/wifi_max_clients">
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/max_client"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/wifi_item_edit_content"
|
||||
android:inputType="number"
|
||||
android:maxLength="10" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
<Switch
|
||||
android:id="@+id/client_user_control"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dip"
|
||||
style="@style/wifi_item_label"
|
||||
android:text="@string/wifi_client_user_control"/>
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/blocked_list_wrapper"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dip"
|
||||
android:hint="@string/wifi_blocked_list"
|
||||
app:errorEnabled="true">
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/blocked_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/wifi_item_edit_content"
|
||||
android:inputType="textMultiLine|textNoSuggestions" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/allowed_list_wrapper"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dip"
|
||||
android:hint="@string/wifi_allowed_list"
|
||||
app:errorEnabled="true">
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/allowed_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/wifi_item_edit_content"
|
||||
android:inputType="textMultiLine|textNoSuggestions" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
</LinearLayout>
|
||||
|
||||
@@ -70,6 +70,8 @@
|
||||
<string name="tethering_manage_wifi_info">%1$d MHz, channel %2$d, width %3$s</string>
|
||||
<string name="tethering_manage_wifi_capabilities">%1$s/%2$d clients connected\nSupported features: %3$s</string>
|
||||
<string name="tethering_manage_wifi_no_features">None</string>
|
||||
<string name="tethering_manage_wifi_client_blocked">Blocked %1$s: %2$s</string>
|
||||
<string name="tethering_manage_wifi_copy_mac">Copy MAC</string>
|
||||
|
||||
<string name="connected_state_incomplete">" (connecting)"</string>
|
||||
<string name="connected_state_valid">" (reachable)"</string>
|
||||
@@ -184,6 +186,10 @@
|
||||
<string name="wifi_ap_choose_6G">6.0 GHz Band</string>
|
||||
<string name="wifi_advanced_mac_address_title">MAC address</string>
|
||||
<string name="wifi_hidden_network">Hidden network</string>
|
||||
<string name="wifi_max_clients">Maximum number of clients</string>
|
||||
<string name="wifi_client_user_control">Control which client can use hotspot</string>
|
||||
<string name="wifi_blocked_list">Blocked list of clients</string>
|
||||
<string name="wifi_allowed_list">Allowed list of clients</string>
|
||||
<string name="wifi_save">Save</string>
|
||||
|
||||
<!-- Based on: https://github.com/PrivacyApps/donations/blob/747d36a18433c7e9329691054122a8ad337a62d2/Donations/src/main/res/values/donations__strings.xml -->
|
||||
|
||||
Reference in New Issue
Block a user