diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/RepeaterService.kt b/mobile/src/main/java/be/mygod/vpnhotspot/RepeaterService.kt index db464636..9b3218e0 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/RepeaterService.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/RepeaterService.kt @@ -5,14 +5,12 @@ import android.content.Intent import android.content.SharedPreferences import android.content.res.Configuration import android.net.NetworkInfo -import android.net.wifi.p2p.WifiP2pDevice -import android.net.wifi.p2p.WifiP2pGroup -import android.net.wifi.p2p.WifiP2pInfo -import android.net.wifi.p2p.WifiP2pManager +import android.net.wifi.p2p.* import android.os.Build import android.os.Handler import android.os.Looper import androidx.annotation.StringRes +import androidx.core.content.edit import androidx.core.content.getSystemService import androidx.core.os.BuildCompat import be.mygod.vpnhotspot.App.Companion.app @@ -33,7 +31,10 @@ import java.lang.reflect.InvocationTargetException class RepeaterService : Service(), WifiP2pManager.ChannelListener, SharedPreferences.OnSharedPreferenceChangeListener { companion object { private const val TAG = "RepeaterService" - const val KEY_OPERATING_CHANNEL = "service.repeater.oc" + private const val KEY_NETWORK_NAME = "service.repeater.networkName" + private const val KEY_PASSPHRASE = "service.repeater.passphrase" + private const val KEY_OPERATING_BAND = "service.repeater.band" + private const val KEY_OPERATING_CHANNEL = "service.repeater.oc" /** * This is only a "ServiceConnection" to system service and its impact on system is minimal. @@ -49,12 +50,23 @@ class RepeaterService : Service(), WifiP2pManager.ChannelListener, SharedPrefere val supported get() = p2pManager != null var persistentSupported = false + var networkName: String? + get() = app.pref.getString(KEY_NETWORK_NAME, null) + set(value) = app.pref.edit { putString(KEY_NETWORK_NAME, value) } + var passphrase: String? + get() = app.pref.getString(KEY_PASSPHRASE, null) + set(value) = app.pref.edit { putString(KEY_PASSPHRASE, value) } + var operatingBand: Int + get() = app.pref.getInt(KEY_OPERATING_BAND, 0) + set(value) = app.pref.edit { putInt(KEY_OPERATING_BAND, value) } 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() + set(value) = app.pref.edit { putString(RepeaterService.KEY_OPERATING_CHANNEL, value.toString()) } + + private val networkNameField by lazy { WifiP2pConfig::class.java.getDeclaredField("networkName") } } enum class Status { @@ -120,6 +132,7 @@ class RepeaterService : Service(), WifiP2pManager.ChannelListener, SharedPrefere } } private var routingManager: RoutingManager? = null + private var persistNextGroup = false var status = Status.IDLE private set(value) { @@ -245,12 +258,25 @@ class RepeaterService : Service(), WifiP2pManager.ChannelListener, SharedPrefere * startService Step 2 (if a group isn't already available) */ private fun doStart() = try { - p2pManager.createGroup(channel, object : WifiP2pManager.ActionListener { + val listener = object : WifiP2pManager.ActionListener { override fun onFailure(reason: Int) { startFailure(formatReason(R.string.repeater_create_group_failure, reason)) } override fun onSuccess() { } // wait for WIFI_P2P_CONNECTION_CHANGED_ACTION to fire to go to step 3 - }) + } + val networkName = networkName + val passphrase = passphrase + if (!BuildCompat.isAtLeastQ() || networkName == null || passphrase == null) { + persistNextGroup = true + p2pManager.createGroup(channel, listener) + } else p2pManager.createGroup(channel, WifiP2pConfig.Builder().apply { + setNetworkName("DIRECT-00-VPNHotspot") // placeholder for bypassing networkName check + setPassphrase(passphrase) + val frequency = operatingChannel + if (frequency == 0) setGroupOperatingBand(operatingBand) else setGroupOperatingFrequency(frequency) + }.build().apply { + networkNameField.set(this, networkName) + }, listener) } catch (e: SecurityException) { Timber.w(e) startFailure(e.readableMessage) @@ -276,6 +302,11 @@ class RepeaterService : Service(), WifiP2pManager.ChannelListener, SharedPrefere */ private fun doStart(group: WifiP2pGroup) { binder.group = group + if (persistNextGroup) { + networkName = group.networkName + passphrase = group.passphrase + persistNextGroup = false + } check(routingManager == null) routingManager = RoutingManager.LocalOnly(this, group.`interface`!!).apply { start() } status = Status.ACTIVE 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 f49bdb05..b7add94c 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/manage/RepeaterManager.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/manage/RepeaterManager.kt @@ -7,6 +7,7 @@ import android.content.Intent import android.content.ServiceConnection import android.content.pm.PackageManager import android.net.wifi.WifiConfiguration +import android.net.wifi.p2p.WifiP2pConfig import android.net.wifi.p2p.WifiP2pGroup import android.os.Build import android.os.Bundle @@ -32,6 +33,7 @@ import be.mygod.vpnhotspot.util.formatAddresses import be.mygod.vpnhotspot.widget.SmartSnackbar import kotlinx.android.parcel.Parcelize import timber.log.Timber +import java.lang.IllegalArgumentException import java.net.NetworkInterface import java.net.SocketException @@ -112,6 +114,8 @@ class RepeaterManager(private val parent: TetheringFragment) : Manager(), Servic } } + @Deprecated("No longer used since Android Q") + @Suppress("DEPRECATION") class ConfigHolder : ViewModel() { var config: P2pSupplicantConfiguration? = null } @@ -124,6 +128,7 @@ class RepeaterManager(private val parent: TetheringFragment) : Manager(), Servic private val data = Data() internal var binder: RepeaterService.Binder? = null private var p2pInterface: String? = null + @Suppress("DEPRECATION") private val holder = ViewModelProviders.of(parent).get() override fun bindTo(viewHolder: RecyclerView.ViewHolder) { @@ -153,25 +158,51 @@ class RepeaterManager(private val parent: TetheringFragment) : Manager(), Servic } 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 + if (BuildCompat.isAtLeastQ()) { + val networkName = RepeaterService.networkName + val passphrase = RepeaterService.passphrase + if (networkName != null && passphrase != null) { + return newWifiApConfiguration(networkName, passphrase).apply { + allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK) // is not actually used + apBand = when (RepeaterService.operatingBand) { + WifiP2pConfig.GROUP_OWNER_BAND_AUTO -> AP_BAND_ANY + WifiP2pConfig.GROUP_OWNER_BAND_2GHZ -> AP_BAND_2GHZ + WifiP2pConfig.GROUP_OWNER_BAND_5GHZ -> AP_BAND_5GHZ + else -> throw IllegalArgumentException("Unknown operatingBand") + } apChannel = RepeaterService.operatingChannel } } - } catch (e: RuntimeException) { - Timber.w(e) + } else @Suppress("DEPRECATION") { + 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(R.string.repeater_configure_failure).show() return null } fun updateConfiguration(config: WifiConfiguration) { - holder.config?.let { master -> + if (BuildCompat.isAtLeastQ()) { + RepeaterService.networkName = config.SSID + RepeaterService.passphrase = config.preSharedKey + RepeaterService.operatingBand = when (config.apBand) { + AP_BAND_ANY -> WifiP2pConfig.GROUP_OWNER_BAND_AUTO + AP_BAND_2GHZ -> WifiP2pConfig.GROUP_OWNER_BAND_2GHZ + AP_BAND_5GHZ -> WifiP2pConfig.GROUP_OWNER_BAND_5GHZ + else -> throw IllegalArgumentException("Unknown apBand") + } + } else @Suppress("DEPRECATION") holder.config?.let { master -> if (binder?.group?.networkName != config.SSID || master.psk != config.preSharedKey) try { master.update(config.SSID, config.preSharedKey) binder!!.group = null diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/configuration/P2pSupplicantConfiguration.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/configuration/P2pSupplicantConfiguration.kt index 396086b4..17323224 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/configuration/P2pSupplicantConfiguration.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/configuration/P2pSupplicantConfiguration.kt @@ -14,6 +14,7 @@ import java.lang.IllegalStateException * https://android.googlesource.com/platform/external/wpa_supplicant_8/+/d2986c2/wpa_supplicant/config.c#488 * https://android.googlesource.com/platform/external/wpa_supplicant_8/+/6fa46df/wpa_supplicant/config_file.c#182 */ +@Deprecated("No longer used since Android Q") class P2pSupplicantConfiguration(private val group: WifiP2pGroup, ownerAddress: String?) { companion object { private const val TAG = "P2pSupplicantConfiguration"