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

@@ -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<String, IpNeighbour>) {
adapter.neighbours = neighbours.values.toList()
}

View File

@@ -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?) {

View File

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

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

@@ -1,4 +1,4 @@
package be.mygod.vpnhotspot.net
package be.mygod.vpnhotspot.net.wifi
import android.content.Context
import android.net.wifi.WifiConfiguration

View File

@@ -1,4 +1,4 @@
package be.mygod.vpnhotspot.net
package be.mygod.vpnhotspot.net.wifi
import android.annotation.SuppressLint
import android.net.wifi.WpsInfo

View File

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

View 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>

View 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>

View File

@@ -40,7 +40,7 @@
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/repeater_ssid"
android:text="@string/wifi_ssid"
android:textAppearance="@style/TextAppearance.AppCompat.Subhead"/>
<Space
@@ -57,31 +57,13 @@
android:focusable="false"
android:text="@{data.ssid}"
android:textIsSelectable="true"
tools:text="DIRECT-rAnd0m"/>
tools:text="DIRECT-rA-nd0m"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_column="0"
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:textAppearance="@style/TextAppearance.AppCompat.Subhead"/>
@@ -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"

View File

@@ -8,8 +8,8 @@
android:title="@string/repeater_wps"
app:showAsAction="ifRoom"/>
<item
android:id="@+id/resetGroup"
android:icon="@drawable/ic_action_autorenew"
android:id="@+id/edit"
android:icon="@drawable/ic_image_edit"
android:title="@string/repeater_reset_credentials"
app:showAsAction="ifRoom"/>
</menu>

View File

@@ -5,15 +5,15 @@
<string name="title_tethering">系统共享</string>
<string name="title_settings">设置选项</string>
<string name="repeater_ssid">中继名称</string>
<string name="repeater_password">中继密码</string>
<string name="repeater_addresses">中继地址</string>
<string name="repeater_wps_dialog_title">输入 PIN</string>
<string name="repeater_wps_dialog_pbc">一键加密</string>
<string name="repeater_wps_success_pbc">请在 2 分钟内在需要连接的设备上使用一键加密以连接到此中继。</string>
<string name="repeater_wps_success_keypad">成功注册 PIN。</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>
<string name="repeater_reset_credentials_dialog_reset">重置</string>
@@ -78,4 +78,12 @@
<string name="exception_interface_not_found">错误:未找到下游接口</string>
<string name="root_unavailable">似乎没有 root</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>

View File

@@ -1,12 +1,19 @@
<?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>
<string name="app_name">VPN Hotspot</string>
<string name="title_repeater">Repeater</string>
<string name="title_tethering">Tethering</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_wps">WPS</string>
<string name="repeater_wps_dialog_title">Enter PIN</string>
@@ -15,10 +22,9 @@
device.</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_reset_credentials">Reset credentials</string>
<string name="repeater_reset_credentials_dialog_message">Android system will generate new network name and password
next time repeater is activated. This is irreversible.</string>
<string name="repeater_reset_credentials_dialog_reset">Reset</string>
<string name="repeater_configure">Configure Wi\u2011Fi repeater</string>
<string name="repeater_configure_failure">Valid config not found. Please start repeater first.</string>
<string name="repeater_reset_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>
@@ -36,14 +42,6 @@
<string name="repeater_failure_reason_unknown">unknown #%d</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_wifi">Wi\u2011Fi hotspot</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="root_unavailable">Root unavailable</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>

View File

@@ -13,4 +13,35 @@
<item name="preferenceCategory_marginBottom">8dp</item>
</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>