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

@@ -15,11 +15,19 @@ import androidx.annotation.RequiresApi
import androidx.collection.SparseArrayCompat
import be.mygod.vpnhotspot.App.Companion.app
import be.mygod.vpnhotspot.R
import be.mygod.vpnhotspot.root.RootManager
import be.mygod.vpnhotspot.root.StartTethering
import be.mygod.vpnhotspot.root.StopTethering
import be.mygod.vpnhotspot.util.Services
import be.mygod.vpnhotspot.util.broadcastReceiver
import be.mygod.vpnhotspot.util.callSuper
import be.mygod.vpnhotspot.util.ensureReceiverUnregistered
import com.android.dx.stock.ProxyBuilder
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import timber.log.Timber
import java.io.File
import java.lang.ref.WeakReference
import java.lang.reflect.InvocationHandler
import java.lang.reflect.InvocationTargetException
@@ -49,7 +57,10 @@ object TetheringManager {
*/
fun onTetheringFailed(error: Int? = null) { }
fun onException() { }
/**
* ADDED: Called when a local Exception occurred.
*/
fun onException(e: Exception) { }
}
/**
@@ -130,7 +141,6 @@ object TetheringManager {
/**
* Ncm local tethering type.
*
* Requires NETWORK_SETTINGS permission, which is sadly not obtainable.
* @see [startTethering]
*/
@RequiresApi(30)
@@ -149,7 +159,7 @@ object TetheringManager {
@get:RequiresApi(30)
private val instance by lazy @TargetApi(30) {
@SuppressLint("WrongConstant") // hidden services are not included in constants as of R preview 4
val service = app.getSystemService(TETHERING_SERVICE)
val service = Services.context.getSystemService(TETHERING_SERVICE)
service
}
@get:RequiresApi(30)
@@ -211,12 +221,64 @@ object TetheringManager {
private fun Handler?.makeExecutor() = Executor { if (this == null) it.run() else post(it) }
@Deprecated("Legacy API")
@RequiresApi(24)
fun startTetheringLegacy(type: Int, showProvisioningUi: Boolean, callback: StartTetheringCallback,
handler: Handler? = null, cacheDir: File = app.deviceStorage.codeCacheDir) {
val reference = WeakReference(callback)
val proxy = ProxyBuilder.forClass(classOnStartTetheringCallback).apply {
dexCache(cacheDir)
handler { proxy, method, args ->
if (args.isNotEmpty()) Timber.w("Unexpected args for ${method.name}: $args")
@Suppress("NAME_SHADOWING") val callback = reference.get()
when (method.name) {
"onTetheringStarted" -> callback?.onTetheringStarted()
"onTetheringFailed" -> callback?.onTetheringFailed()
else -> ProxyBuilder.callSuper(proxy, method, args)
}
}
}.build()
startTetheringLegacy(Services.connectivity, type, showProvisioningUi, proxy, handler)
}
@RequiresApi(30)
fun startTethering(type: Int, exemptFromEntitlementCheck: Boolean, showProvisioningUi: Boolean, executor: Executor,
proxy: Any) {
startTethering(instance, newTetheringRequestBuilder.newInstance(type).let { builder ->
// setting exemption requires TETHER_PRIVILEGED permission
if (exemptFromEntitlementCheck) setExemptFromEntitlementCheck(builder, true)
setShouldShowEntitlementUi(builder, showProvisioningUi)
build(builder)
}, executor, proxy)
}
@RequiresApi(30)
fun proxy(callback: StartTetheringCallback): Any {
val reference = WeakReference(callback)
return Proxy.newProxyInstance(interfaceStartTetheringCallback.classLoader,
arrayOf(interfaceStartTetheringCallback), object : InvocationHandler {
override fun invoke(proxy: Any, method: Method, args: Array<out Any?>?): Any? {
@Suppress("NAME_SHADOWING") val callback = reference.get()
return when (val name = method.name) {
"onTetheringStarted" -> {
if (!args.isNullOrEmpty()) Timber.w("Unexpected args for $name: $args")
callback?.onTetheringStarted()
}
"onTetheringFailed" -> {
if (args?.size != 1) Timber.w("Unexpected args for $name: $args")
callback?.onTetheringFailed(args?.get(0) as Int)
}
else -> callSuper(interfaceStartTetheringCallback, proxy, method, args)
}
}
})
}
/**
* Runs tether provisioning for the given type if needed and then starts tethering if
* the check succeeds. If no carrier provisioning is required for tethering, tethering is
* enabled immediately. If provisioning fails, tethering will not be enabled. It also
* schedules tether provisioning re-checks if appropriate.
*
* CHANGED BEHAVIOR: This method will not throw Exceptions, instead, callback.onException will be called.
*
* @param type The type of tethering to start. Must be one of
* {@link ConnectivityManager.TETHERING_WIFI},
* {@link ConnectivityManager.TETHERING_USB}, or
@@ -234,48 +296,57 @@ object TetheringManager {
*/
@RequiresApi(24)
fun startTethering(type: Int, showProvisioningUi: Boolean, callback: StartTetheringCallback,
handler: Handler? = null) {
val reference = WeakReference(callback)
if (Build.VERSION.SDK_INT >= 30) {
val request = newTetheringRequestBuilder.newInstance(type).let { builder ->
// setting exemption requires TETHER_PRIVILEGED permission
if (app.checkSelfPermission("android.permission.TETHER_PRIVILEGED") ==
PackageManager.PERMISSION_GRANTED) setExemptFromEntitlementCheck(builder, true)
setShouldShowEntitlementUi(builder, showProvisioningUi)
build(builder)
handler: Handler? = null, cacheDir: File = app.deviceStorage.codeCacheDir) {
if (Build.VERSION.SDK_INT >= 30) try {
val proxy = proxy(callback)
val executor = handler.makeExecutor()
try {
startTethering(type, true, showProvisioningUi, executor, proxy)
} catch (e1: InvocationTargetException) {
if (e1.targetException is SecurityException) GlobalScope.launch(Dispatchers.Unconfined) {
val result = try {
RootManager.use { it.execute(StartTethering(type, showProvisioningUi)) }
} catch (e2: Exception) {
e2.addSuppressed(e1)
try {
// last resort: start tethering without trying to bypass entitlement check
startTethering(type, false, showProvisioningUi, executor, proxy)
Timber.w(e2)
} catch (e3: Exception) {
e3.addSuppressed(e2)
Timber.w(e3)
callback.onException(e3)
}
return@launch
}
if (result == null) callback.onTetheringStarted()
else callback.onTetheringFailed(result.value)
} else callback.onException(e1)
}
val proxy = Proxy.newProxyInstance(interfaceStartTetheringCallback.classLoader,
arrayOf(interfaceStartTetheringCallback), object : InvocationHandler {
override fun invoke(proxy: Any, method: Method, args: Array<out Any?>?): Any? {
@Suppress("NAME_SHADOWING") val callback = reference.get()
return when (val name = method.name) {
"onTetheringStarted" -> {
if (!args.isNullOrEmpty()) Timber.w("Unexpected args for $name: $args")
callback?.onTetheringStarted()
}
"onTetheringFailed" -> {
if (args?.size != 1) Timber.w("Unexpected args for $name: $args")
callback?.onTetheringFailed(args?.getOrNull(0) as? Int?)
}
else -> callSuper(interfaceStartTetheringCallback, proxy, method, args)
}
} catch (e: Exception) {
callback.onException(e)
} else @Suppress("DEPRECATION") try {
startTetheringLegacy(type, showProvisioningUi, callback, handler, cacheDir)
} catch (e: InvocationTargetException) {
if (e.targetException is SecurityException) GlobalScope.launch(Dispatchers.Unconfined) {
val result = try {
val rootCache = File(cacheDir, "root")
rootCache.mkdirs()
check(rootCache.exists()) { "Creating root cache dir failed" }
RootManager.use {
it.execute(be.mygod.vpnhotspot.root.StartTetheringLegacy(
rootCache, type, showProvisioningUi))
}.value
} catch (eRoot: Exception) {
eRoot.addSuppressed(e)
Timber.w(eRoot)
callback.onException(eRoot)
return@launch
}
})
startTethering(instance, request, handler.makeExecutor(), proxy)
} else {
val proxy = ProxyBuilder.forClass(classOnStartTetheringCallback).apply {
dexCache(app.deviceStorage.cacheDir)
handler { proxy, method, args ->
if (args.isNotEmpty()) Timber.w("Unexpected args for ${method.name}: $args")
@Suppress("NAME_SHADOWING") val callback = reference.get()
when (method.name) {
"onTetheringStarted" -> callback?.onTetheringStarted()
"onTetheringFailed" -> callback?.onTetheringFailed()
else -> ProxyBuilder.callSuper(proxy, method, args)
}
}
}.build()
startTetheringLegacy(app.connectivity, type, showProvisioningUi, proxy, handler)
if (result) callback.onTetheringStarted() else callback.onTetheringFailed()
} else callback.onException(e)
} catch (e: Exception) {
callback.onException(e)
}
}
@@ -290,7 +361,21 @@ object TetheringManager {
*/
@RequiresApi(24)
fun stopTethering(type: Int) {
if (Build.VERSION.SDK_INT >= 30) stopTethering(instance, type) else stopTetheringLegacy(app.connectivity, type)
if (Build.VERSION.SDK_INT >= 30) stopTethering(instance, type)
else stopTetheringLegacy(Services.connectivity, type)
}
@RequiresApi(24)
fun stopTethering(type: Int, callback: (Exception) -> Unit) = try {
stopTethering(type)
} catch (e: InvocationTargetException) {
if (e.targetException is SecurityException) GlobalScope.launch(Dispatchers.Unconfined) {
try {
RootManager.use { it.execute(StopTethering(type)) }
} catch (eRoot: Exception) {
eRoot.addSuppressed(e)
callback(eRoot)
}
} else callback(e)
}
/**
@@ -517,7 +602,7 @@ object TetheringManager {
* @return error The error code of the last error tethering or untethering the named
* interface
*/
fun getLastTetherError(iface: String): Int = getLastTetherError(app.connectivity, iface) as Int
fun getLastTetherError(iface: String): Int = getLastTetherError(Services.connectivity, iface) as Int
// tether errors defined in ConnectivityManager up to Android 10
private val tetherErrors29 = arrayOf("TETHER_ERROR_NO_ERROR", "TETHER_ERROR_UNKNOWN_IFACE",