diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/RepeaterFragment.kt b/mobile/src/main/java/be/mygod/vpnhotspot/RepeaterFragment.kt
index ff27132e..b886f49f 100644
--- a/mobile/src/main/java/be/mygod/vpnhotspot/RepeaterFragment.kt
+++ b/mobile/src/main/java/be/mygod/vpnhotspot/RepeaterFragment.kt
@@ -1,12 +1,10 @@
package be.mygod.vpnhotspot
-import android.content.ComponentName
-import android.content.Context
-import android.content.Intent
-import android.content.ServiceConnection
+import android.content.*
import android.databinding.BaseObservable
import android.databinding.Bindable
import android.databinding.DataBindingUtil
+import android.net.wifi.WifiConfiguration
import android.net.wifi.p2p.WifiP2pDevice
import android.net.wifi.p2p.WifiP2pGroup
import android.os.Bundle
@@ -24,12 +22,15 @@ import android.support.v7.widget.RecyclerView
import android.support.v7.widget.Toolbar
import android.view.*
import android.widget.EditText
+import android.widget.Toast
import be.mygod.vpnhotspot.databinding.FragmentRepeaterBinding
import be.mygod.vpnhotspot.databinding.ListitemClientBinding
import be.mygod.vpnhotspot.net.IpNeighbour
import be.mygod.vpnhotspot.net.IpNeighbourMonitor
import be.mygod.vpnhotspot.net.TetheringManager
import be.mygod.vpnhotspot.net.TetherType
+import be.mygod.vpnhotspot.net.wifi.P2pSupplicantConfiguration
+import be.mygod.vpnhotspot.net.wifi.WifiApDialog
import java.net.NetworkInterface
import java.net.SocketException
import java.util.*
@@ -60,7 +61,6 @@ class RepeaterFragment : Fragment(), ServiceConnection, Toolbar.OnMenuItemClickL
}
val ssid @Bindable get() = binder?.ssid ?: getText(R.string.repeater_inactive)
- val password @Bindable get() = binder?.password ?: ""
val addresses @Bindable get(): String {
return try {
NetworkInterface.getByName(p2pInterface ?: return "")?.formatAddresses() ?: ""
@@ -78,7 +78,6 @@ class RepeaterFragment : Fragment(), ServiceConnection, Toolbar.OnMenuItemClickL
}
fun onGroupChanged(group: WifiP2pGroup?) {
notifyPropertyChanged(BR.ssid)
- notifyPropertyChanged(BR.password)
p2pInterface = group?.`interface`
notifyPropertyChanged(BR.addresses)
adapter.p2p = group?.clientList ?: emptyList()
@@ -227,19 +226,46 @@ class RepeaterFragment : Fragment(), ServiceConnection, Toolbar.OnMenuItemClickL
dialog.show()
true
} else false
- R.id.resetGroup -> {
- AlertDialog.Builder(requireContext())
- .setTitle(R.string.repeater_reset_credentials)
- .setMessage(getString(R.string.repeater_reset_credentials_dialog_message))
- .setPositiveButton(R.string.repeater_reset_credentials_dialog_reset,
- { _, _ -> binder?.resetCredentials() })
- .setNegativeButton(android.R.string.cancel, null)
- .show()
+ R.id.edit -> {
+ editConfigurations()
true
}
else -> false
}
+ private fun editConfigurations() {
+ val binder = binder
+ val ssid = binder?.ssid
+ val context = requireContext()
+ if (ssid != null) {
+ val wifi = WifiConfiguration()
+ val conf = P2pSupplicantConfiguration()
+ wifi.SSID = ssid
+ wifi.preSharedKey = binder.password
+ if (wifi.preSharedKey == null || wifi.preSharedKey.length < 8) wifi.preSharedKey = conf.readPsk()
+ if (wifi.preSharedKey == null || wifi.preSharedKey.length < 8) {
+ Toast.makeText(context, R.string.root_unavailable, Toast.LENGTH_SHORT).show()
+ return
+ }
+ if (wifi.preSharedKey != null && wifi.preSharedKey.length >= 8) {
+ var dialog: WifiApDialog? = null
+ dialog = WifiApDialog(context, DialogInterface.OnClickListener { _, which ->
+ when (which) {
+ DialogInterface.BUTTON_POSITIVE -> when (conf.update(dialog!!.config!!)) {
+ true -> binder.requestGroupUpdate()
+ false -> Toast.makeText(context, R.string.noisy_su_failure, Toast.LENGTH_SHORT).show()
+ null -> Toast.makeText(context, R.string.root_unavailable, Toast.LENGTH_SHORT).show()
+ }
+ DialogInterface.BUTTON_NEUTRAL -> binder.resetCredentials()
+ }
+ }, wifi)
+ dialog.show()
+ return
+ }
+ }
+ Toast.makeText(context, R.string.repeater_configure_failure, Toast.LENGTH_LONG).show()
+ }
+
override fun onIpNeighbourAvailable(neighbours: Map) {
adapter.neighbours = neighbours.values.toList()
}
diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/RepeaterService.kt b/mobile/src/main/java/be/mygod/vpnhotspot/RepeaterService.kt
index 55aaaa25..3093970c 100644
--- a/mobile/src/main/java/be/mygod/vpnhotspot/RepeaterService.kt
+++ b/mobile/src/main/java/be/mygod/vpnhotspot/RepeaterService.kt
@@ -18,12 +18,12 @@ import android.widget.Toast
import be.mygod.vpnhotspot.App.Companion.app
import be.mygod.vpnhotspot.net.Routing
import be.mygod.vpnhotspot.net.VpnMonitor
-import be.mygod.vpnhotspot.net.WifiP2pManagerHelper
-import be.mygod.vpnhotspot.net.WifiP2pManagerHelper.deletePersistentGroup
-import be.mygod.vpnhotspot.net.WifiP2pManagerHelper.netId
-import be.mygod.vpnhotspot.net.WifiP2pManagerHelper.requestPersistentGroupInfo
-import be.mygod.vpnhotspot.net.WifiP2pManagerHelper.setWifiP2pChannels
-import be.mygod.vpnhotspot.net.WifiP2pManagerHelper.startWps
+import be.mygod.vpnhotspot.net.wifi.WifiP2pManagerHelper
+import be.mygod.vpnhotspot.net.wifi.WifiP2pManagerHelper.deletePersistentGroup
+import be.mygod.vpnhotspot.net.wifi.WifiP2pManagerHelper.netId
+import be.mygod.vpnhotspot.net.wifi.WifiP2pManagerHelper.requestPersistentGroupInfo
+import be.mygod.vpnhotspot.net.wifi.WifiP2pManagerHelper.setWifiP2pChannels
+import be.mygod.vpnhotspot.net.wifi.WifiP2pManagerHelper.startWps
import java.net.InetAddress
import java.net.SocketException
@@ -74,6 +74,22 @@ class RepeaterService : Service(), WifiP2pManager.ChannelListener, VpnMonitor.Ca
formatReason(R.string.repeater_reset_credentials_failure, reason), Toast.LENGTH_SHORT).show()
})
}
+
+ fun requestGroupUpdate() {
+ group = null
+ try {
+ p2pManager.requestPersistentGroupInfo(channel, {
+ when (it.size) {
+ 0 -> { }
+ 1 -> group = it.single()
+ else -> Log.w(TAG, "Unexpected groups: $it")
+ }
+ })
+ } catch (e: Exception) {
+ e.printStackTrace()
+ Toast.makeText(this@RepeaterService, e.message, Toast.LENGTH_LONG).show()
+ }
+ }
}
private lateinit var p2pManager: WifiP2pManager
@@ -152,18 +168,7 @@ class RepeaterService : Service(), WifiP2pManager.ChannelListener, VpnMonitor.Ca
override fun onChannelDisconnected() {
channel = p2pManager.initialize(this, Looper.getMainLooper(), this)
setOperatingChannel(true)
- try {
- p2pManager.requestPersistentGroupInfo(channel, {
- when (it.size) {
- 0 -> { }
- 1 -> group = it.single()
- else -> Log.w(TAG, "Unexpected groups: $it")
- }
- })
- } catch (e: Exception) {
- e.printStackTrace()
- Toast.makeText(this, e.message, Toast.LENGTH_LONG).show()
- }
+ binder.requestGroupUpdate()
}
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/TetheringFragment.kt b/mobile/src/main/java/be/mygod/vpnhotspot/TetheringFragment.kt
index 5f31a825..794b9813 100644
--- a/mobile/src/main/java/be/mygod/vpnhotspot/TetheringFragment.kt
+++ b/mobile/src/main/java/be/mygod/vpnhotspot/TetheringFragment.kt
@@ -30,7 +30,7 @@ import be.mygod.vpnhotspot.databinding.ListitemInterfaceBinding
import be.mygod.vpnhotspot.databinding.ListitemManageTetherBinding
import be.mygod.vpnhotspot.net.TetherType
import be.mygod.vpnhotspot.net.TetheringManager
-import be.mygod.vpnhotspot.net.WifiApManager
+import be.mygod.vpnhotspot.net.wifi.WifiApManager
import java.lang.reflect.InvocationTargetException
import java.net.NetworkInterface
import java.net.SocketException
diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/P2pSupplicantConfiguration.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/P2pSupplicantConfiguration.kt
new file mode 100644
index 00000000..4b8e2245
--- /dev/null
+++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/P2pSupplicantConfiguration.kt
@@ -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()
+ }
+ }
+}
diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiApDialog.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiApDialog.kt
new file mode 100644
index 00000000..00778a3e
--- /dev/null
+++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiApDialog.kt
@@ -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()
+ }
+}
diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/WifiApManager.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiApManager.kt
similarity index 97%
rename from mobile/src/main/java/be/mygod/vpnhotspot/net/WifiApManager.kt
rename to mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiApManager.kt
index 20309ae2..62238251 100644
--- a/mobile/src/main/java/be/mygod/vpnhotspot/net/WifiApManager.kt
+++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiApManager.kt
@@ -1,4 +1,4 @@
-package be.mygod.vpnhotspot.net
+package be.mygod.vpnhotspot.net.wifi
import android.content.Context
import android.net.wifi.WifiConfiguration
diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/WifiP2pManagerHelper.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiP2pManagerHelper.kt
similarity index 99%
rename from mobile/src/main/java/be/mygod/vpnhotspot/net/WifiP2pManagerHelper.kt
rename to mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiP2pManagerHelper.kt
index 344113bb..78157450 100644
--- a/mobile/src/main/java/be/mygod/vpnhotspot/net/WifiP2pManagerHelper.kt
+++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiP2pManagerHelper.kt
@@ -1,4 +1,4 @@
-package be.mygod.vpnhotspot.net
+package be.mygod.vpnhotspot.net.wifi
import android.annotation.SuppressLint
import android.net.wifi.WpsInfo
diff --git a/mobile/src/main/res/drawable/ic_action_autorenew.xml b/mobile/src/main/res/drawable/ic_action_autorenew.xml
deleted file mode 100644
index 794ca6ad..00000000
--- a/mobile/src/main/res/drawable/ic_action_autorenew.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
diff --git a/mobile/src/main/res/drawable/ic_image_edit.xml b/mobile/src/main/res/drawable/ic_image_edit.xml
new file mode 100644
index 00000000..2ab2fb75
--- /dev/null
+++ b/mobile/src/main/res/drawable/ic_image_edit.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/mobile/src/main/res/layout/dialog_wifi_ap.xml b/mobile/src/main/res/layout/dialog_wifi_ap.xml
new file mode 100644
index 00000000..77adb6bc
--- /dev/null
+++ b/mobile/src/main/res/layout/dialog_wifi_ap.xml
@@ -0,0 +1,93 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/src/main/res/layout/fragment_repeater.xml b/mobile/src/main/res/layout/fragment_repeater.xml
index 63964926..69286462 100644
--- a/mobile/src/main/res/layout/fragment_repeater.xml
+++ b/mobile/src/main/res/layout/fragment_repeater.xml
@@ -40,7 +40,7 @@
+ tools:text="DIRECT-rA-nd0m"/>
-
-
-
-
@@ -89,7 +71,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_column="2"
- android:layout_row="2"
+ android:layout_row="1"
android:focusable="false"
android:text="@{data.addresses}"
android:textIsSelectable="true"
diff --git a/mobile/src/main/res/menu/repeater.xml b/mobile/src/main/res/menu/repeater.xml
index 8d8ff517..080f0a17 100644
--- a/mobile/src/main/res/menu/repeater.xml
+++ b/mobile/src/main/res/menu/repeater.xml
@@ -8,8 +8,8 @@
android:title="@string/repeater_wps"
app:showAsAction="ifRoom"/>
\ No newline at end of file
diff --git a/mobile/src/main/res/values-zh-rCN/strings.xml b/mobile/src/main/res/values-zh-rCN/strings.xml
index 8413265a..6bb73d79 100644
--- a/mobile/src/main/res/values-zh-rCN/strings.xml
+++ b/mobile/src/main/res/values-zh-rCN/strings.xml
@@ -5,15 +5,15 @@
系统共享
设置选项
- 中继名称
- 中继密码
中继地址
输入 PIN
一键加密
请在 2 分钟内在需要连接的设备上使用一键加密以连接到此中继。
成功注册 PIN。
打开 WPS 失败(原因:%s)
- 重置凭据
+ 设置 WLAN 中继
+ 未能找到有效的档案。请尝试先打开中继。
+ 重置
Android
系统将在下次打开中继时生成新的中继名称和密码。该操作不可撤销。
重置
@@ -78,4 +78,12 @@
错误:未找到下游接口
似乎没有 root
发生异常,详情请查看调试信息。
+
+ "网络名称"
+ "输入 SSID"
+ "密码"
+ "显示密码"
+ "密码至少应包含 8 个字符。"
+ "保存"
+ "取消"
diff --git a/mobile/src/main/res/values/strings.xml b/mobile/src/main/res/values/strings.xml
index 30a3df60..16b80dbd 100644
--- a/mobile/src/main/res/values/strings.xml
+++ b/mobile/src/main/res/values/strings.xml
@@ -1,12 +1,19 @@
+
VPN Hotspot
Repeater
Tethering
Settings
- Network name
- Password
Addresses
WPS
Enter PIN
@@ -15,10 +22,9 @@
device.
PIN registered.
Failed to start WPS (reason: %s)
- Reset credentials
- Android system will generate new network name and password
- next time repeater is activated. This is irreversible.
- Reset
+ Configure Wi\u2011Fi repeater
+ Valid config not found. Please start repeater first.
+ Reset
Credentials reset.
Failed to reset credentials (reason: %s)
@@ -36,14 +42,6 @@
unknown #%d
Manage…
-
USB tethering
Wi\u2011Fi hotspot
Wi\u2011Fi hotspot (legacy)
@@ -82,4 +80,12 @@
Fatal: Downstream interface not found
Root unavailable
Something went wrong, please check the debug information.
+
+ Network name
+ Enter the SSID
+ Password
+ Show password
+ The password must have at least 8 characters.
+ Save
+ Cancel
diff --git a/mobile/src/main/res/values/styles.xml b/mobile/src/main/res/values/styles.xml
index 4ada3577..95c9fe8c 100644
--- a/mobile/src/main/res/values/styles.xml
+++ b/mobile/src/main/res/values/styles.xml
@@ -13,4 +13,35 @@
- 8dp
+
+
+
+
+
+
+