From 926783cc259ce989251195ee35e5dc3bc5d2b543 Mon Sep 17 00:00:00 2001 From: Mygod Date: Thu, 4 Apr 2019 18:05:21 +0800 Subject: [PATCH 1/9] Refine code style --- mobile/src/main/java/be/mygod/vpnhotspot/ServiceNotification.kt | 2 +- mobile/src/main/res/values-zh-rCN/strings.xml | 2 +- mobile/src/main/res/values/strings.xml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/ServiceNotification.kt b/mobile/src/main/java/be/mygod/vpnhotspot/ServiceNotification.kt index 0830a3f1..8ae2650b 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/ServiceNotification.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/ServiceNotification.kt @@ -34,7 +34,7 @@ object ServiceNotification { context.resources.getQuantityString(R.plurals.notification_connected_devices, size, size, dev) } if (inactive.isNotEmpty()) { - lines += context.getString(R.string.notification_interfaces_inactive) + inactive.joinToString() + lines += context.getString(R.string.notification_interfaces_inactive, inactive.joinToString()) } return if (lines.size <= 1) builder.setContentText(lines.singleOrNull()).build() else { val deviceCount = deviceCounts.sumBy { it.value } diff --git a/mobile/src/main/res/values-zh-rCN/strings.xml b/mobile/src/main/res/values-zh-rCN/strings.xml index 615d8312..c5c46b1c 100644 --- a/mobile/src/main/res/values-zh-rCN/strings.xml +++ b/mobile/src/main/res/values-zh-rCN/strings.xml @@ -125,7 +125,7 @@ %d 个接口 - 不活跃: + 不活跃:%s 未知 #%d 错误:未找到下游接口 diff --git a/mobile/src/main/res/values/strings.xml b/mobile/src/main/res/values/strings.xml index 70098cbb..ee7c4a8a 100644 --- a/mobile/src/main/res/values/strings.xml +++ b/mobile/src/main/res/values/strings.xml @@ -134,7 +134,7 @@ %d interface %d interfaces - "Inactive: " + Inactive: %s unknown #%d Fatal: Downstream interface not found From 834498b1ff8186d6c040d0961846bdbec0718743 Mon Sep 17 00:00:00 2001 From: Mygod Date: Thu, 4 Apr 2019 13:08:00 +0800 Subject: [PATCH 2/9] Deprecate reset credentials Due to persistent groups no longer useful in Android Q, this feature will be removed. Users on older releases can remove remembered groups from Wi-Fi Direct settings manually. --- .../be/mygod/vpnhotspot/RepeaterService.kt | 12 ----------- .../vpnhotspot/manage/RepeaterManager.kt | 21 ++++++++----------- .../vpnhotspot/manage/TetheringFragment.kt | 2 +- .../net/wifi/WifiP2pDialogFragment.kt | 1 - mobile/src/main/res/values-zh-rCN/strings.xml | 3 --- mobile/src/main/res/values/strings.xml | 3 --- 6 files changed, 10 insertions(+), 32 deletions(-) diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/RepeaterService.kt b/mobile/src/main/java/be/mygod/vpnhotspot/RepeaterService.kt index 69fb257d..ec88da98 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/RepeaterService.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/RepeaterService.kt @@ -95,18 +95,6 @@ class RepeaterService : Service(), WifiP2pManager.ChannelListener, SharedPrefere fun shutdown() { if (active) removeGroup() } - - fun resetCredentials() { - val channel = channel - if (channel == null) SmartSnackbar.make(R.string.repeater_failure_disconnected).show() - else p2pManager.deletePersistentGroup(channel, (group ?: return).netId, - object : WifiP2pManager.ActionListener { - override fun onSuccess() = SmartSnackbar.make(R.string.repeater_reset_credentials_success) - .shortToast().show() - override fun onFailure(reason: Int) = SmartSnackbar.make( - formatReason(R.string.repeater_reset_credentials_failure, reason)).show() - }) - } } private val p2pManager get() = RepeaterService.p2pManager!! diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/manage/RepeaterManager.kt b/mobile/src/main/java/be/mygod/vpnhotspot/manage/RepeaterManager.kt index 00050db7..c6313cfb 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/manage/RepeaterManager.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/manage/RepeaterManager.kt @@ -168,18 +168,15 @@ class RepeaterManager(private val parent: TetheringFragment) : Manager(), Servic } } - fun onEditResult(which: Int, data: Intent?) { - when (which) { - DialogInterface.BUTTON_POSITIVE -> try { - val master = holder.config ?: return - val config = AlertDialogFragment.getRet(data!!).configuration - master.update(config.SSID, config.preSharedKey) - binder!!.group = null - } catch (e: Exception) { - Timber.w(e) - SmartSnackbar.make(e).show() - } - DialogInterface.BUTTON_NEUTRAL -> binder!!.resetCredentials() + fun onEditResult(data: Intent?) { + val master = holder.config ?: return + try { + val config = AlertDialogFragment.getRet(data!!).configuration + master.update(config.SSID, config.preSharedKey) + binder!!.group = null + } catch (e: Exception) { + Timber.w(e) + SmartSnackbar.make(e).show() } holder.config = null } diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetheringFragment.kt b/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetheringFragment.kt index a4911964..8fd43896 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetheringFragment.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetheringFragment.kt @@ -133,7 +133,7 @@ class TetheringFragment : Fragment(), ServiceConnection, MenuItem.OnMenuItemClic override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) = when (requestCode) { REPEATER_WPS -> adapter.repeaterManager.onWpsResult(resultCode, data) - REPEATER_EDIT_CONFIGURATION -> adapter.repeaterManager.onEditResult(resultCode, data) + REPEATER_EDIT_CONFIGURATION -> adapter.repeaterManager.onEditResult(data) else -> super.onActivityResult(requestCode, resultCode, data) } diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiP2pDialogFragment.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiP2pDialogFragment.kt index f430baf3..2c447ede 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiP2pDialogFragment.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiP2pDialogFragment.kt @@ -48,7 +48,6 @@ class WifiP2pDialogFragment : AlertDialogFragment打开 WPS 失败(原因:%s) 设置 WLAN 中继 未能找到有效的档案。请尝试先打开中继。 - 重置 - 凭据已重置。 - 重置凭据失败(原因:%s) 删除多余 P2P 群组失败(原因:%s) Wi\u2011Fi 直连不可用,请打开 Wi\u2011Fi diff --git a/mobile/src/main/res/values/strings.xml b/mobile/src/main/res/values/strings.xml index ee7c4a8a..213f4fe6 100644 --- a/mobile/src/main/res/values/strings.xml +++ b/mobile/src/main/res/values/strings.xml @@ -24,9 +24,6 @@ Failed to start WPS (reason: %s) Configure Wi\u2011Fi repeater Valid config not found. Please start repeater first. - Reset - Credentials reset. - Failed to reset credentials (reason: %s) Failed to remove redundant P2P group (reason: %s) Wi\u2011Fi direct unavailable, please enable Wi\u2011Fi From 1145b0f23b1e777f3eef0d5b33215e2aff2aaf48 Mon Sep 17 00:00:00 2001 From: Mygod Date: Thu, 4 Apr 2019 16:43:48 +0800 Subject: [PATCH 3/9] Support editing native Wi-Fi AP configurations Support for repeater channel on Android 5 has been dropped because I am lazy. --- README.md | 4 +- mobile/src/main/AndroidManifest.xml | 2 + .../be/mygod/vpnhotspot/RepeaterService.kt | 10 +- .../manage/LocalOnlyHotspotManager.kt | 17 +- .../vpnhotspot/manage/RepeaterManager.kt | 66 ++++---- .../vpnhotspot/manage/TetheringFragment.kt | 93 ++++++++--- .../vpnhotspot/net/wifi/WifiApManager.kt | 27 +++- .../net/wifi/WifiP2pDialogFragment.kt | 74 --------- .../P2pSupplicantConfiguration.kt | 2 +- .../configuration/WifiApDialogFragment.kt | 149 ++++++++++++++++++ .../wifi/configuration/WifiConfiguration.kt | 84 ++++++++++ .../be/mygod/vpnhotspot/room/Converters.kt | 26 +-- .../java/be/mygod/vpnhotspot/util/Utils.kt | 12 +- .../src/main/res/drawable/ic_content_wave.xml | 11 -- mobile/src/main/res/layout/dialog_wifi_ap.xml | 42 ++++- .../src/main/res/layout/listitem_repeater.xml | 93 ----------- .../src/main/res/menu/toolbar_tethering.xml | 14 ++ mobile/src/main/res/values-ru/strings.xml | 18 ++- mobile/src/main/res/values-zh-rCN/strings.xml | 13 +- mobile/src/main/res/values/strings.xml | 14 +- mobile/src/main/res/values/styles.xml | 12 ++ 21 files changed, 488 insertions(+), 295 deletions(-) delete mode 100644 mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiP2pDialogFragment.kt rename mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/{ => configuration}/P2pSupplicantConfiguration.kt (99%) create mode 100644 mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/configuration/WifiApDialogFragment.kt create mode 100644 mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/configuration/WifiConfiguration.kt delete mode 100644 mobile/src/main/res/drawable/ic_content_wave.xml diff --git a/README.md b/README.md index a715049c..d5c49fcb 100644 --- a/README.md +++ b/README.md @@ -116,6 +116,9 @@ Undocumented API list: * (since API 24) [`Landroid/net/ConnectivityManager;->getLastTetherError(Ljava/lang/String;)I,greylist`](https://android.googlesource.com/platform/prebuilts/runtime/+/aa21a6e/appcompat/hiddenapi-flags.csv#112882) * (since API 24) [`Landroid/net/ConnectivityManager;->startTethering(IZLandroid/net/ConnectivityManager$OnStartTetheringCallback;Landroid/os/Handler;)V,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/aa21a6e/appcompat/hiddenapi-flags.csv#112972) * (since API 24) [`Landroid/net/ConnectivityManager;->stopTethering(I)V,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/aa21a6e/appcompat/hiddenapi-flags.csv#112974) +* [`Landroid/net/wifi/WifiManager;->getWifiApConfiguration()Landroid/net/wifi/WifiConfiguration;,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/aa21a6e/appcompat/hiddenapi-flags.csv#121357) +* [`Landroid/net/wifi/WifiManager;->setWifiApConfiguration(Landroid/net/wifi/WifiConfiguration;)Z,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/aa21a6e/appcompat/hiddenapi-flags.csv#121416) +* (deprecated since API 26) `Landroid/net/wifi/WifiManager;->setWifiApEnabled(Landroid/net/wifi/WifiConfiguration;Z)Z` * [`Landroid/net/wifi/p2p/WifiP2pGroup;->getNetworkId()I,greylist`](https://android.googlesource.com/platform/prebuilts/runtime/+/aa21a6e/appcompat/hiddenapi-flags.csv#123194) * [`Landroid/net/wifi/p2p/WifiP2pGroupList;->getGroupList()Ljava/util/Collection;,greylist`](https://android.googlesource.com/platform/prebuilts/runtime/+/aa21a6e/appcompat/hiddenapi-flags.csv#123239) * [`Landroid/net/wifi/p2p/WifiP2pManager;->deletePersistentGroup(Landroid/net/wifi/p2p/WifiP2pManager$Channel;ILandroid/net/wifi/p2p/WifiP2pManager$ActionListener;)V,greylist`](https://android.googlesource.com/platform/prebuilts/runtime/+/aa21a6e/appcompat/hiddenapi-flags.csv#123431) @@ -123,7 +126,6 @@ Undocumented API list: * [`Landroid/net/wifi/p2p/WifiP2pManager;->setWifiP2pChannels(Landroid/net/wifi/p2p/WifiP2pManager$Channel;IILandroid/net/wifi/p2p/WifiP2pManager$ActionListener;)V,greylist`](https://android.googlesource.com/platform/prebuilts/runtime/+/aa21a6e/appcompat/hiddenapi-flags.csv#123458) * [`Landroid/net/wifi/p2p/WifiP2pManager;->startWps(Landroid/net/wifi/p2p/WifiP2pManager$Channel;Landroid/net/wifi/WpsInfo;Landroid/net/wifi/p2p/WifiP2pManager$ActionListener;)V,greylist`](https://android.googlesource.com/platform/prebuilts/runtime/+/aa21a6e/appcompat/hiddenapi-flags.csv#123459) * [`Ljava/net/InetAddress;->parseNumericAddress(Ljava/lang/String;)Ljava/net/InetAddress;,greylist`](https://android.googlesource.com/platform/prebuilts/runtime/+/aa21a6e/appcompat/hiddenapi-flags.csv#299587) -* (deprecated since API 26) `Landroid/net/wifi/WifiManager;->setWifiApEnabled(Landroid/net/wifi/WifiConfiguration;Z)Z` Undocumented system configurations: diff --git a/mobile/src/main/AndroidManifest.xml b/mobile/src/main/AndroidManifest.xml index a1ef8945..1c0274b0 100644 --- a/mobile/src/main/AndroidManifest.xml +++ b/mobile/src/main/AndroidManifest.xml @@ -32,6 +32,8 @@ + diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/RepeaterService.kt b/mobile/src/main/java/be/mygod/vpnhotspot/RepeaterService.kt index ec88da98..4aab6beb 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/RepeaterService.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/RepeaterService.kt @@ -52,10 +52,12 @@ class RepeaterService : Service(), WifiP2pManager.ChannelListener, SharedPrefere val supported get() = p2pManager != null var persistentSupported = false - val operatingChannel: Int get() { - val result = app.pref.getString(KEY_OPERATING_CHANNEL, null)?.toIntOrNull() ?: 0 - return if (result in 1..165) result else 0 - } + var operatingChannel: Int + get() { + val result = app.pref.getString(KEY_OPERATING_CHANNEL, null)?.toIntOrNull() ?: 0 + return if (result in 1..165) result else 0 + } + set(value) = app.pref.edit().putString(RepeaterService.KEY_OPERATING_CHANNEL, value.toString()).apply() } enum class Status { diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/manage/LocalOnlyHotspotManager.kt b/mobile/src/main/java/be/mygod/vpnhotspot/manage/LocalOnlyHotspotManager.kt index 219a8680..456540cf 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/manage/LocalOnlyHotspotManager.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/manage/LocalOnlyHotspotManager.kt @@ -7,14 +7,10 @@ import android.content.ComponentName import android.content.Intent import android.content.ServiceConnection import android.content.pm.PackageManager -import android.graphics.Typeface import android.location.LocationManager import android.os.Build import android.os.IBinder import android.provider.Settings -import android.text.SpannableStringBuilder -import android.text.Spanned -import android.text.style.TypefaceSpan import android.view.View import android.widget.Toast import androidx.core.content.getSystemService @@ -24,7 +20,6 @@ import be.mygod.vpnhotspot.DebugHelper import be.mygod.vpnhotspot.LocalOnlyHotspotService import be.mygod.vpnhotspot.R import be.mygod.vpnhotspot.databinding.ListitemInterfaceBinding -import be.mygod.vpnhotspot.net.TetherType import be.mygod.vpnhotspot.util.ServiceForegroundConnector import be.mygod.vpnhotspot.util.formatAddresses import be.mygod.vpnhotspot.widget.SmartSnackbar @@ -76,15 +71,7 @@ class LocalOnlyHotspotManager(private val parent: TetheringFragment) : Manager() private val lookup: Map get() = parent.ifaceLookup override val icon get() = R.drawable.ic_action_perm_scan_wifi - override val title: CharSequence get() { - val configuration = binder?.configuration ?: return parent.getString(R.string.tethering_temp_hotspot) - return SpannableStringBuilder("${configuration.SSID} - ").apply { - val start = length - append(configuration.preSharedKey) - setSpan(if (Build.VERSION.SDK_INT >= 28) TypefaceSpan(Typeface.MONOSPACE) else - TypefaceSpan("monospace"), start, length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) - } - } + override val title: CharSequence get() = parent.getString(R.string.tethering_temp_hotspot) override val text: CharSequence get() { return lookup[binder?.iface ?: return ""]?.formatAddresses() ?: "" } @@ -99,7 +86,7 @@ class LocalOnlyHotspotManager(private val parent: TetheringFragment) : Manager() override val type get() = VIEW_TYPE_LOCAL_ONLY_HOTSPOT private val data = Data() - private var binder: LocalOnlyHotspotService.Binder? = null + internal var binder: LocalOnlyHotspotService.Binder? = null override fun bindTo(viewHolder: RecyclerView.ViewHolder) { viewHolder as ViewHolder diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/manage/RepeaterManager.kt b/mobile/src/main/java/be/mygod/vpnhotspot/manage/RepeaterManager.kt index c6313cfb..db0be8c3 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/manage/RepeaterManager.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/manage/RepeaterManager.kt @@ -6,6 +6,7 @@ import android.content.Intent import android.content.ServiceConnection import android.net.wifi.WifiConfiguration import android.net.wifi.p2p.WifiP2pGroup +import android.os.Build import android.os.Bundle import android.os.IBinder import android.os.Parcelable @@ -21,10 +22,8 @@ import androidx.lifecycle.ViewModelProviders import androidx.lifecycle.get import androidx.recyclerview.widget.RecyclerView import be.mygod.vpnhotspot.* -import be.mygod.vpnhotspot.App.Companion.app import be.mygod.vpnhotspot.databinding.ListitemRepeaterBinding -import be.mygod.vpnhotspot.net.wifi.P2pSupplicantConfiguration -import be.mygod.vpnhotspot.net.wifi.WifiP2pDialogFragment +import be.mygod.vpnhotspot.net.wifi.configuration.* import be.mygod.vpnhotspot.util.ServiceForegroundConnector import be.mygod.vpnhotspot.util.formatAddresses import be.mygod.vpnhotspot.widget.SmartSnackbar @@ -51,7 +50,6 @@ class RepeaterManager(private val parent: TetheringFragment) : Manager(), Servic else -> false } - val ssid @Bindable get() = binder?.group?.networkName ?: "" val addresses: CharSequence @Bindable get() { return try { NetworkInterface.getByName(p2pInterface ?: return "")?.formatAddresses() ?: "" @@ -59,12 +57,6 @@ class RepeaterManager(private val parent: TetheringFragment) : Manager(), Servic "" } } - var oc: CharSequence - @Bindable get() { - val oc = RepeaterService.operatingChannel - return if (oc in 1..165) oc.toString() else "" - } - set(value) = app.pref.edit().putString(RepeaterService.KEY_OPERATING_CHANNEL, value.toString()).apply() fun onStatusChanged() { notifyPropertyChanged(BR.switchEnabled) @@ -72,7 +64,6 @@ class RepeaterManager(private val parent: TetheringFragment) : Manager(), Servic notifyPropertyChanged(BR.addresses) } fun onGroupChanged(group: WifiP2pGroup? = null) { - notifyPropertyChanged(BR.ssid) p2pInterface = group?.`interface` notifyPropertyChanged(BR.addresses) } @@ -92,22 +83,6 @@ class RepeaterManager(private val parent: TetheringFragment) : Manager(), Servic fun wps() { if (binder?.active == true) WpsDialogFragment().show(parent, TetheringFragment.REPEATER_WPS) } - - fun editConfigurations() { - val group = binder?.group - if (group != null) try { - val config = P2pSupplicantConfiguration(group, binder?.thisDevice?.deviceAddress) - holder.config = config - WifiP2pDialogFragment().withArg(WifiP2pDialogFragment.Arg(WifiConfiguration().apply { - SSID = group.networkName - preSharedKey = config.psk - })).show(parent, TetheringFragment.REPEATER_EDIT_CONFIGURATION) - return - } catch (e: RuntimeException) { - Timber.w(e) - } - SmartSnackbar.make(R.string.repeater_configure_failure).show() - } } @Parcelize @@ -168,16 +143,35 @@ class RepeaterManager(private val parent: TetheringFragment) : Manager(), Servic } } - fun onEditResult(data: Intent?) { - val master = holder.config ?: return - try { - val config = AlertDialogFragment.getRet(data!!).configuration - master.update(config.SSID, config.preSharedKey) - binder!!.group = null - } catch (e: Exception) { + val configuration: WifiConfiguration? get() { + val group = binder?.group + if (group != null) try { + val config = P2pSupplicantConfiguration(group, binder?.thisDevice?.deviceAddress) + holder.config = config + return newWifiApConfiguration(group.networkName, config.psk).apply { + allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK) // is not actually used + if (Build.VERSION.SDK_INT >= 23) { + apBand = AP_BAND_ANY + apChannel = RepeaterService.operatingChannel + } + } + } catch (e: RuntimeException) { Timber.w(e) - SmartSnackbar.make(e).show() } - holder.config = null + SmartSnackbar.make(R.string.repeater_configure_failure).show() + return null + } + fun updateConfiguration(config: WifiConfiguration) { + holder.config?.let { master -> + if (binder?.group?.networkName != config.SSID || master.psk != config.preSharedKey) try { + master.update(config.SSID, config.preSharedKey) + binder!!.group = null + } catch (e: Exception) { + Timber.w(e) + SmartSnackbar.make(e).show() + } + holder.config = null + } + RepeaterService.operatingChannel = config.apChannel } } diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetheringFragment.kt b/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetheringFragment.kt index 8fd43896..48d71e58 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetheringFragment.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetheringFragment.kt @@ -1,15 +1,16 @@ package be.mygod.vpnhotspot.manage import android.annotation.TargetApi -import android.content.ComponentName -import android.content.Intent -import android.content.IntentFilter -import android.content.ServiceConnection +import android.content.* import android.content.pm.PackageManager import android.os.Build import android.os.Bundle import android.os.IBinder -import android.view.* +import android.view.LayoutInflater +import android.view.MenuItem +import android.view.View +import android.view.ViewGroup +import androidx.appcompat.widget.Toolbar import androidx.core.content.ContextCompat import androidx.databinding.DataBindingUtil import androidx.fragment.app.Fragment @@ -23,24 +24,30 @@ import be.mygod.vpnhotspot.net.TetherType import be.mygod.vpnhotspot.net.TetheringManager import be.mygod.vpnhotspot.net.TetheringManager.localOnlyTetheredIfaces import be.mygod.vpnhotspot.net.TetheringManager.tetheredIfaces +import be.mygod.vpnhotspot.net.wifi.WifiApManager +import be.mygod.vpnhotspot.net.wifi.configuration.WifiApDialogFragment import be.mygod.vpnhotspot.util.ServiceForegroundConnector import be.mygod.vpnhotspot.util.broadcastReceiver import be.mygod.vpnhotspot.util.isNotGone +import be.mygod.vpnhotspot.widget.SmartSnackbar import kotlinx.android.synthetic.main.activity_main.* import timber.log.Timber +import java.lang.IllegalArgumentException +import java.lang.reflect.InvocationTargetException import java.net.NetworkInterface import java.net.SocketException -class TetheringFragment : Fragment(), ServiceConnection, MenuItem.OnMenuItemClickListener { +class TetheringFragment : Fragment(), ServiceConnection, Toolbar.OnMenuItemClickListener { companion object { const val START_LOCAL_ONLY_HOTSPOT = 1 - const val REPEATER_EDIT_CONFIGURATION = 2 const val REPEATER_WPS = 3 + const val CONFIGURE_REPEATER = 2 + const val CONFIGURE_AP = 4 } inner class ManagerAdapter : ListAdapter(Manager) { internal val repeaterManager by lazy { RepeaterManager(this@TetheringFragment) } - private val localOnlyHotspotManager by lazy @TargetApi(26) { LocalOnlyHotspotManager(this@TetheringFragment) } + internal val localOnlyHotspotManager by lazy @TargetApi(26) { LocalOnlyHotspotManager(this@TetheringFragment) } private val tetherManagers by lazy @TargetApi(24) { listOf(TetherManager.Wifi(this@TetheringFragment), TetherManager.Usb(this@TetheringFragment), @@ -100,13 +107,47 @@ class TetheringFragment : Fragment(), ServiceConnection, MenuItem.OnMenuItemClic item.isNotGone = canMonitor.isNotEmpty() item.subMenu.apply { clear() - canMonitor.sorted().forEach { add(it).setOnMenuItemClickListener(this@TetheringFragment) } + for (iface in canMonitor.sorted()) add(iface).setOnMenuItemClickListener { + ContextCompat.startForegroundService(requireContext(), Intent(context, TetheringService::class.java) + .putExtra(TetheringService.EXTRA_ADD_INTERFACE_MONITOR, iface)) + true + } } } override fun onMenuItemClick(item: MenuItem?): Boolean { - ContextCompat.startForegroundService(requireContext(), Intent(context, TetheringService::class.java) - .putExtra(TetheringService.EXTRA_ADD_INTERFACE_MONITOR, item?.title ?: return false)) - return true + return when (item?.itemId) { + R.id.configuration -> item.subMenu.run { + findItem(R.id.configuration_repeater).isNotGone = RepeaterService.supported + findItem(R.id.configuration_temp_hotspot).isNotGone = + adapter.localOnlyHotspotManager.binder?.configuration != null + true + } + R.id.configuration_repeater -> { + WifiApDialogFragment().withArg(WifiApDialogFragment.Arg( + adapter.repeaterManager.configuration ?: return false, + p2pMode = true + )).show(this, CONFIGURE_REPEATER) + true + } + R.id.configuration_temp_hotspot -> { + WifiApDialogFragment().withArg(WifiApDialogFragment.Arg( + adapter.localOnlyHotspotManager.binder?.configuration ?: return false, + readOnly = true + )).show(this, 0) // read-only, no callback needed + true + } + R.id.configuration_ap -> try { + WifiApDialogFragment().withArg(WifiApDialogFragment.Arg( + WifiApManager.configuration + )).show(this, CONFIGURE_AP) + true + } catch (e: InvocationTargetException) { + if (e.targetException !is SecurityException) Timber.w(e) + SmartSnackbar.make(e.targetException).show() + false + } + else -> false + } } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { @@ -117,13 +158,19 @@ class TetheringFragment : Fragment(), ServiceConnection, MenuItem.OnMenuItemClic binding.interfaces.adapter = adapter adapter.update(emptyList(), emptyList(), emptyList()) ServiceForegroundConnector(this, this, TetheringService::class) - requireActivity().toolbar.inflateMenu(R.menu.toolbar_tethering) + requireActivity().toolbar.apply { + inflateMenu(R.menu.toolbar_tethering) + setOnMenuItemClickListener(this@TetheringFragment) + } return binding.root } override fun onDestroyView() { super.onDestroyView() - requireActivity().toolbar.menu.clear() + requireActivity().toolbar.apply { + menu.clear() + setOnMenuItemClickListener(null) + } } override fun onResume() { @@ -131,10 +178,20 @@ class TetheringFragment : Fragment(), ServiceConnection, MenuItem.OnMenuItemClic if (Build.VERSION.SDK_INT >= 27) ManageBar.Data.notifyChange() } - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) = when (requestCode) { - REPEATER_WPS -> adapter.repeaterManager.onWpsResult(resultCode, data) - REPEATER_EDIT_CONFIGURATION -> adapter.repeaterManager.onEditResult(data) - else -> super.onActivityResult(requestCode, resultCode, data) + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + val configuration by lazy { AlertDialogFragment.getRet(data!!).configuration } + when (requestCode) { + REPEATER_WPS -> adapter.repeaterManager.onWpsResult(resultCode, data) + CONFIGURE_REPEATER -> if (resultCode == DialogInterface.BUTTON_POSITIVE) { + adapter.repeaterManager.updateConfiguration(configuration) + } + CONFIGURE_AP -> if (resultCode == DialogInterface.BUTTON_POSITIVE) try { + WifiApManager.configuration = configuration + } catch (e: IllegalArgumentException) { + SmartSnackbar.make(R.string.configuration_rejected).show() + } + else -> super.onActivityResult(requestCode, resultCode, data) + } } override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiApManager.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiApManager.kt index 653d3f78..b9dd7200 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiApManager.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiApManager.kt @@ -3,15 +3,23 @@ package be.mygod.vpnhotspot.net.wifi import android.net.wifi.WifiConfiguration import android.net.wifi.WifiManager import be.mygod.vpnhotspot.App.Companion.app +import java.lang.IllegalArgumentException -/** - * Although the functionalities were removed in API 26, it is already not functioning correctly on API 25. - * - * See also: https://android.googlesource.com/platform/frameworks/base/+/5c0b10a4a9eecc5307bb89a271221f2b20448797%5E%21/ - */ object WifiApManager { - private val setWifiApEnabled = WifiManager::class.java.getDeclaredMethod("setWifiApEnabled", - WifiConfiguration::class.java, Boolean::class.java) + private val getWifiApConfiguration by lazy { WifiManager::class.java.getDeclaredMethod("getWifiApConfiguration") } + private val setWifiApConfiguration by lazy { + WifiManager::class.java.getDeclaredMethod("setWifiApConfiguration", WifiConfiguration::class.java) + } + var configuration: WifiConfiguration + get() = getWifiApConfiguration.invoke(app.wifi) as WifiConfiguration + set(value) { + if (setWifiApConfiguration.invoke(app.wifi, value) as? Boolean != true) throw IllegalArgumentException() + } + + private val setWifiApEnabled by lazy { + 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 @@ -25,6 +33,11 @@ object WifiApManager { private fun WifiManager.setWifiApEnabled(wifiConfig: WifiConfiguration?, enabled: Boolean) = setWifiApEnabled.invoke(this, wifiConfig, enabled) as Boolean + /** + * Although the functionalities were removed in API 26, it is already not functioning correctly on API 25. + * + * See also: https://android.googlesource.com/platform/frameworks/base/+/5c0b10a4a9eecc5307bb89a271221f2b20448797%5E%21/ + */ @Deprecated("Not usable since API 26, malfunctioning on API 25") fun start(wifiConfig: WifiConfiguration? = null) { app.wifi.isWifiEnabled = false diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiP2pDialogFragment.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiP2pDialogFragment.kt deleted file mode 100644 index 2c447ede..00000000 --- a/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiP2pDialogFragment.kt +++ /dev/null @@ -1,74 +0,0 @@ -package be.mygod.vpnhotspot.net.wifi - -import android.content.DialogInterface -import android.net.wifi.WifiConfiguration -import android.net.wifi.WifiConfiguration.AuthAlgorithm -import android.os.Parcelable -import android.text.Editable -import android.text.TextWatcher -import android.view.View -import android.widget.EditText -import android.widget.TextView -import androidx.appcompat.app.AlertDialog -import be.mygod.vpnhotspot.AlertDialogFragment -import be.mygod.vpnhotspot.R -import kotlinx.android.parcel.Parcelize -import kotlinx.android.synthetic.main.dialog_wifi_ap.view.* -import java.nio.charset.Charset - -/** - * Based on: https://android.googlesource.com/platform/packages/apps/Settings/+/39b4674/src/com/android/settings/wifi/WifiApDialog.java - * - * This dialog has been deprecated in API 28, but we are still using it since it works better for our purposes. - * Related: https://android.googlesource.com/platform/packages/apps/Settings/+/defb1183ecb00d6231bac7d934d07f58f90261ea - */ -class WifiP2pDialogFragment : AlertDialogFragment(), TextWatcher { - @Parcelize - data class Arg(val configuration: WifiConfiguration) : Parcelable - - private lateinit var mView: View - private lateinit var mSsid: TextView - private lateinit var mPassword: EditText - override val ret: Arg? 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 Arg(config) - } - - override fun AlertDialog.Builder.prepare(listener: DialogInterface.OnClickListener) { - mView = requireActivity().layoutInflater.inflate(R.layout.dialog_wifi_ap, null) - setView(mView) - setTitle(R.string.repeater_configure) - mSsid = mView.ssid - mPassword = mView.password - setPositiveButton(context.getString(R.string.wifi_save), listener) - setNegativeButton(context.getString(R.string.wifi_cancel), null) - mSsid.text = arg.configuration.SSID - mSsid.addTextChangedListener(this@WifiP2pDialogFragment) - mPassword.setText(arg.configuration.preSharedKey) - mPassword.addTextChangedListener(this@WifiP2pDialogFragment) - } - - override fun onStart() { - super.onStart() - validate() - } - - private fun validate() { - val mSsidString = mSsid.text.toString() - val ssidValid = mSsid.length() != 0 && Charset.forName("UTF-8").encode(mSsidString).limit() <= 32 - val passwordValid = mPassword.length() >= 8 - mView.password_wrapper.error = - if (passwordValid) null else requireContext().getString(R.string.credentials_password_too_short) - (dialog as AlertDialog).getButton(DialogInterface.BUTTON_POSITIVE).isEnabled = ssidValid && passwordValid - } - - 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/wifi/P2pSupplicantConfiguration.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/configuration/P2pSupplicantConfiguration.kt similarity index 99% rename from mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/P2pSupplicantConfiguration.kt rename to mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/configuration/P2pSupplicantConfiguration.kt index 06d9b626..396086b4 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/P2pSupplicantConfiguration.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/configuration/P2pSupplicantConfiguration.kt @@ -1,4 +1,4 @@ -package be.mygod.vpnhotspot.net.wifi +package be.mygod.vpnhotspot.net.wifi.configuration import android.net.wifi.p2p.WifiP2pGroup import android.os.Build diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/configuration/WifiApDialogFragment.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/configuration/WifiApDialogFragment.kt new file mode 100644 index 00000000..b77eee0c --- /dev/null +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/configuration/WifiApDialogFragment.kt @@ -0,0 +1,149 @@ +package be.mygod.vpnhotspot.net.wifi.configuration + +import android.annotation.TargetApi +import android.content.DialogInterface +import android.net.wifi.WifiConfiguration +import android.net.wifi.WifiConfiguration.AuthAlgorithm +import android.os.Build +import android.os.Parcelable +import android.text.Editable +import android.text.TextWatcher +import android.view.View +import android.widget.AdapterView +import android.widget.ArrayAdapter +import androidx.appcompat.app.AlertDialog +import androidx.core.view.isGone +import be.mygod.vpnhotspot.AlertDialogFragment +import be.mygod.vpnhotspot.App.Companion.app +import be.mygod.vpnhotspot.R +import kotlinx.android.parcel.Parcelize +import kotlinx.android.synthetic.main.dialog_wifi_ap.view.* +import java.lang.IllegalStateException +import java.nio.charset.Charset + +/** + * Based on: https://android.googlesource.com/platform/packages/apps/Settings/+/39b4674/src/com/android/settings/wifi/WifiApDialog.java + * + * This dialog has been deprecated in API 28, but we are still using it since it works better for our purposes. + * Related: https://android.googlesource.com/platform/packages/apps/Settings/+/defb1183ecb00d6231bac7d934d07f58f90261ea + */ +class WifiApDialogFragment : AlertDialogFragment(), TextWatcher { + @Parcelize + data class Arg(val configuration: WifiConfiguration, + val readOnly: Boolean = false, + /** + * KeyMgmt is enforced to WPA_PSK. + * Various values for apBand are allowed according to different rules. + */ + val p2pMode: Boolean = false) : Parcelable + + @TargetApi(23) + private sealed class BandOption { + open val apBand get() = AP_BAND_2GHZ + open val apChannel get() = 0 + + object BandAny : BandOption() { + override val apBand get() = AP_BAND_ANY + override fun toString() = app.getString(R.string.wifi_ap_choose_auto) + } + object Band2GHz : BandOption() { + override fun toString() = app.getString(R.string.wifi_ap_choose_2G) + } + object Band5GHz : BandOption() { + override val apBand get() = AP_BAND_5GHZ + override fun toString() = app.getString(R.string.wifi_ap_choose_5G) + } + class Channel(override val apChannel: Int) : BandOption() { + override fun toString() = "${channelToFrequency(apChannel)} MHz ($apChannel)" + } + } + + private lateinit var dialogView: View + override val ret: Arg? get() { + return Arg(WifiConfiguration().apply { + SSID = dialogView.ssid.text.toString() + allowedKeyManagement.set( + if (arg.p2pMode) WifiConfiguration.KeyMgmt.WPA_PSK else dialogView.security.selectedItemPosition) + allowedAuthAlgorithms.set(AuthAlgorithm.OPEN) + if (dialogView.password.length() != 0) preSharedKey = dialogView.password.text.toString() + if (Build.VERSION.SDK_INT >= 23) { + val bandOption = dialogView.band.selectedItem as BandOption + apBand = bandOption.apBand + apChannel = bandOption.apChannel + } + }) + } + + override fun AlertDialog.Builder.prepare(listener: DialogInterface.OnClickListener) { + val activity = requireActivity() + dialogView = activity.layoutInflater.inflate(R.layout.dialog_wifi_ap, null) + setView(dialogView) + setTitle(R.string.configuration_view) + if (!arg.readOnly) setPositiveButton(R.string.wifi_save, listener) + setNegativeButton(R.string.donations__button_close, null) + dialogView.ssid.setText(arg.configuration.SSID) + if (!arg.readOnly) dialogView.ssid.addTextChangedListener(this@WifiApDialogFragment) + if (arg.p2pMode) dialogView.security_wrapper.isGone = true else dialogView.security.apply { + adapter = ArrayAdapter(activity, android.R.layout.simple_spinner_item, 0, + WifiConfiguration.KeyMgmt.strings).apply { + setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) + } + onItemSelectedListener = object : AdapterView.OnItemSelectedListener { + override fun onNothingSelected(parent: AdapterView<*>?) = + throw IllegalStateException("Must select something") + override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { + dialogView.password_wrapper.isGone = position == WifiConfiguration.KeyMgmt.NONE + } + } + val selected = arg.configuration.allowedKeyManagement.nextSetBit(0) + check(selected >= 0) { "No key management selected" } + check(arg.configuration.allowedKeyManagement.nextSetBit(selected + 1) < 0) { + "More than 1 key managements supplied" + } + setSelection(selected) + } + dialogView.password.setText(arg.configuration.preSharedKey) + if (!arg.readOnly) dialogView.password.addTextChangedListener(this@WifiApDialogFragment) + if (Build.VERSION.SDK_INT >= 23) dialogView.band.apply { + val options = mutableListOf().apply { + if (arg.p2pMode) add(BandOption.BandAny) else { + if (Build.VERSION.SDK_INT >= 28) add(BandOption.BandAny) + add(BandOption.Band2GHz) + add(BandOption.Band5GHz) + } + addAll((1..165).map { BandOption.Channel(it) }) + } + adapter = ArrayAdapter(activity, android.R.layout.simple_spinner_item, 0, options).apply { + setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) + } + setSelection(if (arg.configuration.apChannel in 1..165) { + options.indexOfFirst { it.apChannel == arg.configuration.apChannel } + } else options.indexOfFirst { it.apBand == arg.configuration.apBand }) + } else dialogView.band_wrapper.isGone = true + } + + override fun onResume() { + super.onResume() + if (!arg.readOnly) validate() + } + + /** + * This function is reached only if not arg.readOnly. + */ + private fun validate() { + val ssidValid = dialogView.ssid.length() != 0 && + Charset.forName("UTF-8").encode(dialogView.ssid.text.toString()).limit() <= 32 + val passwordValid = when (dialogView.security.selectedItemPosition) { + WifiConfiguration.KeyMgmt.WPA_PSK, WPA2_PSK -> dialogView.password.length() >= 8 + else -> true // do not try to validate + } + dialogView.password_wrapper.error = if (passwordValid) null else { + requireContext().getString(R.string.credentials_password_too_short) + } + (dialog as AlertDialog).getButton(DialogInterface.BUTTON_POSITIVE).isEnabled = ssidValid && passwordValid + } + + 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/wifi/configuration/WifiConfiguration.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/configuration/WifiConfiguration.kt new file mode 100644 index 00000000..d7e361ec --- /dev/null +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/configuration/WifiConfiguration.kt @@ -0,0 +1,84 @@ +package be.mygod.vpnhotspot.net.wifi.configuration + +import android.net.wifi.WifiConfiguration +import androidx.annotation.RequiresApi +import be.mygod.vpnhotspot.net.wifi.WifiApManager +import timber.log.Timber +import java.lang.reflect.InvocationTargetException + +val WPA2_PSK = WifiConfiguration.KeyMgmt.strings.indexOf("WPA2_PSK") + +/** + * apBand and apChannel is available since API 23. + * + * https://android.googlesource.com/platform/frameworks/base/+/android-6.0.0_r1/wifi/java/android/net/wifi/WifiConfiguration.java#242 + */ +private val apBandField by lazy { WifiConfiguration::class.java.getDeclaredField("apBand") } +private val apChannelField by lazy { WifiConfiguration::class.java.getDeclaredField("apChannel") } + +/** + * 2GHz band. + * + * https://android.googlesource.com/platform/frameworks/base/+/android-7.0.0_r1/wifi/java/android/net/wifi/WifiConfiguration.java#241 + */ +@RequiresApi(23) +const val AP_BAND_2GHZ = 0 +/** + * 5GHz band. + */ +@RequiresApi(23) +const val AP_BAND_5GHZ = 1 +/** + * Device is allowed to choose the optimal band (2Ghz or 5Ghz) based on device capability, + * operating country code and current radio conditions. + * + * Introduced in 9.0, but we will abuse this constant anyway. + * https://android.googlesource.com/platform/frameworks/base/+/android-9.0.0_r1/wifi/java/android/net/wifi/WifiConfiguration.java#295 + */ +@RequiresApi(23) +const val AP_BAND_ANY = -1 + +/** + * The band which AP resides on + * -1:Any 0:2G 1:5G + * By default, 2G is chosen + */ +var WifiConfiguration.apBand: Int + @RequiresApi(23) get() = apBandField.get(this) as Int + @RequiresApi(23) set(value) = apBandField.set(this, value) +/** + * The channel which AP resides on + * 2G 1-11 + * 5G 36,40,44,48,149,153,157,161,165 + * 0 - find a random available channel according to the apBand + */ +var WifiConfiguration.apChannel: Int + @RequiresApi(23) get() = apChannelField.get(this) as Int + @RequiresApi(23) set(value) = apChannelField.set(this, value) + +/** + * The frequency which AP resides on (MHz). Resides in range [2412, 5815]. + */ +fun channelToFrequency(channel: Int) = when (channel) { + in 1..14 -> 2407 + 5 * channel + in 15..165 -> 5000 + 5 * channel + else -> throw IllegalArgumentException("Invalid channel $channel") +} + +/** + * Based on: + * https://android.googlesource.com/platform/packages/apps/Settings/+/android-5.0.0_r1/src/com/android/settings/wifi/WifiApDialog.java#88 + * https://android.googlesource.com/platform/packages/apps/Settings/+/b1af85d/src/com/android/settings/wifi/tether/WifiTetherSettings.java#162 + */ +fun newWifiApConfiguration(ssid: String, passphrase: String?) = try { + WifiApManager.configuration +} catch (e: InvocationTargetException) { + if (e.targetException !is SecurityException) Timber.w(e) + WifiConfiguration() +}.apply { + SSID = ssid + preSharedKey = passphrase + allowedKeyManagement.clear() + allowedAuthAlgorithms.clear() + allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN) +} diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/room/Converters.kt b/mobile/src/main/java/be/mygod/vpnhotspot/room/Converters.kt index 9a4c95de..4199a33b 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/room/Converters.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/room/Converters.kt @@ -1,8 +1,8 @@ package be.mygod.vpnhotspot.room -import android.os.Parcel import android.text.TextUtils import androidx.room.TypeConverter +import be.mygod.vpnhotspot.util.useParcel import java.net.InetAddress import java.nio.ByteBuffer import java.nio.ByteOrder @@ -10,27 +10,17 @@ import java.nio.ByteOrder object Converters { @JvmStatic @TypeConverter - fun persistCharSequence(cs: CharSequence): ByteArray { - val p = Parcel.obtain() - try { - TextUtils.writeToParcel(cs, p, 0) - return p.marshall() - } finally { - p.recycle() - } + fun persistCharSequence(cs: CharSequence) = useParcel { p -> + TextUtils.writeToParcel(cs, p, 0) + p.marshall() } @JvmStatic @TypeConverter - fun unpersistCharSequence(data: ByteArray): CharSequence { - val p = Parcel.obtain() - try { - p.unmarshall(data, 0, data.size) - p.setDataPosition(0) - return TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(p) - } finally { - p.recycle() - } + fun unpersistCharSequence(data: ByteArray) = useParcel { p -> + p.unmarshall(data, 0, data.size) + p.setDataPosition(0) + TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(p) } @JvmStatic diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/util/Utils.kt b/mobile/src/main/java/be/mygod/vpnhotspot/util/Utils.kt index 42f174ef..54b63d2a 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/util/Utils.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/util/Utils.kt @@ -1,7 +1,9 @@ package be.mygod.vpnhotspot.util +import android.annotation.SuppressLint import android.content.* import android.os.Build +import android.os.Parcel import android.text.Spannable import android.text.SpannableString import android.text.SpannableStringBuilder @@ -15,7 +17,6 @@ import androidx.databinding.BindingAdapter import be.mygod.vpnhotspot.App.Companion.app import be.mygod.vpnhotspot.room.macToString import be.mygod.vpnhotspot.widget.SmartSnackbar -import java.lang.RuntimeException import java.net.InetAddress import java.net.NetworkInterface import java.net.SocketException @@ -32,6 +33,15 @@ fun Long.toPluralInt(): Int { return (this % 1000000000).toInt() + 1000000000 } +@SuppressLint("Recycle") +fun useParcel(block: (Parcel) -> T) = Parcel.obtain().run { + try { + block(this) + } finally { + recycle() + } +} + fun broadcastReceiver(receiver: (Context, Intent) -> Unit) = object : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) = receiver(context, intent) } diff --git a/mobile/src/main/res/drawable/ic_content_wave.xml b/mobile/src/main/res/drawable/ic_content_wave.xml deleted file mode 100644 index 68e040eb..00000000 --- a/mobile/src/main/res/drawable/ic_content_wave.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - diff --git a/mobile/src/main/res/layout/dialog_wifi_ap.xml b/mobile/src/main/res/layout/dialog_wifi_ap.xml index 2d3a445a..1290d53e 100644 --- a/mobile/src/main/res/layout/dialog_wifi_ap.xml +++ b/mobile/src/main/res/layout/dialog_wifi_ap.xml @@ -1,5 +1,5 @@ - + - Настройка Wi-Fi ретранслятора Действительный конфиг не найден. Пожалуйста, сначала запустите ретранслятор. Не удалось удалить избыточную группу P2P (причина: %s) @@ -15,7 +18,9 @@ неподдерживаемая операция Сервис недоступен. Попробуйте позже - + "USB-модем" + "Точка доступа Wi‑Fi" + "Bluetooth-модем" " (подключение)" " (доступный)" @@ -37,7 +42,16 @@ Ошибка: Нисходящий интерфейс не найден Что-то пошло не так, пожалуйста, проверьте отладочную информацию. + Настройка Wi-Fi ретранслятора + "Имя сети" + "Защита" + "Пароль" Пароль должен содержать не менее 8 символов. + "Диапазон частот Wi-Fi" + "Авто" + "2,4 ГГц" + "5,0 ГГц" + "Сохранить" Закрыть diff --git a/mobile/src/main/res/values-zh-rCN/strings.xml b/mobile/src/main/res/values-zh-rCN/strings.xml index 8f8bcfde..feea1ce4 100644 --- a/mobile/src/main/res/values-zh-rCN/strings.xml +++ b/mobile/src/main/res/values-zh-rCN/strings.xml @@ -12,7 +12,6 @@ 请在 2 分钟内在需要连接的设备上使用一键加密以连接到此中继。 成功注册 PIN。 打开 WPS 失败(原因:%s) - 设置 WLAN 中继 未能找到有效的档案。请尝试先打开中继。 删除多余 P2P 群组失败(原因:%s) @@ -44,7 +43,7 @@ 关闭 diff --git a/mobile/src/main/res/values/strings.xml b/mobile/src/main/res/values/strings.xml index 213f4fe6..656a3228 100644 --- a/mobile/src/main/res/values/strings.xml +++ b/mobile/src/main/res/values/strings.xml @@ -2,8 +2,7 @@ Close diff --git a/mobile/src/main/res/values/styles.xml b/mobile/src/main/res/values/styles.xml index 87353f85..ede8762e 100644 --- a/mobile/src/main/res/values/styles.xml +++ b/mobile/src/main/res/values/styles.xml @@ -21,6 +21,18 @@ vertical start + +