From 97e0a6386f0fb8f19ebfa24f4e62b37c397348dd Mon Sep 17 00:00:00 2001 From: Mygod Date: Sat, 21 Apr 2018 14:49:38 -0700 Subject: [PATCH] Support setting Wi-Fi operating channel --- README.md | 4 -- .../src/main/java/be/mygod/vpnhotspot/App.kt | 8 ++++ .../be/mygod/vpnhotspot/RepeaterService.kt | 41 ++++++++++++++++++- mobile/src/main/res/values-zh-rCN/strings.xml | 5 ++- mobile/src/main/res/values/strings.xml | 5 ++- mobile/src/main/res/xml/pref_settings.xml | 16 ++++++-- 6 files changed, 69 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 70cecbd3..8a25f4b4 100644 --- a/README.md +++ b/README.md @@ -39,10 +39,6 @@ SSID is hardcoded to `DIRECT--` so the only thi system Wi-Fi direct settings. Password is hardcoded to a random 8 char string. Changing anything else requires replacing driver `wpa_supplicant` which we are not considering implementing. -### Connect a 2.4GHz-only device to a 5GHz repeater? - -You'll have to use WPS for now to make the repeater switch to 2.4GHz. - ### [IPv6 tethering?](https://github.com/Mygod/VPNHotspot/issues/6) ### Missing `android.permission.MANAGE_USB` permission? diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/App.kt b/mobile/src/main/java/be/mygod/vpnhotspot/App.kt index a094aa0d..dfabee80 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/App.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/App.kt @@ -15,6 +15,7 @@ import android.widget.Toast class App : Application() { companion object { const val ACTION_CLEAN_ROUTINGS = "be.mygod.vpnhotspot.CLEAN_ROUTINGS" + const val KEY_OPERATING_CHANNEL = "service.repeater.oc" private const val KEY_DNS = "service.dns" @SuppressLint("StaticFieldLeak") @@ -29,6 +30,7 @@ class App : Application() { deviceContext.moveSharedPreferencesFrom(this, PreferenceManager.getDefaultSharedPreferencesName(this)) } else deviceContext = this // workaround for support lib PreferenceDataStore bug + operatingChannel = operatingChannel dns = dns ServiceNotification.updateNotificationChannels() } @@ -43,6 +45,12 @@ class App : Application() { val pref: SharedPreferences by lazy { PreferenceManager.getDefaultSharedPreferences(deviceContext) } val connectivity by lazy { getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager } + var operatingChannel: Int + get() { + val result = pref.getString(KEY_OPERATING_CHANNEL, null)?.toIntOrNull() ?: 0 + return if (result in 0..165) result else 0 + } + set(value) = pref.edit().putString(KEY_OPERATING_CHANNEL, value.toString()).apply() var dns: String get() = pref.getString(KEY_DNS, "8.8.8.8") set(value) = pref.edit().putString(KEY_DNS, value).apply() diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/RepeaterService.kt b/mobile/src/main/java/be/mygod/vpnhotspot/RepeaterService.kt index 69ebb8b5..8f90dd5c 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/RepeaterService.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/RepeaterService.kt @@ -3,6 +3,7 @@ package be.mygod.vpnhotspot import android.app.Service import android.content.Context import android.content.Intent +import android.content.SharedPreferences import android.net.NetworkInfo import android.net.wifi.WpsInfo import android.net.wifi.p2p.WifiP2pGroup @@ -21,7 +22,8 @@ import java.net.InetAddress import java.net.SocketException import java.util.regex.Pattern -class RepeaterService : Service(), WifiP2pManager.ChannelListener, VpnMonitor.Callback { +class RepeaterService : Service(), WifiP2pManager.ChannelListener, VpnMonitor.Callback, + SharedPreferences.OnSharedPreferenceChangeListener { companion object { const val ACTION_STATUS_CHANGED = "be.mygod.vpnhotspot.RepeaterService.STATUS_CHANGED" const val KEY_NET_ID = "netId" @@ -40,6 +42,21 @@ class RepeaterService : Service(), WifiP2pManager.ChannelListener, VpnMonitor.Ca */ private val patternNetworkInfo = "^mNetworkInfo .* (isA|a)vailable: (true|false)".toPattern(Pattern.MULTILINE) + /** + * Available since Android 4.4. + * + * Source: https://android.googlesource.com/platform/frameworks/base/+/android-4.4_r1/wifi/java/android/net/wifi/p2p/WifiP2pManager.java#994 + * Implementation: https://android.googlesource.com/platform/frameworks/opt/net/wifi/+/d72d2f4/service/java/com/android/server/wifi/p2p/SupplicantP2pIfaceHal.java#1159 + */ + private val setWifiP2pChannels by lazy { + WifiP2pManager::class.java.getDeclaredMethod("setWifiP2pChannels", WifiP2pManager.Channel::class.java, + Int::class.java, Int::class.java, WifiP2pManager.ActionListener::class.java) + } + private fun WifiP2pManager.setWifiP2pChannels(c: WifiP2pManager.Channel, lc: Int, oc: Int, + listener: WifiP2pManager.ActionListener) { + setWifiP2pChannels.invoke(this, c, lc, oc, listener) + } + /** * Available since Android 4.3. * @@ -171,6 +188,7 @@ class RepeaterService : Service(), WifiP2pManager.ChannelListener, VpnMonitor.Ca try { p2pManager = getSystemService(Context.WIFI_P2P_SERVICE) as WifiP2pManager onChannelDisconnected() + app.pref.registerOnSharedPreferenceChangeListener(this) } catch (exc: TypeCastException) { exc.printStackTrace() } @@ -178,8 +196,28 @@ class RepeaterService : Service(), WifiP2pManager.ChannelListener, VpnMonitor.Ca override fun onBind(intent: Intent) = binder + private fun setOperatingChannel(init: Boolean = false) { + val oc = app.operatingChannel + if (!init || oc > 0) { + // we don't care about listening channel + debugLog(TAG, "Setting OC to $oc") + p2pManager.setWifiP2pChannels(channel, 0, oc, object : WifiP2pManager.ActionListener { + override fun onSuccess() { } + override fun onFailure(reason: Int) { + Toast.makeText(this@RepeaterService, formatReason(R.string.repeater_set_oc_failure, reason), + Toast.LENGTH_SHORT).show() + } + }) + } + } + override fun onChannelDisconnected() { channel = p2pManager.initialize(this, Looper.getMainLooper(), this) + setOperatingChannel(true) + } + + override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) { + if (key == App.KEY_OPERATING_CHANNEL) setOperatingChannel() } /** @@ -339,6 +377,7 @@ class RepeaterService : Service(), WifiP2pManager.ChannelListener, VpnMonitor.Ca override fun onDestroy() { if (status != Status.IDLE) binder.shutdown() clean() // force clean to prevent leakage + app.pref.unregisterOnSharedPreferenceChangeListener(this) super.onDestroy() } } diff --git a/mobile/src/main/res/values-zh-rCN/strings.xml b/mobile/src/main/res/values-zh-rCN/strings.xml index 84a9d7fc..8413265a 100644 --- a/mobile/src/main/res/values-zh-rCN/strings.xml +++ b/mobile/src/main/res/values-zh-rCN/strings.xml @@ -25,6 +25,7 @@ 创建 P2P 群组失败(原因:%s) 关闭已有 P2P 群组失败(原因:%s) 关闭 P2P 群组失败(原因:%s) + 设置运行频段失败(原因:%s) 内部异常 设备不支持 Wi\u2011Fi 直连 @@ -53,7 +54,9 @@ %s (已断开) 服务 - 严格模式 (仅用于中继) + Wi\u2011Fi 运行频段 + "%s (0 = 自动, 1\u201114 = 2.4GHz, 15\u2011165 = 5GHz)" + 严格模式 只允许通过 VPN 隧道的包通过 备用 DNS 服务器[:端口] 清理/重新应用路由规则 diff --git a/mobile/src/main/res/values/strings.xml b/mobile/src/main/res/values/strings.xml index 7b139b4a..30a3df60 100644 --- a/mobile/src/main/res/values/strings.xml +++ b/mobile/src/main/res/values/strings.xml @@ -27,6 +27,7 @@ Failed to create P2P group (reason: %s) Failed to remove P2P group (reason: %s) Failed to remove old P2P group (reason: %s) + Failed to set operating channel (reason: %s) internal error Wi\u2011Fi direct unsupported @@ -55,7 +56,9 @@ %s (lost) Service - Strict mode (repeater only) + Operating Wi\u2011Fi channel + %s (0 = auto, 1\u201114 = 2.4GHz, 15\u2011165 = 5GHz) + Strict mode Only allow packets that goes through VPN tunnel Fallback DNS server[:port] Clean/reapply routing rules diff --git a/mobile/src/main/res/xml/pref_settings.xml b/mobile/src/main/res/xml/pref_settings.xml index 53aff2f7..850be482 100644 --- a/mobile/src/main/res/xml/pref_settings.xml +++ b/mobile/src/main/res/xml/pref_settings.xml @@ -1,15 +1,25 @@ - + + android:title="@string/title_repeater"> + + +