Files
vpnhotspotmod/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetherManager.kt
2018-08-08 15:12:48 +08:00

209 lines
8.9 KiB
Kotlin

package be.mygod.vpnhotspot.manage
import android.annotation.SuppressLint
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothProfile
import android.content.ActivityNotFoundException
import android.content.Intent
import android.os.Build
import android.provider.Settings
import android.view.View
import androidx.annotation.RequiresApi
import androidx.core.net.toUri
import androidx.core.view.updatePaddingRelative
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.OnLifecycleEvent
import androidx.recyclerview.widget.RecyclerView
import be.mygod.vpnhotspot.App.Companion.app
import be.mygod.vpnhotspot.MainActivity
import be.mygod.vpnhotspot.R
import be.mygod.vpnhotspot.databinding.ListitemInterfaceBinding
import be.mygod.vpnhotspot.net.TetherType
import be.mygod.vpnhotspot.net.TetheringManager
import be.mygod.vpnhotspot.net.wifi.WifiApManager
import com.crashlytics.android.Crashlytics
import java.lang.reflect.InvocationTargetException
sealed class TetherManager(protected val parent: TetheringFragment) : Manager(),
TetheringManager.OnStartTetheringCallback {
class ViewHolder(val binding: ListitemInterfaceBinding) : RecyclerView.ViewHolder(binding.root),
View.OnClickListener {
init {
itemView.updatePaddingRelative(start = itemView.resources.getDimensionPixelOffset(
R.dimen.listitem_manage_tether_padding_start))
itemView.setOnClickListener(this)
}
var manager: TetherManager? = null
set(value) {
field = value!!
binding.data = value.data
}
override fun onClick(v: View?) {
val manager = manager!!
val mainActivity = manager.parent.activity as MainActivity
if (Build.VERSION.SDK_INT >= 23 && !Settings.System.canWrite(mainActivity)) try {
manager.parent.startActivity(Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS,
"package:${mainActivity.packageName}".toUri()))
return
} catch (exc: ActivityNotFoundException) {
exc.printStackTrace()
Crashlytics.logException(exc)
}
val started = manager.isStarted
try {
if (started) manager.stop() else manager.start()
} catch (e: InvocationTargetException) {
e.printStackTrace()
Crashlytics.logException(e)
var cause: Throwable? = e
while (cause != null) {
cause = cause.cause
if (cause != null && cause !is InvocationTargetException) {
mainActivity.snackbar(cause.message.toString()).show()
ManageBar.start(itemView.context)
break
}
}
}
}
}
/**
* A convenient class to delegate stuff to BaseObservable.
*/
inner class Data : be.mygod.vpnhotspot.manage.Data() {
override val icon get() = tetherType.icon
override val title get() = this@TetherManager.title
override var text: CharSequence = ""
override val active get() = isStarted
}
val data = Data()
abstract val title: CharSequence
abstract val tetherType: TetherType
open val isStarted get() = parent.enabledTypes.contains(tetherType)
protected abstract fun start()
protected abstract fun stop()
override fun onTetheringStarted() = data.notifyChange()
override fun onTetheringFailed() =
(parent.activity as MainActivity).snackbar().setText(R.string.tethering_manage_failed).show()
override fun bindTo(viewHolder: RecyclerView.ViewHolder) {
(viewHolder as ViewHolder).manager = this
}
fun updateErrorMessage(errored: List<String>) {
data.text = errored.filter { TetherType.ofInterface(it) == tetherType }.joinToString("\n") {
"$it: " + try {
val error = TetheringManager.getLastTetherError(it)
when (error) {
TetheringManager.TETHER_ERROR_NO_ERROR -> "TETHER_ERROR_NO_ERROR"
TetheringManager.TETHER_ERROR_UNKNOWN_IFACE -> "TETHER_ERROR_UNKNOWN_IFACE"
TetheringManager.TETHER_ERROR_SERVICE_UNAVAIL -> "TETHER_ERROR_SERVICE_UNAVAIL"
TetheringManager.TETHER_ERROR_UNSUPPORTED -> "TETHER_ERROR_UNSUPPORTED"
TetheringManager.TETHER_ERROR_UNAVAIL_IFACE -> "TETHER_ERROR_UNAVAIL_IFACE"
TetheringManager.TETHER_ERROR_MASTER_ERROR -> "TETHER_ERROR_MASTER_ERROR"
TetheringManager.TETHER_ERROR_TETHER_IFACE_ERROR -> "TETHER_ERROR_TETHER_IFACE_ERROR"
TetheringManager.TETHER_ERROR_UNTETHER_IFACE_ERROR -> "TETHER_ERROR_UNTETHER_IFACE_ERROR"
TetheringManager.TETHER_ERROR_ENABLE_NAT_ERROR -> "TETHER_ERROR_ENABLE_NAT_ERROR"
TetheringManager.TETHER_ERROR_DISABLE_NAT_ERROR -> "TETHER_ERROR_DISABLE_NAT_ERROR"
TetheringManager.TETHER_ERROR_IFACE_CFG_ERROR -> "TETHER_ERROR_IFACE_CFG_ERROR"
TetheringManager.TETHER_ERROR_PROVISION_FAILED -> "TETHER_ERROR_PROVISION_FAILED"
else -> app.getString(R.string.failure_reason_unknown, error)
}
} catch (e: SecurityException) {
Crashlytics.logException(e)
e.localizedMessage
}
}
data.notifyChange()
}
@RequiresApi(24)
class Wifi(parent: TetheringFragment) : TetherManager(parent) {
override val title get() = parent.getString(R.string.tethering_manage_wifi)
override val tetherType get() = TetherType.WIFI
override val type get() = VIEW_TYPE_WIFI
override fun start() = TetheringManager.start(TetheringManager.TETHERING_WIFI, true, this)
override fun stop() = TetheringManager.stop(TetheringManager.TETHERING_WIFI)
}
@RequiresApi(24)
class Usb(parent: TetheringFragment) : TetherManager(parent) {
override val title get() = parent.getString(R.string.tethering_manage_usb)
override val tetherType get() = TetherType.USB
override val type get() = VIEW_TYPE_USB
override fun start() = TetheringManager.start(TetheringManager.TETHERING_USB, true, this)
override fun stop() = TetheringManager.stop(TetheringManager.TETHERING_USB)
}
@RequiresApi(24)
class Bluetooth(parent: TetheringFragment) : TetherManager(parent), LifecycleObserver,
BluetoothProfile.ServiceListener {
companion object {
/**
* PAN Profile
* From BluetoothProfile.java.
*/
private const val PAN = 5
private val isTetheringOn by lazy @SuppressLint("PrivateApi") {
Class.forName("android.bluetooth.BluetoothPan").getDeclaredMethod("isTetheringOn")
}
}
private var pan: BluetoothProfile? = null
init {
parent.lifecycle.addObserver(this)
BluetoothAdapter.getDefaultAdapter()?.getProfileProxy(parent.requireContext(), this, PAN)
}
override fun onServiceDisconnected(profile: Int) {
pan = null
}
override fun onServiceConnected(profile: Int, proxy: BluetoothProfile) {
pan = proxy
}
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
fun onDestroy() {
pan = null
}
override val title get() = parent.getString(R.string.tethering_manage_bluetooth)
override val tetherType get() = TetherType.BLUETOOTH
override val type get() = VIEW_TYPE_BLUETOOTH
/**
* Based on: https://android.googlesource.com/platform/packages/apps/Settings/+/78d5efd/src/com/android/settings/TetherSettings.java
*/
override val isStarted: Boolean
get() {
val pan = pan
return BluetoothAdapter.getDefaultAdapter()?.state == BluetoothAdapter.STATE_ON && pan != null &&
isTetheringOn.invoke(pan) as Boolean
}
override fun start() = TetheringManager.start(TetheringManager.TETHERING_BLUETOOTH, true, this)
override fun stop() {
TetheringManager.stop(TetheringManager.TETHERING_BLUETOOTH)
Thread.sleep(1) // give others a room to breathe
onTetheringStarted() // force flush state
}
}
@Suppress("DEPRECATION")
@Deprecated("Not usable since API 26")
class WifiLegacy(parent: TetheringFragment) : TetherManager(parent) {
override val title get() = parent.getString(R.string.tethering_manage_wifi_legacy)
override val tetherType get() = TetherType.WIFI
override val type get() = VIEW_TYPE_WIFI_LEGACY
override fun start() = WifiApManager.start()
override fun stop() = WifiApManager.stop()
}
}