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:
@@ -1,12 +1,10 @@
|
|||||||
package be.mygod.vpnhotspot
|
package be.mygod.vpnhotspot
|
||||||
|
|
||||||
import android.content.ComponentName
|
import android.content.*
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
|
||||||
import android.content.ServiceConnection
|
|
||||||
import android.databinding.BaseObservable
|
import android.databinding.BaseObservable
|
||||||
import android.databinding.Bindable
|
import android.databinding.Bindable
|
||||||
import android.databinding.DataBindingUtil
|
import android.databinding.DataBindingUtil
|
||||||
|
import android.net.wifi.WifiConfiguration
|
||||||
import android.net.wifi.p2p.WifiP2pDevice
|
import android.net.wifi.p2p.WifiP2pDevice
|
||||||
import android.net.wifi.p2p.WifiP2pGroup
|
import android.net.wifi.p2p.WifiP2pGroup
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
@@ -24,12 +22,15 @@ import android.support.v7.widget.RecyclerView
|
|||||||
import android.support.v7.widget.Toolbar
|
import android.support.v7.widget.Toolbar
|
||||||
import android.view.*
|
import android.view.*
|
||||||
import android.widget.EditText
|
import android.widget.EditText
|
||||||
|
import android.widget.Toast
|
||||||
import be.mygod.vpnhotspot.databinding.FragmentRepeaterBinding
|
import be.mygod.vpnhotspot.databinding.FragmentRepeaterBinding
|
||||||
import be.mygod.vpnhotspot.databinding.ListitemClientBinding
|
import be.mygod.vpnhotspot.databinding.ListitemClientBinding
|
||||||
import be.mygod.vpnhotspot.net.IpNeighbour
|
import be.mygod.vpnhotspot.net.IpNeighbour
|
||||||
import be.mygod.vpnhotspot.net.IpNeighbourMonitor
|
import be.mygod.vpnhotspot.net.IpNeighbourMonitor
|
||||||
import be.mygod.vpnhotspot.net.TetheringManager
|
import be.mygod.vpnhotspot.net.TetheringManager
|
||||||
import be.mygod.vpnhotspot.net.TetherType
|
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.NetworkInterface
|
||||||
import java.net.SocketException
|
import java.net.SocketException
|
||||||
import java.util.*
|
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 ssid @Bindable get() = binder?.ssid ?: getText(R.string.repeater_inactive)
|
||||||
val password @Bindable get() = binder?.password ?: ""
|
|
||||||
val addresses @Bindable get(): String {
|
val addresses @Bindable get(): String {
|
||||||
return try {
|
return try {
|
||||||
NetworkInterface.getByName(p2pInterface ?: return "")?.formatAddresses() ?: ""
|
NetworkInterface.getByName(p2pInterface ?: return "")?.formatAddresses() ?: ""
|
||||||
@@ -78,7 +78,6 @@ class RepeaterFragment : Fragment(), ServiceConnection, Toolbar.OnMenuItemClickL
|
|||||||
}
|
}
|
||||||
fun onGroupChanged(group: WifiP2pGroup?) {
|
fun onGroupChanged(group: WifiP2pGroup?) {
|
||||||
notifyPropertyChanged(BR.ssid)
|
notifyPropertyChanged(BR.ssid)
|
||||||
notifyPropertyChanged(BR.password)
|
|
||||||
p2pInterface = group?.`interface`
|
p2pInterface = group?.`interface`
|
||||||
notifyPropertyChanged(BR.addresses)
|
notifyPropertyChanged(BR.addresses)
|
||||||
adapter.p2p = group?.clientList ?: emptyList()
|
adapter.p2p = group?.clientList ?: emptyList()
|
||||||
@@ -227,19 +226,46 @@ class RepeaterFragment : Fragment(), ServiceConnection, Toolbar.OnMenuItemClickL
|
|||||||
dialog.show()
|
dialog.show()
|
||||||
true
|
true
|
||||||
} else false
|
} else false
|
||||||
R.id.resetGroup -> {
|
R.id.edit -> {
|
||||||
AlertDialog.Builder(requireContext())
|
editConfigurations()
|
||||||
.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()
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
else -> false
|
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<String, IpNeighbour>) {
|
override fun onIpNeighbourAvailable(neighbours: Map<String, IpNeighbour>) {
|
||||||
adapter.neighbours = neighbours.values.toList()
|
adapter.neighbours = neighbours.values.toList()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,12 +18,12 @@ import android.widget.Toast
|
|||||||
import be.mygod.vpnhotspot.App.Companion.app
|
import be.mygod.vpnhotspot.App.Companion.app
|
||||||
import be.mygod.vpnhotspot.net.Routing
|
import be.mygod.vpnhotspot.net.Routing
|
||||||
import be.mygod.vpnhotspot.net.VpnMonitor
|
import be.mygod.vpnhotspot.net.VpnMonitor
|
||||||
import be.mygod.vpnhotspot.net.WifiP2pManagerHelper
|
import be.mygod.vpnhotspot.net.wifi.WifiP2pManagerHelper
|
||||||
import be.mygod.vpnhotspot.net.WifiP2pManagerHelper.deletePersistentGroup
|
import be.mygod.vpnhotspot.net.wifi.WifiP2pManagerHelper.deletePersistentGroup
|
||||||
import be.mygod.vpnhotspot.net.WifiP2pManagerHelper.netId
|
import be.mygod.vpnhotspot.net.wifi.WifiP2pManagerHelper.netId
|
||||||
import be.mygod.vpnhotspot.net.WifiP2pManagerHelper.requestPersistentGroupInfo
|
import be.mygod.vpnhotspot.net.wifi.WifiP2pManagerHelper.requestPersistentGroupInfo
|
||||||
import be.mygod.vpnhotspot.net.WifiP2pManagerHelper.setWifiP2pChannels
|
import be.mygod.vpnhotspot.net.wifi.WifiP2pManagerHelper.setWifiP2pChannels
|
||||||
import be.mygod.vpnhotspot.net.WifiP2pManagerHelper.startWps
|
import be.mygod.vpnhotspot.net.wifi.WifiP2pManagerHelper.startWps
|
||||||
import java.net.InetAddress
|
import java.net.InetAddress
|
||||||
import java.net.SocketException
|
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()
|
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
|
private lateinit var p2pManager: WifiP2pManager
|
||||||
@@ -152,18 +168,7 @@ class RepeaterService : Service(), WifiP2pManager.ChannelListener, VpnMonitor.Ca
|
|||||||
override fun onChannelDisconnected() {
|
override fun onChannelDisconnected() {
|
||||||
channel = p2pManager.initialize(this, Looper.getMainLooper(), this)
|
channel = p2pManager.initialize(this, Looper.getMainLooper(), this)
|
||||||
setOperatingChannel(true)
|
setOperatingChannel(true)
|
||||||
try {
|
binder.requestGroupUpdate()
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
|
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ import be.mygod.vpnhotspot.databinding.ListitemInterfaceBinding
|
|||||||
import be.mygod.vpnhotspot.databinding.ListitemManageTetherBinding
|
import be.mygod.vpnhotspot.databinding.ListitemManageTetherBinding
|
||||||
import be.mygod.vpnhotspot.net.TetherType
|
import be.mygod.vpnhotspot.net.TetherType
|
||||||
import be.mygod.vpnhotspot.net.TetheringManager
|
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.lang.reflect.InvocationTargetException
|
||||||
import java.net.NetworkInterface
|
import java.net.NetworkInterface
|
||||||
import java.net.SocketException
|
import java.net.SocketException
|
||||||
|
|||||||
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package be.mygod.vpnhotspot.net
|
package be.mygod.vpnhotspot.net.wifi
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.wifi.WifiConfiguration
|
import android.net.wifi.WifiConfiguration
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package be.mygod.vpnhotspot.net
|
package be.mygod.vpnhotspot.net.wifi
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.net.wifi.WpsInfo
|
import android.net.wifi.WpsInfo
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:viewportWidth="24.0"
|
|
||||||
android:viewportHeight="24.0">
|
|
||||||
<path
|
|
||||||
android:fillColor="#FF000000"
|
|
||||||
android:pathData="M12,6v3l4,-4 -4,-4v3c-4.42,0 -8,3.58 -8,8 0,1.57 0.46,3.03 1.24,4.26L6.7,14.8c-0.45,-0.83 -0.7,-1.79 -0.7,-2.8 0,-3.31 2.69,-6 6,-6zM18.76,7.74L17.3,9.2c0.44,0.84 0.7,1.79 0.7,2.8 0,3.31 -2.69,6 -6,6v-3l-4,4 4,4v-3c4.42,0 8,-3.58 8,-8 0,-1.57 -0.46,-3.03 -1.24,-4.26z"/>
|
|
||||||
</vector>
|
|
||||||
9
mobile/src/main/res/drawable/ic_image_edit.xml
Normal file
9
mobile/src/main/res/drawable/ic_image_edit.xml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24.0"
|
||||||
|
android:viewportHeight="24.0">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="M3,17.25V21h3.75L17.81,9.94l-3.75,-3.75L3,17.25zM20.71,7.04c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 3.75,3.75 1.83,-1.83z"/>
|
||||||
|
</vector>
|
||||||
93
mobile/src/main/res/layout/dialog_wifi_ap.xml
Normal file
93
mobile/src/main/res/layout/dialog_wifi_ap.xml
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Source: https://android.googlesource.com/platform/packages/apps/Settings/+/6b4a31c/res/layout/wifi_ap_dialog.xml -->
|
||||||
|
<!-- Copyright (C) 2010 The Android Open Source Project
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
-->
|
||||||
|
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="300sp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:fadeScrollbars="false">
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:descendantFocusability="beforeDescendants"
|
||||||
|
android:focusableInTouchMode="true"
|
||||||
|
android:orientation="vertical">
|
||||||
|
<LinearLayout android:id="@+id/info"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
style="@style/wifi_section" />
|
||||||
|
<LinearLayout android:id="@+id/type"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
style="@style/wifi_section">
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
style="@style/wifi_item">
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
style="@style/wifi_item_label"
|
||||||
|
android:layout_marginTop="8dip"
|
||||||
|
android:text="@string/wifi_ssid" />
|
||||||
|
<EditText android:id="@+id/ssid"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
style="@style/wifi_item_edit_content"
|
||||||
|
android:hint="@string/wifi_ssid_hint"
|
||||||
|
android:inputType="textNoSuggestions"
|
||||||
|
android:maxLength="32" />
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
<LinearLayout android:id="@+id/fields"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
style="@style/wifi_section">
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
style="@style/wifi_item">
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
style="@style/wifi_item_label"
|
||||||
|
android:layout_marginTop="8dip"
|
||||||
|
android:text="@string/wifi_password" />
|
||||||
|
<EditText android:id="@+id/password"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
style="@style/wifi_item_edit_content"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:inputType="textPassword"
|
||||||
|
android:maxLength="63"
|
||||||
|
android:imeOptions="flagForceAscii" />
|
||||||
|
</LinearLayout>
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
style="@style/wifi_item">
|
||||||
|
<TextView android:id="@+id/hint"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
style="@style/wifi_item_label"
|
||||||
|
android:text="@string/credentials_password_too_short"
|
||||||
|
android:layout_marginTop="8dip"
|
||||||
|
android:layout_marginBottom="10sp"/>
|
||||||
|
<CheckBox android:id="@+id/show_password"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
style="@style/wifi_item_content"
|
||||||
|
android:text="@string/wifi_show_password" />
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
</ScrollView>
|
||||||
@@ -40,7 +40,7 @@
|
|||||||
<TextView
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/repeater_ssid"
|
android:text="@string/wifi_ssid"
|
||||||
android:textAppearance="@style/TextAppearance.AppCompat.Subhead"/>
|
android:textAppearance="@style/TextAppearance.AppCompat.Subhead"/>
|
||||||
|
|
||||||
<Space
|
<Space
|
||||||
@@ -57,31 +57,13 @@
|
|||||||
android:focusable="false"
|
android:focusable="false"
|
||||||
android:text="@{data.ssid}"
|
android:text="@{data.ssid}"
|
||||||
android:textIsSelectable="true"
|
android:textIsSelectable="true"
|
||||||
tools:text="DIRECT-rAnd0m"/>
|
tools:text="DIRECT-rA-nd0m"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_column="0"
|
android:layout_column="0"
|
||||||
android:layout_row="1"
|
android:layout_row="1"
|
||||||
android:text="@string/repeater_password"
|
|
||||||
android:textAppearance="@style/TextAppearance.AppCompat.Subhead"/>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_column="2"
|
|
||||||
android:layout_row="1"
|
|
||||||
android:focusable="false"
|
|
||||||
android:text="@{data.password}"
|
|
||||||
android:textIsSelectable="true"
|
|
||||||
tools:text="p4ssW0rd"/>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_column="0"
|
|
||||||
android:layout_row="2"
|
|
||||||
android:text="@string/repeater_addresses"
|
android:text="@string/repeater_addresses"
|
||||||
android:textAppearance="@style/TextAppearance.AppCompat.Subhead"/>
|
android:textAppearance="@style/TextAppearance.AppCompat.Subhead"/>
|
||||||
|
|
||||||
@@ -89,7 +71,7 @@
|
|||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_column="2"
|
android:layout_column="2"
|
||||||
android:layout_row="2"
|
android:layout_row="1"
|
||||||
android:focusable="false"
|
android:focusable="false"
|
||||||
android:text="@{data.addresses}"
|
android:text="@{data.addresses}"
|
||||||
android:textIsSelectable="true"
|
android:textIsSelectable="true"
|
||||||
|
|||||||
@@ -8,8 +8,8 @@
|
|||||||
android:title="@string/repeater_wps"
|
android:title="@string/repeater_wps"
|
||||||
app:showAsAction="ifRoom"/>
|
app:showAsAction="ifRoom"/>
|
||||||
<item
|
<item
|
||||||
android:id="@+id/resetGroup"
|
android:id="@+id/edit"
|
||||||
android:icon="@drawable/ic_action_autorenew"
|
android:icon="@drawable/ic_image_edit"
|
||||||
android:title="@string/repeater_reset_credentials"
|
android:title="@string/repeater_reset_credentials"
|
||||||
app:showAsAction="ifRoom"/>
|
app:showAsAction="ifRoom"/>
|
||||||
</menu>
|
</menu>
|
||||||
@@ -5,15 +5,15 @@
|
|||||||
<string name="title_tethering">系统共享</string>
|
<string name="title_tethering">系统共享</string>
|
||||||
<string name="title_settings">设置选项</string>
|
<string name="title_settings">设置选项</string>
|
||||||
|
|
||||||
<string name="repeater_ssid">中继名称</string>
|
|
||||||
<string name="repeater_password">中继密码</string>
|
|
||||||
<string name="repeater_addresses">中继地址</string>
|
<string name="repeater_addresses">中继地址</string>
|
||||||
<string name="repeater_wps_dialog_title">输入 PIN</string>
|
<string name="repeater_wps_dialog_title">输入 PIN</string>
|
||||||
<string name="repeater_wps_dialog_pbc">一键加密</string>
|
<string name="repeater_wps_dialog_pbc">一键加密</string>
|
||||||
<string name="repeater_wps_success_pbc">请在 2 分钟内在需要连接的设备上使用一键加密以连接到此中继。</string>
|
<string name="repeater_wps_success_pbc">请在 2 分钟内在需要连接的设备上使用一键加密以连接到此中继。</string>
|
||||||
<string name="repeater_wps_success_keypad">成功注册 PIN。</string>
|
<string name="repeater_wps_success_keypad">成功注册 PIN。</string>
|
||||||
<string name="repeater_wps_failure">打开 WPS 失败(原因:%s)</string>
|
<string name="repeater_wps_failure">打开 WPS 失败(原因:%s)</string>
|
||||||
<string name="repeater_reset_credentials">重置凭据</string>
|
<string name="repeater_configure">设置 WLAN 中继</string>
|
||||||
|
<string name="repeater_configure_failure">未能找到有效的档案。请尝试先打开中继。</string>
|
||||||
|
<string name="repeater_reset_credentials">重置</string>
|
||||||
<string name="repeater_reset_credentials_dialog_message">Android
|
<string name="repeater_reset_credentials_dialog_message">Android
|
||||||
系统将在下次打开中继时生成新的中继名称和密码。该操作不可撤销。</string>
|
系统将在下次打开中继时生成新的中继名称和密码。该操作不可撤销。</string>
|
||||||
<string name="repeater_reset_credentials_dialog_reset">重置</string>
|
<string name="repeater_reset_credentials_dialog_reset">重置</string>
|
||||||
@@ -78,4 +78,12 @@
|
|||||||
<string name="exception_interface_not_found">错误:未找到下游接口</string>
|
<string name="exception_interface_not_found">错误:未找到下游接口</string>
|
||||||
<string name="root_unavailable">似乎没有 root</string>
|
<string name="root_unavailable">似乎没有 root</string>
|
||||||
<string name="noisy_su_failure">发生异常,详情请查看调试信息。</string>
|
<string name="noisy_su_failure">发生异常,详情请查看调试信息。</string>
|
||||||
|
|
||||||
|
<string name="wifi_ssid" msgid="5519636102673067319">"网络名称"</string>
|
||||||
|
<string name="wifi_ssid_hint" msgid="897593601067321355">"输入 SSID"</string>
|
||||||
|
<string name="wifi_password" msgid="5948219759936151048">"密码"</string>
|
||||||
|
<string name="wifi_show_password" msgid="6461249871236968884">"显示密码"</string>
|
||||||
|
<string name="credentials_password_too_short" msgid="7502749986405522663">"密码至少应包含 8 个字符。"</string>
|
||||||
|
<string name="wifi_save" msgid="3331121567988522826">"保存"</string>
|
||||||
|
<string name="wifi_cancel" msgid="6763568902542968964">"取消"</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -1,12 +1,19 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
Values copied from:
|
||||||
|
* https://android.googlesource.com/platform/packages/apps/Settings/+/7686ef8/res/xml/tether_prefs.xml
|
||||||
|
* https://android.googlesource.com/platform/packages/apps/Settings/+/e5ed810/res/values/strings.xml
|
||||||
|
* @string/wifi_tether_configure_ap_text
|
||||||
|
* @string/usb_tethering_button_text
|
||||||
|
* @string/wifi_hotspot_checkbox_text
|
||||||
|
* @string/bluetooth_tether_checkbox_text
|
||||||
|
-->
|
||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">VPN Hotspot</string>
|
<string name="app_name">VPN Hotspot</string>
|
||||||
<string name="title_repeater">Repeater</string>
|
<string name="title_repeater">Repeater</string>
|
||||||
<string name="title_tethering">Tethering</string>
|
<string name="title_tethering">Tethering</string>
|
||||||
<string name="title_settings">Settings</string>
|
<string name="title_settings">Settings</string>
|
||||||
|
|
||||||
<string name="repeater_ssid">Network name</string>
|
|
||||||
<string name="repeater_password">Password</string>
|
|
||||||
<string name="repeater_addresses">Addresses</string>
|
<string name="repeater_addresses">Addresses</string>
|
||||||
<string name="repeater_wps">WPS</string>
|
<string name="repeater_wps">WPS</string>
|
||||||
<string name="repeater_wps_dialog_title">Enter PIN</string>
|
<string name="repeater_wps_dialog_title">Enter PIN</string>
|
||||||
@@ -15,10 +22,9 @@
|
|||||||
device.</string>
|
device.</string>
|
||||||
<string name="repeater_wps_success_keypad">PIN registered.</string>
|
<string name="repeater_wps_success_keypad">PIN registered.</string>
|
||||||
<string name="repeater_wps_failure">Failed to start WPS (reason: %s)</string>
|
<string name="repeater_wps_failure">Failed to start WPS (reason: %s)</string>
|
||||||
<string name="repeater_reset_credentials">Reset credentials</string>
|
<string name="repeater_configure">Configure Wi\u2011Fi repeater</string>
|
||||||
<string name="repeater_reset_credentials_dialog_message">Android system will generate new network name and password
|
<string name="repeater_configure_failure">Valid config not found. Please start repeater first.</string>
|
||||||
next time repeater is activated. This is irreversible.</string>
|
<string name="repeater_reset_credentials">Reset</string>
|
||||||
<string name="repeater_reset_credentials_dialog_reset">Reset</string>
|
|
||||||
<string name="repeater_reset_credentials_success">Credentials reset.</string>
|
<string name="repeater_reset_credentials_success">Credentials reset.</string>
|
||||||
<string name="repeater_reset_credentials_failure">Failed to reset credentials (reason: %s)</string>
|
<string name="repeater_reset_credentials_failure">Failed to reset credentials (reason: %s)</string>
|
||||||
|
|
||||||
@@ -36,14 +42,6 @@
|
|||||||
<string name="repeater_failure_reason_unknown">unknown #%d</string>
|
<string name="repeater_failure_reason_unknown">unknown #%d</string>
|
||||||
|
|
||||||
<string name="tethering_manage">Manage…</string>
|
<string name="tethering_manage">Manage…</string>
|
||||||
<!--
|
|
||||||
Values copied from:
|
|
||||||
* https://android.googlesource.com/platform/packages/apps/Settings/+/7686ef8/res/xml/tether_prefs.xml
|
|
||||||
* https://android.googlesource.com/platform/packages/apps/Settings/+/e5ed810/res/values/strings.xml
|
|
||||||
* @string/usb_tethering_button_text
|
|
||||||
* @string/wifi_hotspot_checkbox_text
|
|
||||||
* @string/bluetooth_tether_checkbox_text
|
|
||||||
-->
|
|
||||||
<string name="tethering_manage_usb">USB tethering</string>
|
<string name="tethering_manage_usb">USB tethering</string>
|
||||||
<string name="tethering_manage_wifi">Wi\u2011Fi hotspot</string>
|
<string name="tethering_manage_wifi">Wi\u2011Fi hotspot</string>
|
||||||
<string name="tethering_manage_wifi_legacy">Wi\u2011Fi hotspot (legacy)</string>
|
<string name="tethering_manage_wifi_legacy">Wi\u2011Fi hotspot (legacy)</string>
|
||||||
@@ -82,4 +80,12 @@
|
|||||||
<string name="exception_interface_not_found">Fatal: Downstream interface not found</string>
|
<string name="exception_interface_not_found">Fatal: Downstream interface not found</string>
|
||||||
<string name="root_unavailable">Root unavailable</string>
|
<string name="root_unavailable">Root unavailable</string>
|
||||||
<string name="noisy_su_failure">Something went wrong, please check the debug information.</string>
|
<string name="noisy_su_failure">Something went wrong, please check the debug information.</string>
|
||||||
|
|
||||||
|
<string name="wifi_ssid">Network name</string>
|
||||||
|
<string name="wifi_ssid_hint">Enter the SSID</string>
|
||||||
|
<string name="wifi_password">Password</string>
|
||||||
|
<string name="wifi_show_password">Show password</string>
|
||||||
|
<string name="credentials_password_too_short">The password must have at least 8 characters.</string>
|
||||||
|
<string name="wifi_save">Save</string>
|
||||||
|
<string name="wifi_cancel">Cancel</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -13,4 +13,35 @@
|
|||||||
<item name="preferenceCategory_marginBottom">8dp</item>
|
<item name="preferenceCategory_marginBottom">8dp</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<!-- https://android.googlesource.com/platform/packages/apps/Settings/+/7efcc35/res/values/styles.xml -->
|
||||||
|
<style name="wifi_item">
|
||||||
|
<item name="android:layout_marginTop">8dip</item>
|
||||||
|
<item name="android:layout_marginStart">8dip</item>
|
||||||
|
<item name="android:layout_marginEnd">8dip</item>
|
||||||
|
<item name="android:paddingStart">8dip</item>
|
||||||
|
<item name="android:paddingEnd">8dip</item>
|
||||||
|
<item name="android:orientation">vertical</item>
|
||||||
|
<item name="android:gravity">start</item>
|
||||||
|
</style>
|
||||||
|
<style name="wifi_item_label">
|
||||||
|
<item name="android:paddingStart">8dip</item>
|
||||||
|
<item name="android:textSize">14sp</item>
|
||||||
|
<item name="android:textAlignment">viewStart</item>
|
||||||
|
<item name="android:textAppearance">@android:style/TextAppearance.Material.Body1</item>
|
||||||
|
<item name="android:textColor">?android:attr/textColorSecondary</item>
|
||||||
|
</style>
|
||||||
|
<style name="wifi_item_edit_content">
|
||||||
|
<item name="android:paddingStart">4dip</item>
|
||||||
|
<item name="android:layout_marginStart">4dip</item>
|
||||||
|
<item name="android:textSize">18sp</item>
|
||||||
|
</style>
|
||||||
|
<style name="wifi_section">
|
||||||
|
<item name="android:orientation">vertical</item>
|
||||||
|
</style>
|
||||||
|
<style name="wifi_item_content">
|
||||||
|
<item name="android:textAlignment">viewStart</item>
|
||||||
|
<item name="android:textAppearance">@android:style/TextAppearance.Material.Subhead</item>
|
||||||
|
<item name="android:textColor">?android:attr/textColorPrimary</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
Reference in New Issue
Block a user