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:
Mygod
2018-04-21 20:18:15 -07:00
parent 6608a76297
commit 570998b255
15 changed files with 393 additions and 84 deletions

View File

@@ -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()
}
}
}

View File

@@ -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()
}
}

View File

@@ -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
}
}

View File

@@ -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
}