librootkotlinx

Fixes #14, #27, #114, #117.
This commit is contained in:
Mygod
2020-06-21 05:33:39 +08:00
parent 7b1f610f9a
commit ad218d7ec6
51 changed files with 1781 additions and 574 deletions

View File

@@ -8,15 +8,11 @@ import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.Build
import android.widget.Toast
import androidx.annotation.RequiresApi
import be.mygod.vpnhotspot.App.Companion.app
import be.mygod.vpnhotspot.net.TetheringManager
import be.mygod.vpnhotspot.util.broadcastReceiver
import be.mygod.vpnhotspot.util.readableMessage
import be.mygod.vpnhotspot.widget.SmartSnackbar
import timber.log.Timber
import java.io.IOException
import java.lang.reflect.InvocationTargetException
class BluetoothTethering(context: Context, val stateListener: () -> Unit) :
@@ -26,9 +22,16 @@ class BluetoothTethering(context: Context, val stateListener: () -> Unit) :
* PAN Profile
*/
private const val PAN = 5
private val isTetheringOn by lazy {
Class.forName("android.bluetooth.BluetoothPan").getDeclaredMethod("isTetheringOn")
private val clazz by lazy { Class.forName("android.bluetooth.BluetoothPan") }
private val constructor by lazy {
clazz.getDeclaredConstructor(Context::class.java, BluetoothProfile.ServiceListener::class.java)
}
private val isTetheringOn by lazy { clazz.getDeclaredMethod("isTetheringOn") }
fun pan(context: Context, serviceListener: BluetoothProfile.ServiceListener) =
constructor.newInstance(context, serviceListener) as BluetoothProfile
val BluetoothProfile.isTetheringOn get() = isTetheringOn(this) as Boolean
fun BluetoothProfile.closePan() = BluetoothAdapter.getDefaultAdapter()!!.closeProfileProxy(PAN, this)
private fun registerBluetoothStateListener(receiver: BroadcastReceiver) =
app.registerReceiver(receiver, IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED))
@@ -41,23 +44,8 @@ class BluetoothTethering(context: Context, val stateListener: () -> Unit) :
@TargetApi(24)
override fun onReceive(context: Context?, intent: Intent?) {
when (intent?.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR)) {
BluetoothAdapter.STATE_ON -> try {
BluetoothAdapter.STATE_ON -> {
TetheringManager.startTethering(TetheringManager.TETHERING_BLUETOOTH, true, pendingCallback!!)
} catch (e: IOException) {
Timber.w(e)
Toast.makeText(context, e.readableMessage, Toast.LENGTH_LONG).show()
pendingCallback!!.onException()
} catch (e: InvocationTargetException) {
if (e.targetException !is SecurityException) Timber.w(e)
var cause: Throwable? = e
while (cause != null) {
cause = cause.cause
if (cause != null && cause !is InvocationTargetException) {
Toast.makeText(context, cause.readableMessage, Toast.LENGTH_LONG).show()
pendingCallback!!.onException()
break
}
}
}
BluetoothAdapter.STATE_OFF, BluetoothAdapter.ERROR -> { }
else -> return // ignore transition states
@@ -81,18 +69,18 @@ class BluetoothTethering(context: Context, val stateListener: () -> Unit) :
}
}
private var connected = false
private var pan: BluetoothProfile? = null
var activeFailureCause: Throwable? = null
/**
* Requires BLUETOOTH_PRIVILEGED on API 30+.
*
* Based on: https://android.googlesource.com/platform/packages/apps/Settings/+/78d5efd/src/com/android/settings/TetherSettings.java
*/
val active: Boolean? get() {
activeFailureCause = null
val pan = pan ?: return null
if (!connected) return null
activeFailureCause = null
return BluetoothAdapter.getDefaultAdapter()?.state == BluetoothAdapter.STATE_ON && try {
isTetheringOn(pan) as Boolean
pan.isTetheringOn
} catch (e: InvocationTargetException) {
activeFailureCause = e.cause ?: e
if (e.cause is SecurityException && Build.VERSION.SDK_INT >= 30) Timber.d(e) else Timber.w(e)
@@ -104,24 +92,23 @@ class BluetoothTethering(context: Context, val stateListener: () -> Unit) :
init {
try {
BluetoothAdapter.getDefaultAdapter()?.getProfileProxy(context, this, PAN)
} catch (e: SecurityException) {
pan = pan(context, this)
} catch (e: InvocationTargetException) {
Timber.w(e)
SmartSnackbar.make(e).show()
activeFailureCause = e
}
registerBluetoothStateListener(receiver)
}
override fun onServiceDisconnected(profile: Int) {
pan = null
connected = false
}
override fun onServiceConnected(profile: Int, proxy: BluetoothProfile) {
pan = proxy
connected = true
stateListener()
}
override fun close() {
app.unregisterReceiver(receiver)
BluetoothAdapter.getDefaultAdapter()?.closeProfileProxy(PAN, pan)
pan = null
pan?.closePan()
}
}

View File

@@ -6,6 +6,7 @@ import be.mygod.vpnhotspot.R
import be.mygod.vpnhotspot.net.IpNeighbour
import be.mygod.vpnhotspot.net.monitor.IpNeighbourMonitor
import be.mygod.vpnhotspot.util.KillableTileService
import java.net.Inet4Address
@RequiresApi(24)
abstract class IpNeighbourMonitoringTileService : KillableTileService(), IpNeighbourMonitor.Callback {
@@ -24,7 +25,7 @@ abstract class IpNeighbourMonitoringTileService : KillableTileService(), IpNeigh
protected fun Tile.subtitleDevices(filter: (String) -> Boolean) {
val size = neighbours
.filter { it.state != IpNeighbour.State.FAILED && filter(it.dev) }
.filter { it.ip is Inet4Address && it.state != IpNeighbour.State.FAILED && filter(it.dev) }
.distinctBy { it.lladdr }
.size
if (size > 0) subtitle(resources.getQuantityString(

View File

@@ -210,9 +210,8 @@ class RepeaterManager(private val parent: TetheringFragment) : Manager(), Servic
band = SoftApConfigurationCompat.BAND_ANY
channel = RepeaterService.operatingChannel
try {
val config = withContext(Dispatchers.Default) {
P2pSupplicantConfiguration(group, RepeaterService.lastMac)
}
val config = P2pSupplicantConfiguration(group)
config.init(RepeaterService.lastMac)
holder.config = config
passphrase = config.psk
bssid = config.bssid

View File

@@ -12,6 +12,7 @@ import androidx.core.content.ContextCompat
import be.mygod.vpnhotspot.R
import be.mygod.vpnhotspot.RepeaterService
import be.mygod.vpnhotspot.util.KillableTileService
import be.mygod.vpnhotspot.util.Services
import be.mygod.vpnhotspot.util.stopAndUnbind
@RequiresApi(24)
@@ -22,13 +23,13 @@ class RepeaterTileService : KillableTileService() {
override fun onStartListening() {
super.onStartListening()
if (RepeaterService.supported) {
if (Services.p2p != null) {
bindService(Intent(this, RepeaterService::class.java), this, Context.BIND_AUTO_CREATE)
} else updateTile()
}
override fun onStopListening() {
if (RepeaterService.supported) stopAndUnbind(this)
if (Services.p2p != null) stopAndUnbind(this)
super.onStopListening()
}

View File

@@ -20,8 +20,10 @@ import be.mygod.vpnhotspot.net.TetheringManager
import be.mygod.vpnhotspot.net.wifi.WifiApManager
import be.mygod.vpnhotspot.util.readableMessage
import be.mygod.vpnhotspot.widget.SmartSnackbar
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import timber.log.Timber
import java.io.IOException
import java.lang.reflect.InvocationTargetException
sealed class TetherManager(protected val parent: TetheringFragment) : Manager(),
@@ -50,25 +52,12 @@ sealed class TetherManager(protected val parent: TetheringFragment) : Manager(),
} catch (e: RuntimeException) {
app.logEvent("manage_write_settings") { param("message", e.toString()) }
}
val started = manager.isStarted
try {
if (started) manager.stop() else manager.start()
} catch (e: IOException) {
Timber.w(e)
Toast.makeText(mainActivity, e.readableMessage, Toast.LENGTH_LONG).show()
ManageBar.start(itemView.context)
if (manager.isStarted) try {
manager.stop()
} catch (e: InvocationTargetException) {
if (e.targetException !is SecurityException) Timber.w(e)
var cause: Throwable? = e
while (cause != null) {
cause = cause.cause
if (cause != null && cause !is InvocationTargetException) {
Toast.makeText(mainActivity, cause.readableMessage, Toast.LENGTH_LONG).show()
ManageBar.start(itemView.context)
break
}
}
}
manager.onException(e)
} else manager.start()
}
}
@@ -96,6 +85,14 @@ sealed class TetherManager(protected val parent: TetheringFragment) : Manager(),
error?.let { SmartSnackbar.make("$tetherType: ${TetheringManager.tetherErrorMessage(it)}") }
data.notifyChange()
}
override fun onException(e: Exception) {
if (e !is InvocationTargetException || e.targetException !is SecurityException) Timber.w(e)
GlobalScope.launch(Dispatchers.Main.immediate) {
val context = parent.context ?: app
Toast.makeText(context, e.readableMessage, Toast.LENGTH_LONG).show()
ManageBar.start(context)
}
}
override fun bindTo(viewHolder: RecyclerView.ViewHolder) {
(viewHolder as ViewHolder).manager = this
@@ -124,7 +121,7 @@ sealed class TetherManager(protected val parent: TetheringFragment) : Manager(),
override val type get() = VIEW_TYPE_WIFI
override fun start() = TetheringManager.startTethering(TetheringManager.TETHERING_WIFI, true, this)
override fun stop() = TetheringManager.stopTethering(TetheringManager.TETHERING_WIFI)
override fun stop() = TetheringManager.stopTethering(TetheringManager.TETHERING_WIFI, this::onException)
}
@RequiresApi(24)
class Usb(parent: TetheringFragment) : TetherManager(parent) {
@@ -133,7 +130,7 @@ sealed class TetherManager(protected val parent: TetheringFragment) : Manager(),
override val type get() = VIEW_TYPE_USB
override fun start() = TetheringManager.startTethering(TetheringManager.TETHERING_USB, true, this)
override fun stop() = TetheringManager.stopTethering(TetheringManager.TETHERING_USB)
override fun stop() = TetheringManager.stopTethering(TetheringManager.TETHERING_USB, this::onException)
}
@RequiresApi(24)
class Bluetooth(parent: TetheringFragment) : TetherManager(parent), DefaultLifecycleObserver {
@@ -153,8 +150,6 @@ sealed class TetherManager(protected val parent: TetheringFragment) : Manager(),
override val type get() = VIEW_TYPE_BLUETOOTH
override val isStarted get() = tethering.active == true
override fun onException() = ManageBar.start(parent.context ?: app)
private var baseError: CharSequence? = null
private fun makeErrorMessage(): CharSequence = listOfNotNull(
if (tethering.active == null) tethering.activeFailureCause?.readableMessage else null,
@@ -166,7 +161,7 @@ sealed class TetherManager(protected val parent: TetheringFragment) : Manager(),
override fun start() = BluetoothTethering.start(this)
override fun stop() {
TetheringManager.stopTethering(TetheringManager.TETHERING_BLUETOOTH)
TetheringManager.stopTethering(TetheringManager.TETHERING_BLUETOOTH, this::onException)
Thread.sleep(1) // give others a room to breathe
onTetheringStarted() // force flush state
}
@@ -178,7 +173,7 @@ sealed class TetherManager(protected val parent: TetheringFragment) : Manager(),
override val type get() = VIEW_TYPE_ETHERNET
override fun start() = TetheringManager.startTethering(TetheringManager.TETHERING_ETHERNET, true, this)
override fun stop() = TetheringManager.stopTethering(TetheringManager.TETHERING_ETHERNET)
override fun stop() = TetheringManager.stopTethering(TetheringManager.TETHERING_ETHERNET, this::onException)
}
@RequiresApi(30)
class Ncm(parent: TetheringFragment) : TetherManager(parent) {
@@ -187,7 +182,7 @@ sealed class TetherManager(protected val parent: TetheringFragment) : Manager(),
override val type get() = VIEW_TYPE_NCM
override fun start() = TetheringManager.startTethering(TetheringManager.TETHERING_NCM, true, this)
override fun stop() = TetheringManager.stopTethering(TetheringManager.TETHERING_NCM)
override fun stop() = TetheringManager.stopTethering(TetheringManager.TETHERING_NCM, this::onException)
}
@Suppress("DEPRECATION")

View File

@@ -27,10 +27,9 @@ import be.mygod.vpnhotspot.net.TetheringManager.localOnlyTetheredIfaces
import be.mygod.vpnhotspot.net.TetheringManager.tetheredIfaces
import be.mygod.vpnhotspot.net.wifi.WifiApDialogFragment
import be.mygod.vpnhotspot.net.wifi.WifiApManager
import be.mygod.vpnhotspot.util.ServiceForegroundConnector
import be.mygod.vpnhotspot.util.broadcastReceiver
import be.mygod.vpnhotspot.util.isNotGone
import be.mygod.vpnhotspot.util.showAllowingStateLoss
import be.mygod.vpnhotspot.root.RootManager
import be.mygod.vpnhotspot.root.WifiApCommands
import be.mygod.vpnhotspot.util.*
import be.mygod.vpnhotspot.widget.SmartSnackbar
import kotlinx.coroutines.CompletableDeferred
import timber.log.Timber
@@ -89,7 +88,7 @@ class TetheringFragment : Fragment(), ServiceConnection, Toolbar.OnMenuItemClick
updateEnabledTypes()
val list = ArrayList<Manager>()
if (RepeaterService.supported) list.add(repeaterManager)
if (Services.p2p != null) list.add(repeaterManager)
if (Build.VERSION.SDK_INT >= 26) list.add(localOnlyHotspotManager)
val monitoredIfaces = binder?.monitoredIfaces ?: emptyList()
updateMonitorList(activeIfaces - monitoredIfaces)
@@ -150,10 +149,12 @@ class TetheringFragment : Fragment(), ServiceConnection, Toolbar.OnMenuItemClick
}
}
}
private var apConfigurationRunning = false
override fun onMenuItemClick(item: MenuItem?): Boolean {
return when (item?.itemId) {
R.id.configuration -> item.subMenu.run {
findItem(R.id.configuration_repeater).isNotGone = RepeaterService.supported
findItem(R.id.configuration_repeater).isNotGone = Services.p2p != null
findItem(R.id.configuration_temp_hotspot).isNotGone =
adapter.localOnlyHotspotManager.binder?.configuration != null
true
@@ -170,16 +171,30 @@ class TetheringFragment : Fragment(), ServiceConnection, Toolbar.OnMenuItemClick
}.showAllowingStateLoss(parentFragmentManager)
true
}
R.id.configuration_ap -> try {
WifiApDialogFragment().apply {
arg(WifiApDialogFragment.Arg(WifiApManager.configuration))
key()
}.showAllowingStateLoss(parentFragmentManager)
R.id.configuration_ap -> if (apConfigurationRunning) false else {
apConfigurationRunning = true
viewLifecycleOwner.lifecycleScope.launchWhenCreated {
try {
WifiApManager.configuration
} catch (e: InvocationTargetException) {
if (e.targetException !is SecurityException) Timber.w(e)
try {
RootManager.use { it.execute(WifiApCommands.GetConfiguration()) }
} catch (eRoot: Exception) {
eRoot.addSuppressed(e)
Timber.w(eRoot)
SmartSnackbar.make(eRoot).show()
null
}
}?.let { configuration ->
WifiApDialogFragment().apply {
arg(WifiApDialogFragment.Arg(configuration))
key()
}.showAllowingStateLoss(parentFragmentManager)
}
apConfigurationRunning = false
}
true
} catch (e: InvocationTargetException) {
if (e.targetException !is SecurityException) Timber.w(e)
SmartSnackbar.make(e.targetException).show()
false
}
else -> false
}
@@ -187,13 +202,20 @@ class TetheringFragment : Fragment(), ServiceConnection, Toolbar.OnMenuItemClick
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
AlertDialogFragment.setResultListener<WifiApDialogFragment, WifiApDialogFragment.Arg>(this) { which, ret ->
if (which == DialogInterface.BUTTON_POSITIVE) try {
WifiApManager.configuration = ret!!.configuration
} catch (e: IllegalArgumentException) {
Timber.d(e)
SmartSnackbar.make(R.string.configuration_rejected).show()
} catch (e: InvocationTargetException) {
SmartSnackbar.make(e.targetException).show()
if (which == DialogInterface.BUTTON_POSITIVE) viewLifecycleOwner.lifecycleScope.launchWhenCreated {
val success = try {
WifiApManager.setConfiguration(ret!!.configuration)
} catch (e: InvocationTargetException) {
try {
RootManager.use { it.execute(WifiApCommands.SetConfiguration(ret!!.configuration)) }
} catch (eRoot: Exception) {
eRoot.addSuppressed(e)
Timber.w(eRoot)
SmartSnackbar.make(eRoot).show()
null
}
}
if (success == false) SmartSnackbar.make(R.string.configuration_rejected).show()
}
}
binding = FragmentTetheringBinding.inflate(inflater, container, false)

View File

@@ -11,6 +11,7 @@ import android.service.quicksettings.Tile
import android.widget.Toast
import androidx.annotation.RequiresApi
import androidx.core.content.ContextCompat
import be.mygod.vpnhotspot.App
import be.mygod.vpnhotspot.R
import be.mygod.vpnhotspot.TetheringService
import be.mygod.vpnhotspot.net.TetherType
@@ -20,8 +21,10 @@ import be.mygod.vpnhotspot.net.wifi.WifiApManager
import be.mygod.vpnhotspot.util.broadcastReceiver
import be.mygod.vpnhotspot.util.readableMessage
import be.mygod.vpnhotspot.util.stopAndUnbind
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import timber.log.Timber
import java.io.IOException
import java.lang.reflect.InvocationTargetException
@RequiresApi(24)
@@ -97,30 +100,17 @@ sealed class TetheringTileService : IpNeighbourMonitoringTileService(), Tetherin
}
}
protected inline fun safeInvoker(func: () -> Unit) = try {
func()
} catch (e: IOException) {
Timber.w(e)
Toast.makeText(this, e.readableMessage, Toast.LENGTH_LONG).show()
} catch (e: InvocationTargetException) {
if (e.targetException !is SecurityException) Timber.w(e)
var cause: Throwable? = e
while (cause != null) {
cause = cause.cause
if (cause != null && cause !is InvocationTargetException) {
Toast.makeText(this, cause.readableMessage, Toast.LENGTH_LONG).show()
break
}
}
}
override fun onClick() {
val interested = interested ?: return
if (interested.isEmpty()) safeInvoker { start() } else {
if (interested.isEmpty()) start() else {
val binder = binder
if (binder == null) tapPending = true else {
val inactive = interested.filterNot(binder::isActive)
if (inactive.isEmpty()) safeInvoker { stop() }
else ContextCompat.startForegroundService(this, Intent(this, TetheringService::class.java)
if (inactive.isEmpty()) try {
stop()
} catch (e: Exception) {
onException(e)
} else ContextCompat.startForegroundService(this, Intent(this, TetheringService::class.java)
.putExtra(TetheringService.EXTRA_ADD_INTERFACES, inactive.toTypedArray()))
}
}
@@ -132,6 +122,12 @@ sealed class TetheringTileService : IpNeighbourMonitoringTileService(), Tetherin
error?.let { Toast.makeText(this, TetheringManager.tetherErrorMessage(it), Toast.LENGTH_LONG).show() }
updateTile()
}
override fun onException(e: Exception) {
if (e !is InvocationTargetException || e.targetException !is SecurityException) Timber.w(e)
GlobalScope.launch(Dispatchers.Main.immediate) {
Toast.makeText(this@TetheringTileService, e.readableMessage, Toast.LENGTH_LONG).show()
}
}
class Wifi : TetheringTileService() {
override val labelString get() = R.string.tethering_manage_wifi
@@ -139,14 +135,14 @@ sealed class TetheringTileService : IpNeighbourMonitoringTileService(), Tetherin
override val icon get() = R.drawable.ic_device_wifi_tethering
override fun start() = TetheringManager.startTethering(TetheringManager.TETHERING_WIFI, true, this)
override fun stop() = TetheringManager.stopTethering(TetheringManager.TETHERING_WIFI)
override fun stop() = TetheringManager.stopTethering(TetheringManager.TETHERING_WIFI, this::onException)
}
class Usb : TetheringTileService() {
override val labelString get() = R.string.tethering_manage_usb
override val tetherType get() = TetherType.USB
override fun start() = TetheringManager.startTethering(TetheringManager.TETHERING_USB, true, this)
override fun stop() = TetheringManager.stopTethering(TetheringManager.TETHERING_USB)
override fun stop() = TetheringManager.stopTethering(TetheringManager.TETHERING_USB, this::onException)
}
class Bluetooth : TetheringTileService() {
private var tethering: BluetoothTethering? = null
@@ -156,7 +152,7 @@ sealed class TetheringTileService : IpNeighbourMonitoringTileService(), Tetherin
override fun start() = BluetoothTethering.start(this)
override fun stop() {
TetheringManager.stopTethering(TetheringManager.TETHERING_BLUETOOTH)
TetheringManager.stopTethering(TetheringManager.TETHERING_BLUETOOTH, this::onException)
Thread.sleep(1) // give others a room to breathe
onTetheringStarted() // force flush state
}
@@ -202,12 +198,15 @@ sealed class TetheringTileService : IpNeighbourMonitoringTileService(), Tetherin
val binder = binder
if (binder == null) tapPending = true else {
val inactive = (interested ?: return).filterNot(binder::isActive)
if (inactive.isEmpty()) safeInvoker { stop() }
else ContextCompat.startForegroundService(this, Intent(this, TetheringService::class.java)
if (inactive.isEmpty()) try {
stop()
} catch (e: Exception) {
onException(e)
} else ContextCompat.startForegroundService(this, Intent(this, TetheringService::class.java)
.putExtra(TetheringService.EXTRA_ADD_INTERFACES, inactive.toTypedArray()))
}
}
false -> safeInvoker { start() }
false -> start()
else -> tapPending = true
}
}
@@ -218,7 +217,7 @@ sealed class TetheringTileService : IpNeighbourMonitoringTileService(), Tetherin
override val tetherType get() = TetherType.ETHERNET
override fun start() = TetheringManager.startTethering(TetheringManager.TETHERING_ETHERNET, true, this)
override fun stop() = TetheringManager.stopTethering(TetheringManager.TETHERING_ETHERNET)
override fun stop() = TetheringManager.stopTethering(TetheringManager.TETHERING_ETHERNET, this::onException)
}
@RequiresApi(30)
class Ncm : TetheringTileService() {
@@ -226,7 +225,7 @@ sealed class TetheringTileService : IpNeighbourMonitoringTileService(), Tetherin
override val tetherType get() = TetherType.NCM
override fun start() = TetheringManager.startTethering(TetheringManager.TETHERING_NCM, true, this)
override fun stop() = TetheringManager.stopTethering(TetheringManager.TETHERING_NCM)
override fun stop() = TetheringManager.stopTethering(TetheringManager.TETHERING_NCM, this::onException)
}
@Suppress("DEPRECATION")