import 'dart:convert'; import 'dart:io'; import 'package:crypto/crypto.dart'; import 'package:device_identity/device_identity.dart'; import 'package:device_info_plus/device_info_plus.dart'; import 'package:flutter/services.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:hgdj/extension/extensions.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:path_provider/path_provider.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:uuid/uuid.dart'; import '../config/config.dart'; import '../hj_utils/light_model.dart'; import '../hj_utils/store_keys.dart'; import '../hj_utils/text_util.dart'; //设备信息管理类 class DeviceInfoService { /// iOS Keychain 存 DevID。同一个 bundle id 卸载重装后 Keychain 项仍然存在(iOS /// 沙盒清理不动 Keychain) → 同账号身份。**没声明 Keychain Sharing access group, /// 不同 bundle id 马甲互相读不到对方的 DevID(各自独立),这是产品取舍。** static const _kKeychainDevIdKey = 'hgdj_device_id_v1'; static const _secureStorage = FlutterSecureStorage( iOptions: IOSOptions( accessibility: KeychainAccessibility.first_unlock, ), ); static final DeviceInfoPlugin _infoPlugin = DeviceInfoPlugin(); static String? _brand; static String? _model; static String? _deviceId; static String? _deviceOS; static String? _systemVersion; static String? _devType; static String? _ua; static String? _buildID; //剪切板缓存(iOS 每次读取会弹系统提示,全局只读一次;缓存 Future 防并发重复触发) static Future? _clipboardFuture; /// 获取剪切板文本,全局只触发一次系统读取 static Future getClipboardTextCached() { return _clipboardFuture ??= _readClipboardOnce(); } static Future _readClipboardOnce() async { try { final data = await Clipboard.getData(Clipboard.kTextPlain); return data?.text; } catch (_) { return null; } } /// 获取品牌 static String get brand => _brand ?? ""; /// 获取型号 static String get model => _model ?? ""; /// 设备唯一 ID(APP 内保持不变) static String get deviceId => _deviceId ?? ""; /// 设备平台 static String get deviceOS => _deviceOS ?? ""; //系统版本 static String get systemVersion => _systemVersion ?? ""; //拼接参数 static String get devType => _devType ?? ""; static String get ua => _ua ?? ""; /// 修正后的 pkgName:iOS 用 TF 内部包名常量,Android 用真实包名 static String get buildID => _buildID ?? ""; /// ios tf 的内部包名 /// note_this:不要修改这个值 static const iosBundleId = "yinse_opera"; static const naticeChannel = 'com.yinse/device'; //原生通道(设备/视频/编解码信息) static const requestChannel = 'com.yinse/request'; //原生系统桥(现只剩换启动图标 changeIcon) /// 外部调用入口 static Future init() async { // buildID 初始化一次,后续都用缓存 if (Platform.isIOS) { _buildID = iosBundleId; } else { final pkg = await PackageInfo.fromPlatform(); _buildID = pkg.packageName; } // 设备ID:多重持久化(SP + 私有目录文件),任一存在即复用 _deviceId = await _resolveDeviceId(); if (Platform.isAndroid) { final android = await _infoPlugin.androidInfo; _brand = android.brand; _model = android.model; _deviceOS = "Android"; _systemVersion = android.version.release; _devType = "${android.device}:${android.version.sdkInt}:id=${android.id}"; } else if (Platform.isIOS) { final ios = await _infoPlugin.iosInfo; _brand = "Apple"; _model = ios.utsname.machine; // 如 iPhone14,2 _deviceOS = "iOS"; _systemVersion = ios.utsname.release; _devType = "${ios.systemName}:$_systemVersion"; } else { _brand = "Unknown"; _model = "Unknown"; _deviceOS = Platform.operatingSystem; _systemVersion = 'Unknown'; } _ua = genUserAgent(); } /// 拼装 UA。[deviceId] 可覆盖默认 deviceId static String genUserAgent({String? deviceId}) { final Map params = { "DevID": (deviceId == null || deviceId.isEmpty) ? DeviceInfoService.deviceId : deviceId, "DevType": DeviceInfoService.devType, "SysType": Platform.operatingSystem, "Ver": Config.innerVersion, "BuildID": DeviceInfoService.buildID, 'DeviceBrand': DeviceInfoService.brand, 'SystemName': DeviceInfoService.deviceOS, 'SystemVersion': DeviceInfoService.systemVersion, 'device_model': DeviceInfoService.model, }; params.removeWhere((key, value) => value == null || value.isEmpty); final ua = Uri.encodeComponent( params.entries.map((e) => "${e.key}=${e.value}").join(";")); return ua; } /// 获取带缓存的 UA。 /// - [deviceId] 为空:命中 KV 缓存直接返回; /// - [deviceId] 非空:跳过缓存,按当前最新设备信息重新生成并写回 KV(用于扫码登录、设备切换等场景)。 /// 实际写入 UA 的 DevID 字段:deviceId 非空用它,否则 fallback 到 [DeviceInfoService.deviceId]。 static Future getCachedUserAgent({String? deviceId}) async { final oldUa = (await lightKV.getString(StoreKeys.UA_CACHE)) ?? ''; if (TextUtil.isEmpty(deviceId) && TextUtil.isNotEmpty(oldUa)) { return oldUa; } final newUa = genUserAgent(deviceId: deviceId); if (TextUtil.isNotEmpty(newUa)) lightKV.setString(StoreKeys.UA_CACHE, newUa); return newUa; } /// 清除 UA 缓存 static Future clearCachedUserAgent() => lightKV.setString(StoreKeys.UA_CACHE, null); /// 从剪切板 json 里取推广 tid,取不到返回空串 static Future fetchTraceId() async { final clipboardText = await getClipboardTextCached(); if (TextUtil.isEmpty(clipboardText)) return ''; try { return jsonDecode(clipboardText!)['tid'] ?? ''; } catch (_) { return ''; } } /// 已知 ANDROID_ID 脏值(厂商定制 ROM / 早期山寨机长期出现) static const _dirtyAndroidIds = { '9774d56d682e549c', // Android 4.2 之前山寨机著名脏值 '0000000000000000', 'ffffffffffffffff', '0123456789abcdef', }; /// 解析设备 ID:按平台分两条独立路径,任一命中都回写到该平台的所有存储层。 /// /// iOS Keychain 是唯一**跨卸载 + 跨 bundle id** 持久的存储(配合 Keychain Sharing)。 /// SP/文件在 app 沙盒里,卸载就抹;放最高优先级用 Keychain 才能保住账号身份。 /// Android 没有等价机制,applicationId 一变就是新 app,账号无法共享(产品取舍)。 static Future _resolveDeviceId() async { final prefs = await SharedPreferences.getInstance(); return Platform.isIOS ? _resolveIOS(prefs) : _resolveAndroid(prefs); } /// iOS:Keychain → SP → 文件 → 新生成,命中即回写三层(Keychain+SP+文件) static Future _resolveIOS(SharedPreferences prefs) async { // 1. Keychain 优先(卸载重装、马甲切换都能拿到原 DevID) try { final kid = await _secureStorage.read(key: _kKeychainDevIdKey); if (TextUtil.isNotEmpty(kid)) return _persistIOS(prefs, kid!); } catch (_) { // Keychain 偶发失败不阻塞启动,继续走 SP/文件兜底 } // 2. SP 命中 final spId = prefs.getString(StoreKeys.DEVICE_ID); if (TextUtil.isNotEmpty(spId)) return _persistIOS(prefs, spId!); // 3. 文件命中 final fileId = await _readIdFile(); if (TextUtil.isNotEmpty(fileId)) return _persistIOS(prefs, fileId!); // 4. 全新生成 return _persistIOS(prefs, await _generateDeviceId()); } /// Android:SP → 文件 → 新生成,命中即回写两层(SP+文件),无 Keychain 等价机制 static Future _resolveAndroid(SharedPreferences prefs) async { // 1. SP 命中 final spId = prefs.getString(StoreKeys.DEVICE_ID); if (TextUtil.isNotEmpty(spId)) return _persistAndroid(prefs, spId!); // 2. 文件命中 final fileId = await _readIdFile(); if (TextUtil.isNotEmpty(fileId)) return _persistAndroid(prefs, fileId!); // 3. 全新生成 return _persistAndroid(prefs, await _generateDeviceId()); } /// iOS 回写三层,保证 Keychain/SP/文件一致 static Future _persistIOS(SharedPreferences prefs, String id) async { await prefs.setString(StoreKeys.DEVICE_ID, id); await _writeIdFile(id); await _trySaveKeychain(id); return id; } /// Android 回写 SP + 文件 static Future _persistAndroid( SharedPreferences prefs, String id) async { await prefs.setString(StoreKeys.DEVICE_ID, id); await _writeIdFile(id); return id; } /// 写 Keychain 失败不抛错(权限/entitlements 配错时降级走 SP+文件,不卡启动) static Future _trySaveKeychain(String id) async { try { await _secureStorage.write(key: _kKeychainDevIdKey, value: id); } catch (_) {} } /// 生成新设备 ID /// Android:OAID → ANDROID_ID → UUID 兜底 /// iOS:identifierForVendor(IDFV)→ UUID 兜底 static Future _generateDeviceId() async { String raw = ''; if (Platform.isAndroid) { try { await DeviceIdentity.register(); raw = await DeviceIdentity.oaid; if (_isInvalidId(raw)) { raw = await DeviceIdentity.androidId; } } catch (_) {} } else if (Platform.isIOS) { try { final ios = await _infoPlugin.iosInfo; raw = ios.identifierForVendor ?? ''; } catch (_) {} } if (_isInvalidId(raw)) raw = const Uuid().v4(); // 业务规则:hgdj_ 前缀,去空格,限制 50 字符(日志系统约束) return 'hgdj_${raw.replaceAll(' ', '')}'.limit(50); } /// 脏值检测:空 / 太短 / 全 0 / 已知脏值 static bool _isInvalidId(String? id) { if (TextUtil.isEmpty(id)) return true; final v = id!.toLowerCase(); if (v.length < 8) return true; if (v.hasManyZeros()) return true; return _dirtyAndroidIds.contains(v); } /// 写入应用支持目录文件(零权限,卸载会丢,但与 SP 互为冗余) static Future _writeIdFile(String id) async { try { final dir = await getApplicationSupportDirectory(); final file = File('${dir.path}/.device_id'); await file.writeAsString(id, flush: true); } catch (_) {} } /// 读取应用支持目录文件 static Future _readIdFile() async { try { final dir = await getApplicationSupportDirectory(); final file = File('${dir.path}/.device_id'); if (!await file.exists()) return null; final v = (await file.readAsString()).trim(); return v.isEmpty ? null : v; } catch (_) { return null; } } /// 获取加密后的设备id验签 static String getDevToken(String devId) { String a = "g24p5VJ4fJ"; String b = "5P#at%Yu"; String c = "ZPRwQuKl8YlVIr"; String s = a + b + c + devId + a + b + c; var data = utf8.encode(s); var chiper = sha256.convert(data); return base64Encode(chiper.bytes); } ///获取渠道(非 Android 或取不到都返回空串) static Future getChannel() async { if (!Platform.isAndroid) return ""; try { return await const MethodChannel(naticeChannel) .invokeMethod("getChannel") ?? ""; } on PlatformException { return ""; } } }