Implement new startTethering API
This commit is contained in:
@@ -35,7 +35,7 @@ class BluetoothTethering(context: Context, val stateListener: (Int) -> Unit) :
|
|||||||
app.registerReceiver(receiver, IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED))
|
app.registerReceiver(receiver, IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED))
|
||||||
private val Intent.bluetoothState get() = getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR)
|
private val Intent.bluetoothState get() = getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR)
|
||||||
|
|
||||||
private var pendingCallback: TetheringManager.OnStartTetheringCallback? = null
|
private var pendingCallback: TetheringManager.StartTetheringCallback? = null
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* https://android.googlesource.com/platform/packages/apps/Settings/+/b1af85d/src/com/android/settings/TetherSettings.java#215
|
* https://android.googlesource.com/platform/packages/apps/Settings/+/b1af85d/src/com/android/settings/TetherSettings.java#215
|
||||||
@@ -72,7 +72,7 @@ class BluetoothTethering(context: Context, val stateListener: (Int) -> Unit) :
|
|||||||
* https://android.googlesource.com/platform/packages/apps/Settings/+/b1af85d/src/com/android/settings/TetherSettings.java#384
|
* https://android.googlesource.com/platform/packages/apps/Settings/+/b1af85d/src/com/android/settings/TetherSettings.java#384
|
||||||
*/
|
*/
|
||||||
@RequiresApi(24)
|
@RequiresApi(24)
|
||||||
fun start(callback: TetheringManager.OnStartTetheringCallback) {
|
fun start(callback: TetheringManager.StartTetheringCallback) {
|
||||||
if (pendingCallback != null) return
|
if (pendingCallback != null) return
|
||||||
val adapter = BluetoothAdapter.getDefaultAdapter()
|
val adapter = BluetoothAdapter.getDefaultAdapter()
|
||||||
if (adapter?.state == BluetoothAdapter.STATE_OFF) {
|
if (adapter?.state == BluetoothAdapter.STATE_OFF) {
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import android.view.View
|
|||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import androidx.core.os.BuildCompat
|
|
||||||
import androidx.core.view.updatePaddingRelative
|
import androidx.core.view.updatePaddingRelative
|
||||||
import androidx.lifecycle.DefaultLifecycleObserver
|
import androidx.lifecycle.DefaultLifecycleObserver
|
||||||
import androidx.lifecycle.LifecycleOwner
|
import androidx.lifecycle.LifecycleOwner
|
||||||
@@ -20,12 +19,13 @@ import be.mygod.vpnhotspot.net.TetherType
|
|||||||
import be.mygod.vpnhotspot.net.TetheringManager
|
import be.mygod.vpnhotspot.net.TetheringManager
|
||||||
import be.mygod.vpnhotspot.net.wifi.WifiApManager
|
import be.mygod.vpnhotspot.net.wifi.WifiApManager
|
||||||
import be.mygod.vpnhotspot.util.readableMessage
|
import be.mygod.vpnhotspot.util.readableMessage
|
||||||
|
import be.mygod.vpnhotspot.widget.SmartSnackbar
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.lang.reflect.InvocationTargetException
|
import java.lang.reflect.InvocationTargetException
|
||||||
|
|
||||||
sealed class TetherManager(protected val parent: TetheringFragment) : Manager(),
|
sealed class TetherManager(protected val parent: TetheringFragment) : Manager(),
|
||||||
TetheringManager.OnStartTetheringCallback {
|
TetheringManager.StartTetheringCallback {
|
||||||
class ViewHolder(private val binding: ListitemInterfaceBinding) : RecyclerView.ViewHolder(binding.root),
|
class ViewHolder(private val binding: ListitemInterfaceBinding) : RecyclerView.ViewHolder(binding.root),
|
||||||
View.OnClickListener {
|
View.OnClickListener {
|
||||||
init {
|
init {
|
||||||
@@ -91,8 +91,9 @@ sealed class TetherManager(protected val parent: TetheringFragment) : Manager(),
|
|||||||
protected abstract fun stop()
|
protected abstract fun stop()
|
||||||
|
|
||||||
override fun onTetheringStarted() = data.notifyChange()
|
override fun onTetheringStarted() = data.notifyChange()
|
||||||
override fun onTetheringFailed() {
|
override fun onTetheringFailed(error: Int?) {
|
||||||
Timber.d(javaClass.simpleName, "onTetheringFailed")
|
Timber.d(javaClass.simpleName, "onTetheringFailed: $error")
|
||||||
|
error?.let { SmartSnackbar.make("$tetherType: ${TetheringManager.tetherErrorMessage(it)}") }
|
||||||
data.notifyChange()
|
data.notifyChange()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,34 +102,12 @@ sealed class TetherManager(protected val parent: TetheringFragment) : Manager(),
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun getErrorMessage(iface: String): String {
|
private fun getErrorMessage(iface: String): String {
|
||||||
val error = try {
|
return TetheringManager.tetherErrorMessage(try {
|
||||||
TetheringManager.getLastTetherError(iface)
|
TetheringManager.getLastTetherError(iface)
|
||||||
} catch (e: InvocationTargetException) {
|
} catch (e: InvocationTargetException) {
|
||||||
if (Build.VERSION.SDK_INT !in 24..25 || e.cause !is SecurityException) Timber.w(e) else Timber.d(e)
|
if (Build.VERSION.SDK_INT !in 24..25 || e.cause !is SecurityException) Timber.w(e) else Timber.d(e)
|
||||||
return e.readableMessage
|
return e.readableMessage
|
||||||
}
|
})
|
||||||
if (BuildCompat.isAtLeastR()) try {
|
|
||||||
TetheringManager.tetherErrors.get(error)?.let { return it }
|
|
||||||
} catch (e: ReflectiveOperationException) {
|
|
||||||
Timber.w(e)
|
|
||||||
}
|
|
||||||
return 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"
|
|
||||||
TetheringManager.TETHER_ERROR_DHCPSERVER_ERROR -> "TETHER_ERROR_DHCPSERVER_ERROR"
|
|
||||||
TetheringManager.TETHER_ERROR_ENTITLEMENT_UNKNOWN -> "TETHER_ERROR_ENTITLEMENT_UNKNOWN"
|
|
||||||
else -> app.getString(R.string.failure_reason_unknown, error)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
fun updateErrorMessage(errored: List<String>) {
|
fun updateErrorMessage(errored: List<String>) {
|
||||||
data.text = errored.filter { TetherType.ofInterface(it) == tetherType }
|
data.text = errored.filter { TetherType.ofInterface(it) == tetherType }
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import java.io.IOException
|
|||||||
import java.lang.reflect.InvocationTargetException
|
import java.lang.reflect.InvocationTargetException
|
||||||
|
|
||||||
@RequiresApi(24)
|
@RequiresApi(24)
|
||||||
sealed class TetheringTileService : TetherListeningTileService(), TetheringManager.OnStartTetheringCallback {
|
sealed class TetheringTileService : TetherListeningTileService(), TetheringManager.StartTetheringCallback {
|
||||||
protected val tileOff by lazy { Icon.createWithResource(application, icon) }
|
protected val tileOff by lazy { Icon.createWithResource(application, icon) }
|
||||||
protected val tileOn by lazy { Icon.createWithResource(application, R.drawable.ic_quick_settings_tile_on) }
|
protected val tileOn by lazy { Icon.createWithResource(application, R.drawable.ic_quick_settings_tile_on) }
|
||||||
|
|
||||||
@@ -108,8 +108,9 @@ sealed class TetheringTileService : TetherListeningTileService(), TetheringManag
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onTetheringStarted() = updateTile()
|
override fun onTetheringStarted() = updateTile()
|
||||||
override fun onTetheringFailed() {
|
override fun onTetheringFailed(error: Int?) {
|
||||||
Timber.d("onTetheringFailed")
|
Timber.d("onTetheringFailed: $error")
|
||||||
|
error?.let { Toast.makeText(this, TetheringManager.tetherErrorMessage(it), Toast.LENGTH_LONG).show() }
|
||||||
updateTile()
|
updateTile()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,21 @@
|
|||||||
package be.mygod.vpnhotspot.net
|
package be.mygod.vpnhotspot.net
|
||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.content.pm.PackageManager
|
||||||
import android.net.ConnectivityManager
|
import android.net.ConnectivityManager
|
||||||
|
import android.net.LinkAddress
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.collection.SparseArrayCompat
|
import androidx.collection.SparseArrayCompat
|
||||||
import androidx.core.os.BuildCompat
|
import androidx.core.os.BuildCompat
|
||||||
import be.mygod.vpnhotspot.App.Companion.app
|
import be.mygod.vpnhotspot.App.Companion.app
|
||||||
|
import be.mygod.vpnhotspot.R
|
||||||
import com.android.dx.stock.ProxyBuilder
|
import com.android.dx.stock.ProxyBuilder
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.lang.ref.WeakReference
|
import java.lang.ref.WeakReference
|
||||||
|
import java.lang.reflect.InvocationTargetException
|
||||||
|
import java.util.concurrent.Executor
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Heavily based on:
|
* Heavily based on:
|
||||||
@@ -21,7 +26,7 @@ object TetheringManager {
|
|||||||
/**
|
/**
|
||||||
* Callback for use with [startTethering] to find out whether tethering succeeded.
|
* Callback for use with [startTethering] to find out whether tethering succeeded.
|
||||||
*/
|
*/
|
||||||
interface OnStartTetheringCallback {
|
interface StartTetheringCallback {
|
||||||
/**
|
/**
|
||||||
* Called when tethering has been successfully started.
|
* Called when tethering has been successfully started.
|
||||||
*/
|
*/
|
||||||
@@ -29,12 +34,22 @@ object TetheringManager {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when starting tethering failed.
|
* Called when starting tethering failed.
|
||||||
|
*
|
||||||
|
* @param error The error that caused the failure.
|
||||||
*/
|
*/
|
||||||
fun onTetheringFailed() { }
|
fun onTetheringFailed(error: Int? = null) { }
|
||||||
|
|
||||||
fun onException() { }
|
fun onException() { }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use with {@link #getSystemService(String)} to retrieve a {@link android.net.TetheringManager}
|
||||||
|
* for managing tethering functions.
|
||||||
|
* @hide
|
||||||
|
* @see android.net.TetheringManager
|
||||||
|
*/
|
||||||
|
const val TETHERING_SERVICE = "tethering"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is a sticky broadcast since almost forever.
|
* This is a sticky broadcast since almost forever.
|
||||||
*
|
*
|
||||||
@@ -62,59 +77,95 @@ object TetheringManager {
|
|||||||
* for any interfaces listed here.
|
* for any interfaces listed here.
|
||||||
*/
|
*/
|
||||||
const val EXTRA_ERRORED_TETHER = "erroredArray"
|
const val EXTRA_ERRORED_TETHER = "erroredArray"
|
||||||
const val TETHER_ERROR_NO_ERROR = 0
|
|
||||||
const val TETHER_ERROR_UNKNOWN_IFACE = 1
|
|
||||||
const val TETHER_ERROR_SERVICE_UNAVAIL = 2
|
|
||||||
const val TETHER_ERROR_UNSUPPORTED = 3
|
|
||||||
const val TETHER_ERROR_UNAVAIL_IFACE = 4
|
|
||||||
const val TETHER_ERROR_MASTER_ERROR = 5
|
|
||||||
const val TETHER_ERROR_TETHER_IFACE_ERROR = 6
|
|
||||||
const val TETHER_ERROR_UNTETHER_IFACE_ERROR = 7
|
|
||||||
const val TETHER_ERROR_ENABLE_NAT_ERROR = 8
|
|
||||||
const val TETHER_ERROR_DISABLE_NAT_ERROR = 9
|
|
||||||
const val TETHER_ERROR_IFACE_CFG_ERROR = 10
|
|
||||||
const val TETHER_ERROR_PROVISION_FAILED = 11
|
|
||||||
const val TETHER_ERROR_DHCPSERVER_ERROR = 12
|
|
||||||
const val TETHER_ERROR_ENTITLEMENT_UNKNOWN = 13
|
|
||||||
|
|
||||||
|
// tether errors defined in ConnectivityManager up to Android 10
|
||||||
|
private const val TETHER_ERROR_NO_ERROR = 0
|
||||||
|
private const val TETHER_ERROR_UNKNOWN_IFACE = 1
|
||||||
|
private const val TETHER_ERROR_SERVICE_UNAVAIL = 2
|
||||||
|
private const val TETHER_ERROR_UNSUPPORTED = 3
|
||||||
|
private const val TETHER_ERROR_UNAVAIL_IFACE = 4
|
||||||
|
private const val TETHER_ERROR_MASTER_ERROR = 5
|
||||||
|
private const val TETHER_ERROR_TETHER_IFACE_ERROR = 6
|
||||||
|
private const val TETHER_ERROR_UNTETHER_IFACE_ERROR = 7
|
||||||
|
private const val TETHER_ERROR_ENABLE_NAT_ERROR = 8
|
||||||
|
private const val TETHER_ERROR_DISABLE_NAT_ERROR = 9
|
||||||
|
private const val TETHER_ERROR_IFACE_CFG_ERROR = 10
|
||||||
|
private const val TETHER_ERROR_PROVISION_FAILED = 11
|
||||||
|
private const val TETHER_ERROR_DHCPSERVER_ERROR = 12
|
||||||
|
private const val TETHER_ERROR_ENTITLEMENT_UNKNOWN = 13
|
||||||
|
|
||||||
|
@RequiresApi(24)
|
||||||
const val TETHERING_WIFI = 0
|
const val TETHERING_WIFI = 0
|
||||||
/**
|
/**
|
||||||
* Requires MANAGE_USB permission, unfortunately.
|
* Requires MANAGE_USB permission, unfortunately.
|
||||||
*
|
*
|
||||||
* Source: https://android.googlesource.com/platform/frameworks/base/+/7ca5d3a/services/usb/java/com/android/server/usb/UsbService.java#389
|
* Source: https://android.googlesource.com/platform/frameworks/base/+/7ca5d3a/services/usb/java/com/android/server/usb/UsbService.java#389
|
||||||
*/
|
*/
|
||||||
|
@RequiresApi(24)
|
||||||
const val TETHERING_USB = 1
|
const val TETHERING_USB = 1
|
||||||
/**
|
/**
|
||||||
* Requires BLUETOOTH permission, or BLUETOOTH_PRIVILEGED on API 30+.
|
* Requires BLUETOOTH permission, or BLUETOOTH_PRIVILEGED on API 30+.
|
||||||
*/
|
*/
|
||||||
|
@RequiresApi(24)
|
||||||
const val TETHERING_BLUETOOTH = 2
|
const val TETHERING_BLUETOOTH = 2
|
||||||
|
|
||||||
@get:RequiresApi(30)
|
@get:RequiresApi(30)
|
||||||
val tetherErrors by lazy {
|
private val clazz by lazy { Class.forName("android.net.TetheringManager") }
|
||||||
SparseArrayCompat<String>().apply {
|
@get:RequiresApi(30)
|
||||||
for (field in Class.forName("android.net.TetheringManager").declaredFields) try {
|
private val instance by lazy { app.getSystemService(TETHERING_SERVICE) }
|
||||||
// all TETHER_ERROR_* are system-api since API 30
|
|
||||||
if (field.name.startsWith("TETHER_ERROR_")) put(field.get(null) as Int, field.name)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Timber.w(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
@get:RequiresApi(24)
|
||||||
private val classOnStartTetheringCallback by lazy {
|
private val classOnStartTetheringCallback by lazy {
|
||||||
Class.forName("android.net.ConnectivityManager\$OnStartTetheringCallback")
|
Class.forName("android.net.ConnectivityManager\$OnStartTetheringCallback")
|
||||||
}
|
}
|
||||||
private val startTethering by lazy {
|
@get:RequiresApi(24)
|
||||||
|
private val startTetheringLegacy by lazy {
|
||||||
ConnectivityManager::class.java.getDeclaredMethod("startTethering",
|
ConnectivityManager::class.java.getDeclaredMethod("startTethering",
|
||||||
Int::class.java, Boolean::class.java, classOnStartTetheringCallback, Handler::class.java)
|
Int::class.java, Boolean::class.java, classOnStartTetheringCallback, Handler::class.java)
|
||||||
}
|
}
|
||||||
private val stopTethering by lazy {
|
@get:RequiresApi(24)
|
||||||
|
private val stopTetheringLegacy by lazy {
|
||||||
ConnectivityManager::class.java.getDeclaredMethod("stopTethering", Int::class.java)
|
ConnectivityManager::class.java.getDeclaredMethod("stopTethering", Int::class.java)
|
||||||
}
|
}
|
||||||
|
@get:RequiresApi(24)
|
||||||
private val getLastTetherError by lazy {
|
private val getLastTetherError by lazy {
|
||||||
ConnectivityManager::class.java.getDeclaredMethod("getLastTetherError", String::class.java)
|
ConnectivityManager::class.java.getDeclaredMethod("getLastTetherError", String::class.java)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@get:RequiresApi(30)
|
||||||
|
private val classTetheringRequestBuilder by lazy {
|
||||||
|
Class.forName("android.net.TetheringManager\$TetheringRequest\$Builder")
|
||||||
|
}
|
||||||
|
@get:RequiresApi(30)
|
||||||
|
private val newTetheringRequestBuilder by lazy { classTetheringRequestBuilder.getConstructor(Int::class.java) }
|
||||||
|
@get:RequiresApi(30)
|
||||||
|
private val setStaticIpv4Addresses by lazy {
|
||||||
|
classTetheringRequestBuilder.getDeclaredMethod("setStaticIpv4Addresses",
|
||||||
|
LinkAddress::class.java, LinkAddress::class.java)
|
||||||
|
}
|
||||||
|
@get:RequiresApi(30)
|
||||||
|
private val setExemptFromEntitlementCheck by lazy {
|
||||||
|
classTetheringRequestBuilder.getDeclaredMethod("setExemptFromEntitlementCheck", Boolean::class.java)
|
||||||
|
}
|
||||||
|
@get:RequiresApi(30)
|
||||||
|
private val setShouldShowEntitlementUi by lazy {
|
||||||
|
classTetheringRequestBuilder.getDeclaredMethod("setShouldShowEntitlementUi", Boolean::class.java)
|
||||||
|
}
|
||||||
|
@get:RequiresApi(30)
|
||||||
|
private val build by lazy { classTetheringRequestBuilder.getDeclaredMethod("build") }
|
||||||
|
|
||||||
|
@get:RequiresApi(30)
|
||||||
|
private val classStartTetheringCallback by lazy {
|
||||||
|
Class.forName("android.net.TetheringManager\$StartTetheringCallback")
|
||||||
|
}
|
||||||
|
@get:RequiresApi(30)
|
||||||
|
private val startTethering by lazy {
|
||||||
|
clazz.getDeclaredMethod("startTethering", Class.forName("android.net.TetheringManager\$TetheringRequest"),
|
||||||
|
Executor::class.java, classStartTetheringCallback)
|
||||||
|
}
|
||||||
|
@get:RequiresApi(30)
|
||||||
|
private val stopTethering by lazy { clazz.getDeclaredMethod("stopTethering", Int::class.java) }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Runs tether provisioning for the given type if needed and then starts tethering if
|
* 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
|
* the check succeeds. If no carrier provisioning is required for tethering, tethering is
|
||||||
@@ -131,15 +182,64 @@ object TetheringManager {
|
|||||||
* check failing and how they can sign up for tethering if possible.
|
* check failing and how they can sign up for tethering if possible.
|
||||||
* @param callback an {@link OnStartTetheringCallback} which will be called to notify the caller
|
* @param callback an {@link OnStartTetheringCallback} which will be called to notify the caller
|
||||||
* of the result of trying to tether.
|
* of the result of trying to tether.
|
||||||
* @param handler {@link Handler} to specify the thread upon which the callback will be invoked.
|
* @param handler {@link Handler} to specify the thread upon which the callback will be invoked
|
||||||
|
* @param address A pair (localIPv4Address, clientAddress) for API 30+. If present, it
|
||||||
|
* configures tethering with static IPv4 assignment.
|
||||||
|
*
|
||||||
|
* A DHCP server will be started, but will only be able to offer the client address.
|
||||||
|
* The two addresses must be in the same prefix.
|
||||||
|
*
|
||||||
|
* localIPv4Address: The preferred local IPv4 link address to use.
|
||||||
|
* clientAddress: The static client address.
|
||||||
|
* @see setStaticIpv4Addresses
|
||||||
*/
|
*/
|
||||||
@RequiresApi(24)
|
@RequiresApi(24)
|
||||||
fun startTethering(type: Int, showProvisioningUi: Boolean, callback: OnStartTetheringCallback,
|
fun startTethering(type: Int, showProvisioningUi: Boolean, callback: StartTetheringCallback,
|
||||||
handler: Handler? = null) {
|
handler: Handler? = null, address: Pair<LinkAddress, LinkAddress>? = null) {
|
||||||
val reference = WeakReference(callback)
|
val reference = WeakReference(callback)
|
||||||
val proxy = ProxyBuilder.forClass(classOnStartTetheringCallback)
|
if (BuildCompat.isAtLeastR()) try {
|
||||||
.dexCache(app.deviceStorage.cacheDir)
|
val request = newTetheringRequestBuilder.newInstance(type).let { builder ->
|
||||||
.handler { proxy, method, args ->
|
// setting exemption requires TETHER_PRIVILEGED permission
|
||||||
|
if (app.checkSelfPermission("android.permission.TETHER_PRIVILEGED") ==
|
||||||
|
PackageManager.PERMISSION_GRANTED) setExemptFromEntitlementCheck.invoke(builder, true)
|
||||||
|
setShouldShowEntitlementUi.invoke(builder, showProvisioningUi)
|
||||||
|
if (address != null) {
|
||||||
|
val (localIPv4Address, clientAddress) = address
|
||||||
|
setStaticIpv4Addresses(builder, localIPv4Address, clientAddress)
|
||||||
|
}
|
||||||
|
build.invoke(this)
|
||||||
|
}
|
||||||
|
val executor = Executor { if (handler == null) it.run() else handler.post(it) }
|
||||||
|
val proxy = ProxyBuilder.forClass(classStartTetheringCallback).apply {
|
||||||
|
dexCache(app.deviceStorage.cacheDir)
|
||||||
|
handler { proxy, method, args ->
|
||||||
|
@Suppress("NAME_SHADOWING") val callback = reference.get()
|
||||||
|
when (val name = method.name) {
|
||||||
|
"onTetheringStarted" -> {
|
||||||
|
if (args.isNotEmpty()) Timber.w("Unexpected args for $name: $args")
|
||||||
|
callback?.onTetheringStarted()
|
||||||
|
null
|
||||||
|
}
|
||||||
|
"onTetheringFailed" -> {
|
||||||
|
if (args.size != 1) Timber.w("Unexpected args for $name: $args")
|
||||||
|
callback?.onTetheringFailed(args.getOrNull(0) as? Int?)
|
||||||
|
null
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
Timber.w("Unexpected method, calling super: $method")
|
||||||
|
ProxyBuilder.callSuper(proxy, method, args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.build()
|
||||||
|
startTethering.invoke(instance, request, executor, proxy)
|
||||||
|
return
|
||||||
|
} catch (e: InvocationTargetException) {
|
||||||
|
Timber.w(e, "Unable to invoke TetheringManager.startTethering, falling back to ConnectivityManager")
|
||||||
|
}
|
||||||
|
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")
|
if (args.isNotEmpty()) Timber.w("Unexpected args for ${method.name}: $args")
|
||||||
@Suppress("NAME_SHADOWING") val callback = reference.get()
|
@Suppress("NAME_SHADOWING") val callback = reference.get()
|
||||||
when (method.name) {
|
when (method.name) {
|
||||||
@@ -157,8 +257,8 @@ object TetheringManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.build()
|
}.build()
|
||||||
startTethering.invoke(app.connectivity, type, showProvisioningUi, proxy, handler)
|
startTetheringLegacy.invoke(app.connectivity, type, showProvisioningUi, proxy, handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -172,7 +272,12 @@ object TetheringManager {
|
|||||||
*/
|
*/
|
||||||
@RequiresApi(24)
|
@RequiresApi(24)
|
||||||
fun stopTethering(type: Int) {
|
fun stopTethering(type: Int) {
|
||||||
stopTethering.invoke(app.connectivity, type)
|
if (BuildCompat.isAtLeastR()) try {
|
||||||
|
stopTethering.invoke(instance, type)
|
||||||
|
} catch (e: InvocationTargetException) {
|
||||||
|
Timber.w(e, "Unable to invoke TetheringManager.stopTethering, falling back to ConnectivityManager")
|
||||||
|
}
|
||||||
|
stopTetheringLegacy.invoke(app.connectivity, type)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -185,6 +290,42 @@ object TetheringManager {
|
|||||||
*/
|
*/
|
||||||
fun getLastTetherError(iface: String): Int = getLastTetherError.invoke(app.connectivity, iface) as Int
|
fun getLastTetherError(iface: String): Int = getLastTetherError.invoke(app.connectivity, iface) as Int
|
||||||
|
|
||||||
|
@get:RequiresApi(30)
|
||||||
|
private val tetherErrors by lazy {
|
||||||
|
SparseArrayCompat<String>().apply {
|
||||||
|
for (field in clazz.declaredFields) try {
|
||||||
|
// all TETHER_ERROR_* are system-api since API 30
|
||||||
|
if (field.name.startsWith("TETHER_ERROR_")) put(field.get(null) as Int, field.name)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Timber.w(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fun tetherErrorMessage(error: Int): String {
|
||||||
|
if (BuildCompat.isAtLeastR()) try {
|
||||||
|
tetherErrors.get(error)?.let { return it }
|
||||||
|
} catch (e: ReflectiveOperationException) {
|
||||||
|
Timber.w(e)
|
||||||
|
}
|
||||||
|
return when (error) {
|
||||||
|
TETHER_ERROR_NO_ERROR -> "TETHER_ERROR_NO_ERROR"
|
||||||
|
TETHER_ERROR_UNKNOWN_IFACE -> "TETHER_ERROR_UNKNOWN_IFACE"
|
||||||
|
TETHER_ERROR_SERVICE_UNAVAIL -> "TETHER_ERROR_SERVICE_UNAVAIL"
|
||||||
|
TETHER_ERROR_UNSUPPORTED -> "TETHER_ERROR_UNSUPPORTED"
|
||||||
|
TETHER_ERROR_UNAVAIL_IFACE -> "TETHER_ERROR_UNAVAIL_IFACE"
|
||||||
|
TETHER_ERROR_MASTER_ERROR -> "TETHER_ERROR_MASTER_ERROR"
|
||||||
|
TETHER_ERROR_TETHER_IFACE_ERROR -> "TETHER_ERROR_TETHER_IFACE_ERROR"
|
||||||
|
TETHER_ERROR_UNTETHER_IFACE_ERROR -> "TETHER_ERROR_UNTETHER_IFACE_ERROR"
|
||||||
|
TETHER_ERROR_ENABLE_NAT_ERROR -> "TETHER_ERROR_ENABLE_NAT_ERROR"
|
||||||
|
TETHER_ERROR_DISABLE_NAT_ERROR -> "TETHER_ERROR_DISABLE_NAT_ERROR"
|
||||||
|
TETHER_ERROR_IFACE_CFG_ERROR -> "TETHER_ERROR_IFACE_CFG_ERROR"
|
||||||
|
TETHER_ERROR_PROVISION_FAILED -> "TETHER_ERROR_PROVISION_FAILED"
|
||||||
|
TETHER_ERROR_DHCPSERVER_ERROR -> "TETHER_ERROR_DHCPSERVER_ERROR"
|
||||||
|
TETHER_ERROR_ENTITLEMENT_UNKNOWN -> "TETHER_ERROR_ENTITLEMENT_UNKNOWN"
|
||||||
|
else -> app.getString(R.string.failure_reason_unknown, error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val Intent.tetheredIfaces get() = getStringArrayListExtra(
|
val Intent.tetheredIfaces get() = getStringArrayListExtra(
|
||||||
if (Build.VERSION.SDK_INT >= 26) EXTRA_ACTIVE_TETHER else EXTRA_ACTIVE_TETHER_LEGACY)
|
if (Build.VERSION.SDK_INT >= 26) EXTRA_ACTIVE_TETHER else EXTRA_ACTIVE_TETHER_LEGACY)
|
||||||
val Intent.localOnlyTetheredIfaces get() = if (Build.VERSION.SDK_INT >= 26) {
|
val Intent.localOnlyTetheredIfaces get() = if (Build.VERSION.SDK_INT >= 26) {
|
||||||
|
|||||||
Reference in New Issue
Block a user