Android使用Wi-Fi配对的原理和实现

原理

Android Studio中插件实现的原理

  1. 源码

https://cs.android.com/android-studio/platform/tools/adt/idea/+/mirror-goog-studio-master-dev:android-adb/src/com/android/tools/idea/adb/wireless/WiFiPairingServiceImpl.kt

  1. 重点函数说明

    2.1 检查adb mdns的支持状况

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    override fun checkMdnsSupport(): ListenableFuture<MdnsSupportState> {
    // TODO: Investigate updating (then using) ddmlib instead of spawning an adb client command, so that
    // we don't have to rely on parsing command line output
    LOG.info("Checking if mDNS is supported (`adb mdns check` command)")
    val futureResult = adbService.executeCommand(listOf("mdns", "check"))
    return futureResult.transform(taskExecutor) { result ->
    when {
    result.errorCode != 0 -> {
    LOG.warn("`adb mdns check` returned a non-zero error code (${result.errorCode})")
    val isUnknownCommand = result.stderr.any { line -> line.contains(Regex("unknown.*command")) }
    if (isUnknownCommand)
    MdnsSupportState.AdbVersionTooLow
    else
    MdnsSupportState.AdbInvocationError
    }
    result.stdout.isEmpty() -> {
    LOG.warn("`adb mdns check` returned an empty output (why?)")
    MdnsSupportState.AdbInvocationError
    }
    // See https://android-review.googlesource.com/c/platform/system/core/+/1274009/5/adb/client/transport_mdns.cpp#553
    result.stdout.any { it.contains("mdns daemon version") } -> {
    MdnsSupportState.Supported
    }
    else -> {
    MdnsSupportState.NotSupported
    }
    }
    }.catching(taskExecutor, Throwable::class.java) { t ->
    LOG.warn("Error executing `adb mdns check`", t)
    MdnsSupportState.AdbInvocationError
    }.transform(taskExecutor) { supportState ->
    // This `tansform` is just for logging purposes
    LOG.info("Checking if mDNS is supportState result: ${supportState}")
    supportState
    }
    }

    2.2 adb mdns services列出支持mdns的设备

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    override fun scanMdnsServices(): ListenableFuture<List<MdnsService>> {
    // TODO: Investigate updating (then using) ddmlib instead of spawning an adb client command, so that
    // we don't have to rely on parsing command line output
    val futureResult = adbService.executeCommand(listOf("mdns", "services"))
    return futureResult.transform(taskExecutor) { result ->
    // Output example:
    // List of discovered mdns services
    // adb-939AX05XBZ-vWgJpq _adb-tls-connect._tcp. 192.168.1.86:39149
    // adb-939AX05XBZ-vWgJpq _adb-tls-pairing._tcp. 192.168.1.86:37313
    // Regular expression
    // adb-<everything-until-space><spaces>__adb-tls-pairing._tcp.<spaces><everything-until-colon>:<port>
    val lineRegex = Regex("([^\\t]+)\\t*_adb-tls-pairing._tcp.\\t*([^:]+):([0-9]+)")

    if (result.errorCode != 0) {
    throw AdbCommandException("Error discovering services", result.errorCode, result.stderr)
    }

    if (result.stdout.isEmpty()) {
    throw AdbCommandException("Empty output from \"adb mdns services\" command", -1, result.stderr)
    }

    return@transform result.stdout
    .drop(1)
    .mapNotNull { line ->
    val matchResult = lineRegex.find(line)
    matchResult?.let {
    try {
    val serviceName = it.groupValues[1]
    val ipAddress = InetAddress.getByName(it.groupValues[2])
    val port = it.groupValues[3].toInt()
    val serviceType = if (serviceName.startsWith(studioServiceNamePrefix)) ServiceType.QrCode else ServiceType.PairingCode
    MdnsService(serviceName, serviceType, ipAddress, port)
    }
    catch (ignored: Exception) {
    LOG.warn("mDNS service entry ignored due do invalid characters: ${line}")
    null
    }
    }
    }
    }
    }

    2.3 adb pair ipAddr:port password,用来配对PC和Android手机

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
        override fun pairMdnsService(mdnsService: MdnsService, password: String): ListenableFuture<PairingResult> {
    LOG.info("Start mDNS pairing: ${mdnsService}")

    val deviceAddress = "${mdnsService.ipAddress.hostAddress}:${mdnsService.port}"
    // TODO: Update this when password can be passed as an argument
    val passwordInput = password + LineSeparator.getSystemLineSeparator().separatorString
    // TODO: Investigate updating (then using) ddmlib instead of spawning an adb client command, so that
    // we don't have to rely on parsing command line output
    val futureResult = adbService.executeCommand(listOf("pair", deviceAddress), passwordInput)

    return futureResult.transform(taskExecutor) { result ->
    LOG.info("mDNS pairing exited with code ${result.errorCode}")
    result.stdout.take(5).forEachIndexed { index, line ->
    LOG.info(" stdout line #$index: $line") }

    if (result.errorCode != 0) {
    throw AdbCommandException("Error pairing device", result.errorCode, result.stderr)
    }

    if (result.stdout.isEmpty()) {
    throw AdbCommandException("Empty output from \"adb pair\" command", -1, result.stderr)
    }

    // Output example:
    // Enter pairing code: Successfully paired to 192.168.1.86:41915 [guid=adb-939AX05XBZ-vWgJpq]
    // Regular expression
    // <Prefix><everything-until-colon>:<port>[guid=<everything-until-close-bracket>]
    val lineRegex = Regex("Successfully paired to ([^:]*):([0-9]*) \\[guid=([^\\]]*)\\]")
    val matchResult = lineRegex.find(result.stdout[0])
    matchResult?.let {
    try {
    val ipAddress = InetAddress.getByName(it.groupValues[1])
    val port = it.groupValues[2].toInt()
    val serviceGuid = it.groupValues[3]
    PairingResult(ipAddress, port, serviceGuid)
    }
    catch (e: Exception) {
    throw InvalidDataException("Pairing result is invalid", e)
    }
    } ?: throw InvalidDataException("Pairing result is invalid")
    }
    }

    2.4 生成二维码的内容格式

    1
    2
    3
    4
    5
    6
    /**
    * Format is "WIFI:T:ADB;S:service;P:password;;" (without the quotes)
    */
    private fun createPairingString(service: String, password: String): String {
    return "WIFI:T:ADB;S:${service};P:${password};;"
    }

实现

用nodejs实现的命令行工具

  1. adb-wifi2