174 lines
7.4 KiB
Kotlin
174 lines
7.4 KiB
Kotlin
package be.mygod.vpnhotspot.net
|
|
|
|
import android.annotation.SuppressLint
|
|
import android.net.ConnectivityManager
|
|
import android.os.Build
|
|
import android.os.Bundle
|
|
import android.os.Handler
|
|
import android.util.Log
|
|
import androidx.annotation.RequiresApi
|
|
import be.mygod.vpnhotspot.App.Companion.app
|
|
import com.android.dx.stock.ProxyBuilder
|
|
import com.crashlytics.android.Crashlytics
|
|
|
|
/**
|
|
* Heavily based on:
|
|
* https://github.com/aegis1980/WifiHotSpot
|
|
* https://android.googlesource.com/platform/frameworks/base.git/+/android-7.0.0_r1/core/java/android/net/ConnectivityManager.java
|
|
*/
|
|
object TetheringManager {
|
|
/**
|
|
* Callback for use with [.startTethering] to find out whether tethering succeeded.
|
|
*/
|
|
interface OnStartTetheringCallback {
|
|
/**
|
|
* Called when tethering has been successfully started.
|
|
*/
|
|
fun onTetheringStarted() { }
|
|
|
|
/**
|
|
* Called when starting tethering failed.
|
|
*/
|
|
fun onTetheringFailed() { }
|
|
}
|
|
|
|
private const val TAG = "TetheringManager"
|
|
|
|
/**
|
|
* This is a sticky broadcast since almost forever.
|
|
*
|
|
* https://android.googlesource.com/platform/frameworks/base.git/+/2a091d7aa0c174986387e5d56bf97a87fe075bdb%5E%21/services/java/com/android/server/connectivity/Tethering.java
|
|
*/
|
|
const val ACTION_TETHER_STATE_CHANGED = "android.net.conn.TETHER_STATE_CHANGED"
|
|
private const val EXTRA_ACTIVE_TETHER_LEGACY = "activeArray"
|
|
/**
|
|
* gives a String[] listing all the interfaces currently in local-only
|
|
* mode (ie, has DHCPv4+IPv6-ULA support and no packet forwarding)
|
|
*/
|
|
@RequiresApi(26)
|
|
private const val EXTRA_ACTIVE_LOCAL_ONLY = "localOnlyArray"
|
|
/**
|
|
* gives a String[] listing all the interfaces currently tethered
|
|
* (ie, has DHCPv4 support and packets potentially forwarded/NATed)
|
|
*/
|
|
@RequiresApi(26)
|
|
private const val EXTRA_ACTIVE_TETHER = "tetherArray"
|
|
/**
|
|
* gives a String[] listing all the interfaces we tried to tether and
|
|
* failed. Use {@link #getLastTetherError} to find the error code
|
|
* for any interfaces listed here.
|
|
*/
|
|
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 TETHERING_WIFI = 0
|
|
/**
|
|
* Requires MANAGE_USB permission, unfortunately.
|
|
*
|
|
* Source: https://android.googlesource.com/platform/frameworks/base/+/7ca5d3a/services/usb/java/com/android/server/usb/UsbService.java#389
|
|
*/
|
|
const val TETHERING_USB = 1
|
|
/**
|
|
* Requires BLUETOOTH permission.
|
|
*/
|
|
const val TETHERING_BLUETOOTH = 2
|
|
|
|
private val classOnStartTetheringCallback by lazy @SuppressLint("PrivateApi") {
|
|
Class.forName("android.net.ConnectivityManager\$OnStartTetheringCallback")
|
|
}
|
|
private val startTethering by lazy {
|
|
ConnectivityManager::class.java.getDeclaredMethod("startTethering",
|
|
Int::class.java, Boolean::class.java, classOnStartTetheringCallback, Handler::class.java)
|
|
}
|
|
private val stopTethering by lazy {
|
|
ConnectivityManager::class.java.getDeclaredMethod("stopTethering", Int::class.java)
|
|
}
|
|
private val getLastTetherError by lazy {
|
|
ConnectivityManager::class.java.getDeclaredMethod("getLastTetherError", String::class.java)
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
*
|
|
* @param type The type of tethering to start. Must be one of
|
|
* {@link ConnectivityManager.TETHERING_WIFI},
|
|
* {@link ConnectivityManager.TETHERING_USB}, or
|
|
* {@link ConnectivityManager.TETHERING_BLUETOOTH}.
|
|
* @param showProvisioningUi a boolean indicating to show the provisioning app UI if there
|
|
* is one. This should be true the first time this function is called and also any time
|
|
* the user can see this UI. It gives users information from their carrier about the
|
|
* 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
|
|
* of the result of trying to tether.
|
|
* @param handler {@link Handler} to specify the thread upon which the callback will be invoked.
|
|
*/
|
|
@RequiresApi(24)
|
|
fun start(type: Int, showProvisioningUi: Boolean, callback: OnStartTetheringCallback, handler: Handler? = null) {
|
|
val proxy = ProxyBuilder.forClass(classOnStartTetheringCallback)
|
|
.dexCache(app.cacheDir)
|
|
.handler { proxy, method, args ->
|
|
if (args.isNotEmpty()) Crashlytics.log(Log.WARN, TAG, "Unexpected args for ${method.name}: $args")
|
|
when (method.name) {
|
|
"onTetheringStarted" -> {
|
|
callback.onTetheringStarted()
|
|
null
|
|
}
|
|
"onTetheringFailed" -> {
|
|
callback.onTetheringFailed()
|
|
null
|
|
}
|
|
else -> {
|
|
Crashlytics.log(Log.WARN, TAG, "Unexpected method, calling super: $method")
|
|
ProxyBuilder.callSuper(proxy, method, args)
|
|
}
|
|
}
|
|
}
|
|
.build()
|
|
startTethering.invoke(app.connectivity, type, showProvisioningUi, proxy, handler)
|
|
}
|
|
|
|
/**
|
|
* Stops tethering for the given type. Also cancels any provisioning rechecks for that type if
|
|
* applicable.
|
|
*
|
|
* @param type The type of tethering to stop. Must be one of
|
|
* {@link ConnectivityManager.TETHERING_WIFI},
|
|
* {@link ConnectivityManager.TETHERING_USB}, or
|
|
* {@link ConnectivityManager.TETHERING_BLUETOOTH}.
|
|
*/
|
|
@RequiresApi(24)
|
|
fun stop(type: Int) {
|
|
stopTethering.invoke(app.connectivity, type)
|
|
}
|
|
|
|
/**
|
|
* Get a more detailed error code after a Tethering or Untethering
|
|
* request asynchronously failed.
|
|
*
|
|
* @param iface The name of the interface of interest
|
|
* @return error The error code of the last error tethering or untethering the named
|
|
* interface
|
|
*/
|
|
fun getLastTetherError(iface: String): Int = getLastTetherError.invoke(app.connectivity, iface) as Int
|
|
|
|
fun getTetheredIfaces(extras: Bundle) = extras.getStringArrayList(
|
|
if (Build.VERSION.SDK_INT >= 26) EXTRA_ACTIVE_TETHER else EXTRA_ACTIVE_TETHER_LEGACY)!!
|
|
fun getLocalOnlyTetheredIfaces(extras: Bundle) =
|
|
if (Build.VERSION.SDK_INT >= 26) extras.getStringArrayList(EXTRA_ACTIVE_LOCAL_ONLY)!!
|
|
else emptyList<String>()
|
|
}
|