diff --git a/WebRTCControlled/app/src/main/java/com/ttstd/controlled/activity/main/MainActivity.java b/WebRTCControlled/app/src/main/java/com/ttstd/controlled/activity/main/MainActivity.java index 3d78310..52f1201 100644 --- a/WebRTCControlled/app/src/main/java/com/ttstd/controlled/activity/main/MainActivity.java +++ b/WebRTCControlled/app/src/main/java/com/ttstd/controlled/activity/main/MainActivity.java @@ -21,6 +21,8 @@ import com.ttstd.controlled.R; import com.ttstd.controlled.base.BaseMvvmActivity; import com.ttstd.controlled.databinding.ActivityMainBinding; import com.ttstd.controlled.service.ScreenCaptureService; +import com.ttstd.controlled.utils.DeviceUtils; +import com.ttstd.controlled.utils.SignatureUtils; import com.ttstd.controlled.webrtc.WebRtcClient; import org.webrtc.RendererCommon; @@ -65,7 +67,7 @@ public class MainActivity extends BaseMvvmActivity startScreenSharing()); binding.btnStop.setOnClickListener(v -> stopScreenSharing()); @@ -76,6 +78,12 @@ public class MainActivity extends BaseMvvmActivity= Build.VERSION_CODES.O) { + try { + // 对于系统应用,Build.getSerial() 应该能成功返回真实的硬件序列号 + return Build.getSerial(); + } catch (Exception e) { + return Build.SERIAL; + } + } + return Build.SERIAL; + } + + /** + * 获取设备标识(适用于普通应用) + *

+ * 做了 Android 版本兼容。如果无法获取硬件序列号(如 Android 10+), + * 则尝试使用 Android ID。如果 Android ID 也获取不到,则通过硬件信息生成 UUID。 + * + * @param context 上下文 + * @return 设备唯一标识 + */ + @SuppressLint({"MissingPermission", "HardwareIds"}) + public static String getSerialNumber(Context context) { + String serial = null; + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + // 尝试通过 getSerial 获取,Android 10+ 普通应用通常会抛异常或返回 unknown + serial = Build.getSerial(); + } else { + serial = Build.SERIAL; + } + } catch (Exception ignored) { + } + + // 校验序列号是否有效 + if (!TextUtils.isEmpty(serial) && !Build.UNKNOWN.equalsIgnoreCase(serial)) { + return serial; + } + + // 尝试获取 Android ID + String androidId = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID); + // 排除某些设备上已知的错误 Android ID ("9774d56d682e549c") + if (!TextUtils.isEmpty(androidId) && !"9774d56d682e549c".equals(androidId)) { + return androidId; + } + + // 如果上述方式都失败,则根据硬件机型信息生成 UUID + return getDeviceUuid(); + } + + /** + * 使用硬件机型信息生成 UUID + * 这种方案在设备重启或刷机后通常能保持稳定,但在系统升级导致某些 Build 字段变化时可能会变。 + */ + private static String getDeviceUuid() { + String devInfo = "35" + // 模拟 IMEI 的前缀 + Build.BOARD.length() % 10 + + Build.BRAND.length() % 10 + + Build.SUPPORTED_ABIS[0].length() % 10 + + Build.DEVICE.length() % 10 + + Build.DISPLAY.length() % 10 + + Build.HOST.length() % 10 + + Build.ID.length() % 10 + + Build.MANUFACTURER.length() % 10 + + Build.MODEL.length() % 10 + + Build.PRODUCT.length() % 10 + + Build.TAGS.length() % 10 + + Build.TYPE.length() % 10 + + Build.USER.length() % 10; + + // 使用 Build.SERIAL 参与 hash,增加独特性(即使它是 "unknown" 也是一种标识) + return new UUID(devInfo.hashCode(), Build.SERIAL.hashCode()).toString(); + } +} diff --git a/WebRTCController/app/src/main/AndroidManifest.xml b/WebRTCController/app/src/main/AndroidManifest.xml index 3fcfba7..6c3261c 100644 --- a/WebRTCController/app/src/main/AndroidManifest.xml +++ b/WebRTCController/app/src/main/AndroidManifest.xml @@ -4,6 +4,15 @@ + + + + + + + connectToControlled()); diff --git a/WebRTCController/app/src/main/java/com/ttstd/controller/utils/DeviceUtils.java b/WebRTCController/app/src/main/java/com/ttstd/controller/utils/DeviceUtils.java new file mode 100644 index 0000000..da9babe --- /dev/null +++ b/WebRTCController/app/src/main/java/com/ttstd/controller/utils/DeviceUtils.java @@ -0,0 +1,83 @@ +package com.ttstd.controller.utils; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.os.Build; +import android.provider.Settings; +import android.text.TextUtils; + +import java.util.UUID; + +/** + * 设备信息工具类(控制端,非系统签名应用) + *

+ * 与 WebRTCControlled 的 DeviceUtils 对应,但控制端没有系统签名, + * 因此无法稳定获取真实的硬件序列号,这里采用「不要求准确」的兜底方案: + * 1. 先尝试 Build.getSerial() / Build.SERIAL(Android 10+ 普通应用通常失败); + * 2. 失败则使用官方提供的 Android ID; + * 3. 再失败则基于硬件机型信息生成 UUID。 + */ +public class DeviceUtils { + + /** + * 获取设备标识(适用于普通应用) + *

+ * 做了 Android 版本兼容。如果无法获取硬件序列号(如 Android 10+), + * 则尝试使用 Android ID。如果 Android ID 也获取不到,则通过硬件信息生成 UUID。 + * + * @param context 上下文 + * @return 设备唯一标识 + */ + @SuppressLint({"MissingPermission", "HardwareIds"}) + public static String getSerialNumber(Context context) { + String serial = null; + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + // 尝试通过 getSerial 获取,Android 10+ 普通应用通常会抛异常或返回 unknown + serial = Build.getSerial(); + } else { + serial = Build.SERIAL; + } + } catch (Exception ignored) { + } + + // 校验序列号是否有效 + if (!TextUtils.isEmpty(serial) && !Build.UNKNOWN.equalsIgnoreCase(serial)) { + return serial; + } + + // 尝试获取 Android ID(官方提供的设备标识,无需特殊权限) + String androidId = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID); + // 排除某些设备上已知的错误 Android ID ("9774d56d682e549c") + if (!TextUtils.isEmpty(androidId) && !"9774d56d682e549c".equals(androidId)) { + return androidId; + } + + // 如果上述方式都失败,则根据硬件机型信息生成 UUID + return getDeviceUuid(); + } + + /** + * 使用硬件机型信息生成 UUID + * 这种方案在设备重启或刷机后通常能保持稳定,但在系统升级导致某些 Build 字段变化时可能会变。 + */ + private static String getDeviceUuid() { + String devInfo = "35" + // 模拟 IMEI 的前缀 + Build.BOARD.length() % 10 + + Build.BRAND.length() % 10 + + Build.SUPPORTED_ABIS[0].length() % 10 + + Build.DEVICE.length() % 10 + + Build.DISPLAY.length() % 10 + + Build.HOST.length() % 10 + + Build.ID.length() % 10 + + Build.MANUFACTURER.length() % 10 + + Build.MODEL.length() % 10 + + Build.PRODUCT.length() % 10 + + Build.TAGS.length() % 10 + + Build.TYPE.length() % 10 + + Build.USER.length() % 10; + + // 使用 Build.SERIAL 参与 hash,增加独特性(即使它是 "unknown" 也是一种标识) + return new UUID(devInfo.hashCode(), Build.SERIAL.hashCode()).toString(); + } +} diff --git a/webrtc_controller_flutter/lib/main.dart b/webrtc_controller_flutter/lib/main.dart index 77016ed..8a81765 100644 --- a/webrtc_controller_flutter/lib/main.dart +++ b/webrtc_controller_flutter/lib/main.dart @@ -1,10 +1,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_webrtc/flutter_webrtc.dart'; -import 'package:uuid/uuid.dart'; import 'config/ice_servers.dart'; import 'controller/remote_controller.dart'; import 'utils/control_commands.dart'; +import 'utils/device_utils.dart'; import 'widgets/remote_touch_view.dart'; void main() { @@ -54,7 +54,15 @@ class _ControllerHomeState extends State { @override void initState() { super.initState(); - _deviceIdController.text = 'controller_${const Uuid().v4().substring(0, 8)}'; + _initDeviceId(); + } + + /// 异步获取设备标识并填入设备ID输入框(非系统签名,使用兜底方案)。 + Future _initDeviceId() async { + final id = await DeviceUtils.getSerialNumber(); + if (mounted) { + setState(() => _deviceIdController.text = id); + } } @override diff --git a/webrtc_controller_flutter/lib/utils/device_utils.dart b/webrtc_controller_flutter/lib/utils/device_utils.dart new file mode 100644 index 0000000..7360bde --- /dev/null +++ b/webrtc_controller_flutter/lib/utils/device_utils.dart @@ -0,0 +1,42 @@ +import 'dart:io'; + +import 'package:device_info_plus/device_info_plus.dart'; +import 'package:uuid/uuid.dart'; + +/// 设备信息工具类(控制端,非系统签名应用) +/// +/// 与 WebRTCControlled 的 DeviceUtils 对应,但控制端没有系统签名, +/// 因此无法稳定获取真实的硬件序列号,这里采用「不要求准确」的兜底方案: +/// 1. 优先使用官方插件([DeviceInfoPlugin])提供的 serialNumber; +/// 2. 失败则使用 iOS 的 identifierForVendor; +/// 3. 再失败则生成随机 UUID。 +class DeviceUtils { + DeviceUtils._(); + + /// 获取设备标识(适用于普通应用)。 + /// + /// 兼容各平台:Android 使用官方序列号,iOS 使用 identifierForVendor, + /// 均不可用时回退到随机 UUID。 + static Future getSerialNumber() async { + const uuid = Uuid(); + try { + final deviceInfo = DeviceInfoPlugin(); + if (Platform.isAndroid) { + final androidInfo = await deviceInfo.androidInfo; + final serial = androidInfo.serialNumber; + if (serial.isNotEmpty && serial.toLowerCase() != 'unknown') { + return serial; + } + } else if (Platform.isIOS) { + final iosInfo = await deviceInfo.iosInfo; + final id = iosInfo.identifierForVendor; + if (id != null && id.isNotEmpty) { + return id; + } + } + } catch (_) { + // 获取失败,走兜底逻辑 + } + return uuid.v4(); + } +} diff --git a/webrtc_controller_flutter/macos/Flutter/GeneratedPluginRegistrant.swift b/webrtc_controller_flutter/macos/Flutter/GeneratedPluginRegistrant.swift index 4f68d10..774d11e 100644 --- a/webrtc_controller_flutter/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/webrtc_controller_flutter/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,8 +5,10 @@ import FlutterMacOS import Foundation +import device_info_plus import flutter_webrtc func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) FlutterWebRTCPlugin.register(with: registry.registrar(forPlugin: "FlutterWebRTCPlugin")) } diff --git a/webrtc_controller_flutter/pubspec.lock b/webrtc_controller_flutter/pubspec.lock index 965a81f..bdf7f8e 100644 --- a/webrtc_controller_flutter/pubspec.lock +++ b/webrtc_controller_flutter/pubspec.lock @@ -81,6 +81,22 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "1.8.1" + device_info_plus: + dependency: "direct main" + description: + name: device_info_plus + sha256: "98f28b42168cc509abc92f88518882fd58061ea372d7999aecc424345c7bff6a" + url: "https://pub.flutter-io.cn" + source: hosted + version: "11.5.0" + device_info_plus_platform_interface: + dependency: transitive + description: + name: device_info_plus_platform_interface + sha256: e1ea89119e34903dca74b883d0dd78eb762814f97fb6c76f35e9ff74d261a18f + url: "https://pub.flutter-io.cn" + source: hosted + version: "7.0.3" fake_async: dependency: transitive description: @@ -97,6 +113,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "2.2.0" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.flutter-io.cn" + source: hosted + version: "7.0.1" fixnum: dependency: "direct main" description: @@ -123,6 +147,11 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" flutter_webrtc: dependency: "direct main" description: @@ -472,6 +501,22 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "1.5.1" + win32: + dependency: transitive + description: + name: win32 + sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e + url: "https://pub.flutter-io.cn" + source: hosted + version: "5.15.0" + win32_registry: + dependency: transitive + description: + name: win32_registry + sha256: "6f1b564492d0147b330dd794fee8f512cec4977957f310f9951b5f9d83618dae" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.0" xdg_directories: dependency: transitive description: diff --git a/webrtc_controller_flutter/pubspec.yaml b/webrtc_controller_flutter/pubspec.yaml index 5319dcd..55177c5 100644 --- a/webrtc_controller_flutter/pubspec.yaml +++ b/webrtc_controller_flutter/pubspec.yaml @@ -43,6 +43,9 @@ dependencies: # 设备 ID 生成 uuid: ^4.4.0 + + # 官方提供的设备信息插件,用于获取 Android ID / 设备标识 + device_info_plus: ^11.0.0 protobuf: ^6.0.0 fixnum: ^1.1.1