Add support for modifying repeater credentials
Credits go to @fxsheep: https://forum.xda-developers.com/showpost.php?p=76298728&postcount=5 Currently it only works on later versions of Android due to usage of `killall`. A workaround is in progress.
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
package be.mygod.vpnhotspot.net.wifi
|
||||
|
||||
import android.net.wifi.WifiConfiguration
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import be.mygod.vpnhotspot.App.Companion.app
|
||||
import be.mygod.vpnhotspot.loggerSu
|
||||
import be.mygod.vpnhotspot.noisySu
|
||||
import java.io.File
|
||||
|
||||
class P2pSupplicantConfiguration {
|
||||
companion object {
|
||||
private const val TAG = "P2pSupplicationConf"
|
||||
// format for ssid is much more complicated, therefore we are only trying to find the line
|
||||
private val ssidMatcher = "^[\\r\\t ]*ssid=".toRegex()
|
||||
private val pskParser = "^[\\r\\t ]*psk=\"(.*)\"\$".toRegex(RegexOption.MULTILINE)
|
||||
}
|
||||
|
||||
private val content by lazy { loggerSu("cat /data/misc/wifi/p2p_supplicant.conf") }
|
||||
|
||||
fun readPsk(): String? {
|
||||
return try {
|
||||
pskParser.findAll(content ?: return null).single().groupValues[1]
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, content)
|
||||
e.printStackTrace()
|
||||
Toast.makeText(app, e.message, Toast.LENGTH_LONG).show()
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun update(config: WifiConfiguration): Boolean? {
|
||||
val content = content ?: return null
|
||||
val tempFile = File.createTempFile("vpnhotspot-", ".conf", app.cacheDir)
|
||||
try {
|
||||
var ssidFound = false
|
||||
var pskFound = false
|
||||
tempFile.printWriter().use {
|
||||
for (line in content.lineSequence()) it.println(when {
|
||||
ssidMatcher.containsMatchIn(line) -> {
|
||||
ssidFound = true
|
||||
"\tssid=" + config.SSID.toByteArray()
|
||||
.joinToString("") { it.toInt().toString(16).padStart(2, '0') }
|
||||
}
|
||||
pskParser.containsMatchIn(line) -> {
|
||||
pskFound = true
|
||||
"\tpsk=\"${config.preSharedKey}\"" // no control chars or weird stuff
|
||||
}
|
||||
else -> line // do nothing
|
||||
})
|
||||
}
|
||||
if (!ssidFound || !pskFound) {
|
||||
Log.w(TAG, "Invalid conf ($ssidFound, $pskFound): $content")
|
||||
return false
|
||||
}
|
||||
return noisySu("cat ${tempFile.absolutePath} > /data/misc/wifi/p2p_supplicant.conf", "killall wpa_supplicant")
|
||||
} finally {
|
||||
if (!tempFile.delete()) tempFile.deleteOnExit()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package be.mygod.vpnhotspot.net.wifi
|
||||
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.net.wifi.WifiConfiguration
|
||||
import android.net.wifi.WifiConfiguration.AuthAlgorithm
|
||||
import android.os.Bundle
|
||||
import android.support.v7.app.AlertDialog
|
||||
import android.text.Editable
|
||||
import android.text.InputType
|
||||
import android.text.TextWatcher
|
||||
import android.view.View
|
||||
import android.widget.CheckBox
|
||||
import android.widget.EditText
|
||||
import android.widget.TextView
|
||||
import be.mygod.vpnhotspot.R
|
||||
import java.nio.charset.Charset
|
||||
|
||||
/**
|
||||
* https://android.googlesource.com/platform/packages/apps/Settings/+/39b4674/src/com/android/settings/wifi/WifiApDialog.java
|
||||
*/
|
||||
class WifiApDialog(mContext: Context, private val mListener: DialogInterface.OnClickListener,
|
||||
private val mWifiConfig: WifiConfiguration?) : AlertDialog(mContext), View.OnClickListener, TextWatcher {
|
||||
companion object {
|
||||
private const val BUTTON_SUBMIT = DialogInterface.BUTTON_POSITIVE
|
||||
}
|
||||
|
||||
private lateinit var mView: View
|
||||
private lateinit var mSsid: TextView
|
||||
private lateinit var mPassword: EditText
|
||||
/**
|
||||
* TODO: SSID in WifiConfiguration for soft ap
|
||||
* is being stored as a raw string without quotes.
|
||||
* This is not the case on the client side. We need to
|
||||
* make things consistent and clean it up
|
||||
*/
|
||||
val config: WifiConfiguration?
|
||||
get() {
|
||||
val config = WifiConfiguration()
|
||||
config.SSID = mSsid.text.toString()
|
||||
config.allowedAuthAlgorithms.set(AuthAlgorithm.OPEN)
|
||||
if (mPassword.length() != 0) {
|
||||
val password = mPassword.text.toString()
|
||||
config.preSharedKey = password
|
||||
}
|
||||
return config
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
mView = layoutInflater.inflate(R.layout.dialog_wifi_ap, null)
|
||||
setView(mView)
|
||||
val context = context
|
||||
setTitle(R.string.repeater_configure)
|
||||
mSsid = mView.findViewById(R.id.ssid)
|
||||
mPassword = mView.findViewById(R.id.password)
|
||||
setButton(BUTTON_SUBMIT, context.getString(R.string.wifi_save), mListener)
|
||||
setButton(DialogInterface.BUTTON_NEGATIVE,
|
||||
context.getString(R.string.wifi_cancel), mListener)
|
||||
setButton(DialogInterface.BUTTON_NEUTRAL, context.getString(R.string.repeater_reset_credentials), mListener)
|
||||
if (mWifiConfig != null) {
|
||||
mSsid.text = mWifiConfig.SSID
|
||||
mPassword.setText(mWifiConfig.preSharedKey)
|
||||
}
|
||||
mSsid.addTextChangedListener(this)
|
||||
mPassword.addTextChangedListener(this)
|
||||
(mView.findViewById(R.id.show_password) as CheckBox).setOnClickListener(this)
|
||||
super.onCreate(savedInstanceState)
|
||||
validate()
|
||||
}
|
||||
|
||||
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
|
||||
super.onRestoreInstanceState(savedInstanceState)
|
||||
mPassword.inputType = InputType.TYPE_CLASS_TEXT or
|
||||
if ((mView.findViewById(R.id.show_password) as CheckBox).isChecked)
|
||||
InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD
|
||||
else InputType.TYPE_TEXT_VARIATION_PASSWORD
|
||||
}
|
||||
|
||||
private fun validate() {
|
||||
val mSsidString = mSsid.text.toString()
|
||||
getButton(BUTTON_SUBMIT).isEnabled = mSsid.length() != 0 &&
|
||||
mPassword.length() >= 8 && Charset.forName("UTF-8").encode(mSsidString).limit() <= 32
|
||||
}
|
||||
|
||||
override fun onClick(view: View) {
|
||||
mPassword.inputType = InputType.TYPE_CLASS_TEXT or if ((view as CheckBox).isChecked)
|
||||
InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD
|
||||
else
|
||||
InputType.TYPE_TEXT_VARIATION_PASSWORD
|
||||
}
|
||||
|
||||
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) { }
|
||||
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) { }
|
||||
override fun afterTextChanged(editable: Editable) {
|
||||
validate()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package be.mygod.vpnhotspot.net.wifi
|
||||
|
||||
import android.content.Context
|
||||
import android.net.wifi.WifiConfiguration
|
||||
import android.net.wifi.WifiManager
|
||||
import be.mygod.vpnhotspot.App.Companion.app
|
||||
|
||||
@Deprecated("No longer usable since API 26.")
|
||||
object WifiApManager {
|
||||
private val wifi = app.getSystemService(Context.WIFI_SERVICE) as WifiManager
|
||||
private val setWifiApEnabled = WifiManager::class.java.getDeclaredMethod("setWifiApEnabled",
|
||||
WifiConfiguration::class.java, Boolean::class.java)
|
||||
/**
|
||||
* Start AccessPoint mode with the specified
|
||||
* configuration. If the radio is already running in
|
||||
* AP mode, update the new configuration
|
||||
* Note that starting in access point mode disables station
|
||||
* mode operation
|
||||
* @param wifiConfig SSID, security and channel details as
|
||||
* part of WifiConfiguration
|
||||
* @return {@code true} if the operation succeeds, {@code false} otherwise
|
||||
*/
|
||||
private fun WifiManager.setWifiApEnabled(wifiConfig: WifiConfiguration?, enabled: Boolean) =
|
||||
setWifiApEnabled.invoke(this, wifiConfig, enabled) as Boolean
|
||||
|
||||
fun start(wifiConfig: WifiConfiguration? = null) {
|
||||
wifi.isWifiEnabled = false
|
||||
wifi.setWifiApEnabled(wifiConfig, true)
|
||||
}
|
||||
fun stop() {
|
||||
wifi.setWifiApEnabled(null, false)
|
||||
wifi.isWifiEnabled = true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
package be.mygod.vpnhotspot.net.wifi
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.net.wifi.WpsInfo
|
||||
import android.net.wifi.p2p.WifiP2pGroup
|
||||
import android.net.wifi.p2p.WifiP2pManager
|
||||
import android.util.Log
|
||||
import com.android.dx.stock.ProxyBuilder
|
||||
import java.lang.reflect.Proxy
|
||||
import java.util.regex.Pattern
|
||||
|
||||
object WifiP2pManagerHelper {
|
||||
private const val TAG = "WifiP2pManagerHelper"
|
||||
|
||||
/**
|
||||
* Matches the output of dumpsys wifip2p. This part is available since Android 4.2.
|
||||
*
|
||||
* Related sources:
|
||||
* https://android.googlesource.com/platform/frameworks/base/+/f0afe4144d09aa9b980cffd444911ab118fa9cbe%5E%21/wifi/java/android/net/wifi/p2p/WifiP2pService.java
|
||||
* https://android.googlesource.com/platform/frameworks/opt/net/wifi/+/a8d5e40/service/java/com/android/server/wifi/p2p/WifiP2pServiceImpl.java#639
|
||||
*
|
||||
* https://android.googlesource.com/platform/frameworks/base.git/+/android-5.0.0_r1/core/java/android/net/NetworkInfo.java#433
|
||||
* https://android.googlesource.com/platform/frameworks/base.git/+/220871a/core/java/android/net/NetworkInfo.java#415
|
||||
*/
|
||||
val patternNetworkInfo = "^mNetworkInfo .* (isA|a)vailable: (true|false)".toPattern(Pattern.MULTILINE)
|
||||
|
||||
/**
|
||||
* Available since Android 4.4.
|
||||
*
|
||||
* Source: https://android.googlesource.com/platform/frameworks/base/+/android-4.4_r1/wifi/java/android/net/wifi/p2p/WifiP2pManager.java#994
|
||||
* Implementation: https://android.googlesource.com/platform/frameworks/opt/net/wifi/+/d72d2f4/service/java/com/android/server/wifi/p2p/SupplicantP2pIfaceHal.java#1159
|
||||
*/
|
||||
private val setWifiP2pChannels by lazy {
|
||||
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) {
|
||||
setWifiP2pChannels.invoke(this, c, lc, oc, listener)
|
||||
}
|
||||
|
||||
/**
|
||||
* Available since Android 4.3.
|
||||
*
|
||||
* Source: https://android.googlesource.com/platform/frameworks/base/+/android-4.3_r0.9/wifi/java/android/net/wifi/p2p/WifiP2pManager.java#958
|
||||
*/
|
||||
private val startWps by lazy {
|
||||
WifiP2pManager::class.java.getDeclaredMethod("startWps",
|
||||
WifiP2pManager.Channel::class.java, WpsInfo::class.java, WifiP2pManager.ActionListener::class.java)
|
||||
}
|
||||
fun WifiP2pManager.startWps(c: WifiP2pManager.Channel, wps: WpsInfo,
|
||||
listener: WifiP2pManager.ActionListener) {
|
||||
startWps.invoke(this, c, wps, listener)
|
||||
}
|
||||
|
||||
/**
|
||||
* Available since Android 4.2.
|
||||
*
|
||||
* Source: https://android.googlesource.com/platform/frameworks/base/+/android-4.2_r1/wifi/java/android/net/wifi/p2p/WifiP2pManager.java#1353
|
||||
*/
|
||||
private val deletePersistentGroup by lazy {
|
||||
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) {
|
||||
deletePersistentGroup.invoke(this, c, netId, listener)
|
||||
}
|
||||
|
||||
private val interfacePersistentGroupInfoListener by lazy @SuppressLint("PrivateApi") {
|
||||
Class.forName("android.net.wifi.p2p.WifiP2pManager\$PersistentGroupInfoListener")
|
||||
}
|
||||
private val getGroupList by lazy @SuppressLint("PrivateApi") {
|
||||
Class.forName("android.net.wifi.p2p.WifiP2pGroupList").getDeclaredMethod("getGroupList")
|
||||
}
|
||||
private val requestPersistentGroupInfo by lazy {
|
||||
WifiP2pManager::class.java.getDeclaredMethod("requestPersistentGroupInfo",
|
||||
WifiP2pManager.Channel::class.java, interfacePersistentGroupInfoListener)
|
||||
}
|
||||
/**
|
||||
* Request a list of all the persistent p2p groups stored in system.
|
||||
*
|
||||
* @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,
|
||||
arrayOf(interfacePersistentGroupInfoListener), { proxy, method, args ->
|
||||
if (method.name == "onPersistentGroupInfoAvailable") {
|
||||
if (args.size != 1) Log.w(TAG, "Unexpected args: $args")
|
||||
listener(getGroupList.invoke(args[0]) as Collection<WifiP2pGroup>)
|
||||
null
|
||||
} else {
|
||||
Log.w(TAG, "Unexpected method, calling super: $method")
|
||||
ProxyBuilder.callSuper(proxy, method, args)
|
||||
}
|
||||
})
|
||||
requestPersistentGroupInfo.invoke(this, c, proxy)
|
||||
}
|
||||
|
||||
/**
|
||||
* Available since Android 4.2.
|
||||
*
|
||||
* Source: https://android.googlesource.com/platform/frameworks/base/+/android-4.2_r1/wifi/java/android/net/wifi/p2p/WifiP2pGroup.java#253
|
||||
*/
|
||||
private val getNetworkId by lazy { WifiP2pGroup::class.java.getDeclaredMethod("getNetworkId") }
|
||||
val WifiP2pGroup.netId get() = getNetworkId.invoke(this) as Int
|
||||
}
|
||||
Reference in New Issue
Block a user