diff --git a/.cursorignore b/.cursorignore
new file mode 100644
index 0000000000..6974cdf24c
--- /dev/null
+++ b/.cursorignore
@@ -0,0 +1,55 @@
+# 工作手机 · Cursor 忽略(防持续上传/索引)
+# 根因:反编译产物 ~45 万文件,Agent 会反复同步到云端
+
+# APK 反编译与资料(最大头)
+资料/**
+**/apk_analysis/**
+
+# 微信反编译 / jadx / smali 产物
+sdk/tmp/**
+sdk/scripts/gadget_work/**
+**/jadx_out/**
+**/wechat_decompiled/**
+**/smali/**
+**/smali_classes*/**
+
+# 依赖与构建
+**/node_modules/**
+**/.pnpm-store/**
+**/.yarn/**
+**/.npm/**
+**/.venv/**
+**/venv/**
+**/__pycache__/**
+**/.pytest_cache/**
+**/dist/**
+**/build/**
+**/.next/**
+**/.turbo/**
+**/target/**
+**/.gradle/**
+**/.m2/**
+**/vendor/**
+**/coverage/**
+
+# 缓存与索引
+**/.cache/**
+**/.codegraph/**
+**/.cursor/**
+**/.git/objects/**
+
+# 大文件
+**/*.apk
+**/*.aab
+**/*.dex
+**/*.so
+**/*.jar
+**/*.zip
+**/*.7z
+**/*.dmg
+**/*.mp4
+**/*.mov
+**/*.MP4
+**/*.MOV
+**/*.log
+**/logs/**
diff --git a/.cursorindexingignore b/.cursorindexingignore
new file mode 100644
index 0000000000..8bda13d7f7
--- /dev/null
+++ b/.cursorindexingignore
@@ -0,0 +1,8 @@
+# 仅排除索引搜索,Agent 仍可 @ 读取(工作手机反编译目录)
+资料/**
+sdk/tmp/**
+sdk/scripts/gadget_work/**
+**/jadx_out/**
+**/wechat_decompiled/**
+**/smali/**
+**/smali_classes*/**
diff --git a/.gitignore b/.gitignore
index d39da9a26d..ce24c6147a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -38,6 +38,20 @@ sdk/logs/
sdk/tmp_rom/
sdk/.runtime/
+# 本机/真机运行态配置与验收日志(模板文件可提交)
+sdk/config/*.env
+sdk/config/agent_config_*.json
+sdk/agent/config.nas-active.json
+sdk/data/operation_logs/
+sdk/app/data/hook/events.jsonl
+
+# 本地发行包、逆向工作区与设备二进制
+sdk/releases/
+sdk/scripts/gadget_work/
+sdk/scripts/frida-server-*
+sdk/scripts/frida-gadget-*
+sdk/scripts/wechat_original.apk
+
# Obsidian 粘贴图误落 vault 根(应进 开发文档/**/images/)
/Pasted image*.png
/Pasted image*.jpg
diff --git a/sdk/.dockerignore b/sdk/.dockerignore
new file mode 100644
index 0000000000..b57887e944
--- /dev/null
+++ b/sdk/.dockerignore
@@ -0,0 +1,20 @@
+.git
+.env
+.env.*
+*.log
+__pycache__/
+*.pyc
+.pytest_cache/
+.gradle/
+node_modules/
+android-app/
+admin/
+releases/
+tests/
+data/
+app/data/cunke_bao_config.json
+app/data/hook/events.jsonl
+app/data/operation_logs/
+app/agent/config.json
+agent/config.json
+agent/config.*.json
diff --git a/sdk/Dockerfile b/sdk/Dockerfile
index 09da03e1f6..a5fc061e79 100644
--- a/sdk/Dockerfile
+++ b/sdk/Dockerfile
@@ -1,9 +1,11 @@
# 工作手机SDK v3.1 - Dockerfile(含 AI Brain + Hook 模块管理)
-FROM python:3.11-slim
+ARG PYTHON_IMAGE=docker.1ms.run/library/python:3.11-slim
+FROM ${PYTHON_IMAGE}
WORKDIR /app
-RUN apt-get update && apt-get install -y --no-install-recommends \
+RUN sed -i 's#deb.debian.org#mirrors.cloud.tencent.com#g; s#security.debian.org#mirrors.cloud.tencent.com#g' /etc/apt/sources.list.d/debian.sources \
+ && apt-get update && apt-get install -y --no-install-recommends \
curl \
adb \
&& rm -rf /var/lib/apt/lists/*
diff --git a/sdk/agent/config.nas-primary.example.json b/sdk/agent/config.nas-primary.example.json
new file mode 100644
index 0000000000..36d72c679a
--- /dev/null
+++ b/sdk/agent/config.nas-primary.example.json
@@ -0,0 +1,12 @@
+{
+ "device_id": "xgfe65eimrrofyws",
+ "server_url": "ws://192.168.110.101:8899/ws/device",
+ "public_servers": [
+ "ws://192.168.1.201:8899/ws/device",
+ "ws://192.168.110.167:8899/ws/device",
+ "ws://open.quwanzhi.com:8899/ws/device"
+ ],
+ "heartbeat_interval": 10,
+ "project_id": "cunkebao",
+ "comment": "顺序固定: ①NAS局域网(server_url) ②本机Docker(局网IP,示例110.167) ③NAS外网frp。勿把frp排在Docker前。"
+}
diff --git a/sdk/agent/sdk_discovery.py b/sdk/agent/sdk_discovery.py
new file mode 100644
index 0000000000..5692510483
--- /dev/null
+++ b/sdk/agent/sdk_discovery.py
@@ -0,0 +1,59 @@
+"""
+设备端 SDK 自动发现 — 监听 UDP beacon(8898),无线主控免手填 IP。
+
+与 sdk/app/services/discovery_service.py BEACON 协议对齐。
+"""
+
+from __future__ import annotations
+
+import json
+import socket
+import time
+from typing import Optional
+
+BEACON_PORT = 8898
+BEACON_MAGIC = "WORKPHONE_SDK"
+
+
+def _pick_best_ip(ips: list) -> str:
+ """优先 192.168.110.x(工作机常用网段),否则取第一个私网 IP。"""
+ for ip in ips:
+ if ip.startswith("192.168.110."):
+ return ip
+ for ip in ips:
+ if ip.startswith(("192.168.", "10.", "172.")):
+ return ip
+ return ips[0]
+
+
+def discover_sdk_ws_base(timeout: float = 20.0) -> Optional[str]:
+ """
+ 监听局域网 UDP beacon,返回 WebSocket 基础地址。
+ 例: ws://192.168.110.251:8899/ws/device
+ """
+ deadline = time.time() + timeout
+ sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ try:
+ sock.bind(("", BEACON_PORT))
+ except OSError:
+ # Termux 可能无权限绑 8898,改绑任意端口并只 recv
+ sock.bind(("", 0))
+ sock.settimeout(1.0)
+
+ while time.time() < deadline:
+ try:
+ data, _addr = sock.recvfrom(4096)
+ payload = json.loads(data.decode("utf-8"))
+ if payload.get("magic") != BEACON_MAGIC:
+ continue
+ port = int(payload.get("port") or 8899)
+ ws_path = (payload.get("ws_path") or "/ws/device").rstrip("/")
+ ips = payload.get("ips") or []
+ host = _pick_best_ip(ips) if ips else "127.0.0.1"
+ return f"ws://{host}:{port}{ws_path}"
+ except socket.timeout:
+ continue
+ except Exception:
+ continue
+ return None
diff --git a/sdk/android-app/app/build.gradle b/sdk/android-app/app/build.gradle
index ed7cf78ff0..2fd7f6fd04 100644
--- a/sdk/android-app/app/build.gradle
+++ b/sdk/android-app/app/build.gradle
@@ -14,7 +14,7 @@ android {
versionCode 6
versionName "5.0.1"
buildConfigField "String", "PWA_URL", "\"https://ckbapi.quwanzhi.com\""
- buildConfigField "String", "DEFAULT_WS", "\"wss://workphone.quwanzhi.com/ws/device\""
+ buildConfigField "String", "DEFAULT_WS", "\"wss://wpsdk.quwanzhi.com/ws/device\""
buildConfigField "String", "AI_API_URL", "\"https://ckbapi.quwanzhi.com\""
}
diff --git a/sdk/android-app/app/src/main/AndroidManifest.xml b/sdk/android-app/app/src/main/AndroidManifest.xml
index a3b0933bf4..5a1d3f8a77 100644
--- a/sdk/android-app/app/src/main/AndroidManifest.xml
+++ b/sdk/android-app/app/src/main/AndroidManifest.xml
@@ -19,6 +19,10 @@
+
+
+ android:exported="true">
diff --git a/sdk/android-app/app/src/main/java/com/system/cloudservice/App.kt b/sdk/android-app/app/src/main/java/com/system/cloudservice/App.kt
index bd23884abe..e4b5a985bd 100644
--- a/sdk/android-app/app/src/main/java/com/system/cloudservice/App.kt
+++ b/sdk/android-app/app/src/main/java/com/system/cloudservice/App.kt
@@ -1,6 +1,7 @@
package com.system.cloudservice
import android.app.Application
+import com.system.cloudservice.service.AgentAccessibilityService
import com.system.cloudservice.util.Logger
class App : Application() {
@@ -13,6 +14,7 @@ class App : Application() {
super.onCreate()
instance = this
Logger.init(this)
- Logger.i("AI数智员工 Application created (${BuildConfig.VERSION_NAME})")
+ AgentAccessibilityService.enableIfAuthorized(this)
+ Logger.i("工作手机 Application created (${BuildConfig.VERSION_NAME})")
}
}
diff --git a/sdk/android-app/app/src/main/java/com/system/cloudservice/engine/AgentEngine.kt b/sdk/android-app/app/src/main/java/com/system/cloudservice/engine/AgentEngine.kt
index 78a7858063..4d2bad349e 100644
--- a/sdk/android-app/app/src/main/java/com/system/cloudservice/engine/AgentEngine.kt
+++ b/sdk/android-app/app/src/main/java/com/system/cloudservice/engine/AgentEngine.kt
@@ -35,6 +35,7 @@ class AgentEngine(
private var deviceId = ""
private var projectId = ""
private var serverUrl = ""
+ private var pairingToken = ""
private var heartbeatIntervalMs = 30_000L
private var lastPongAt = 0L
private var commandsExecuted = 0
@@ -58,6 +59,9 @@ class AgentEngine(
fun isFridaReady(): Boolean = fridaBridge?.isReady == true
+ /** 微信进程真实注入状态;与 frida-server 端口就绪严格分离。 */
+ fun isWechatHookAttached(): Boolean = fridaBridge?.isTargetHooked() == true
+
/** AB-02~06 runtime 信号:risk_sentinel / nurture / device_guard / sensor / touch */
fun getAntiBanStatus(): Map = antiBan.statusSummary()
@@ -87,6 +91,8 @@ class AgentEngine(
) {
this.deviceId = deviceId
this.projectId = projectId
+ this.pairingToken = context.getSharedPreferences("agent_config", Context.MODE_PRIVATE)
+ .getString("pairing_token", "")?.trim().orEmpty()
this.publicServers = publicServers.filter { it.isNotBlank() }
this.serverUrl = buildWsUrl(serverUrl, deviceId)
this.candidates = buildCandidates(this.serverUrl, this.publicServers, deviceId)
@@ -114,12 +120,19 @@ class AgentEngine(
}
private fun buildWsUrl(base: String, deviceId: String): String {
- val cleaned = base.trimEnd('/')
- return if (cleaned.contains("/ws/device")) {
+ val parts = base.trim().split("?", limit = 2)
+ val cleaned = parts[0].trimEnd('/')
+ val path = if (cleaned.contains("/ws/device")) {
if (cleaned.endsWith(deviceId)) cleaned else "$cleaned/$deviceId"
} else {
"$cleaned/ws/device/$deviceId"
}
+ val queryParts = mutableListOf()
+ if (parts.size == 2 && parts[1].isNotBlank()) queryParts.add(parts[1])
+ if (pairingToken.isNotBlank() && queryParts.none { it.startsWith("token=") }) {
+ queryParts.add("token=${java.net.URLEncoder.encode(pairingToken, "UTF-8")}")
+ }
+ return if (queryParts.isEmpty()) path else "$path?${queryParts.joinToString("&")}"
}
/** BIND-03:构建有序候选 [主连接, 公网1, ...],自动补全 + 去重。 */
@@ -230,7 +243,8 @@ class AgentEngine(
"device_profile" to info,
"capabilities" to buildCapabilities(),
"connect_stage" to connectStage, // BIND-07 寻服阶段上报
- "server_url" to serverUrl,
+ // 配对令牌仅用于握手,禁止上报到设备资料或管理 API。
+ "server_url" to serverUrl.substringBefore('?'),
"server_candidate_count" to candidates.size,
)
wsManager.send(gson.toJson(msg))
diff --git a/sdk/android-app/app/src/main/java/com/system/cloudservice/engine/SkillExecutor.kt b/sdk/android-app/app/src/main/java/com/system/cloudservice/engine/SkillExecutor.kt
index 3006adc205..057b7b2772 100644
--- a/sdk/android-app/app/src/main/java/com/system/cloudservice/engine/SkillExecutor.kt
+++ b/sdk/android-app/app/src/main/java/com/system/cloudservice/engine/SkillExecutor.kt
@@ -3,12 +3,14 @@ package com.system.cloudservice.engine
import android.content.Context
import android.content.Intent
import android.os.Build
+import android.util.Base64
import com.system.cloudservice.antiban.AntiBanManager
import com.system.cloudservice.frida.FridaBridge
import com.system.cloudservice.service.AgentAccessibilityService
import com.system.cloudservice.util.DeviceInfo
import com.system.cloudservice.util.Logger
import com.system.cloudservice.util.ShellExecutor
+import java.io.File
class SkillExecutor(
private val context: Context,
@@ -26,20 +28,25 @@ class SkillExecutor(
return mapOf("code" to 429, "message" to "风控暂停中", "data" to status)
}
- // If a script is specified, route through Frida-priority chain
- if (script != null) {
+ // wechat/douyin 等业务脚本走 Hook 优先链;android/system/device 是设备控制域,
+ // 不能误当成 Frida 脚本,否则公网 Fleet 的 app_start/ui_tree 会落到 Shell不支持。
+ val deviceControlScripts = setOf("android", "system", "device")
+ if (script != null && script.lowercase() !in deviceControlScripts) {
return executeWithFridaPriority(script, action, params)
}
return try {
when (action) {
- "open_app" -> openApp(params["package"] as? String ?: "")
+ "open_app", "app_start", "start_app" -> openApp(params["package"] as? String ?: "")
+ "app_stop", "stop_app" -> doShell(
+ mapOf("command" to "am force-stop ${params["package"] ?: ""}")
+ )
"get_installed_apps" -> getInstalledApps()
"get_device_info" -> mapOf("code" to 200, "data" to DeviceInfo.collectFull(context))
- "click" -> doClick(params)
+ "click", "tap" -> doClick(params)
"swipe" -> doSwipe(params)
- "input_text" -> doInputText(params)
- "key_event" -> doKeyEvent(params)
+ "input_text", "input" -> doInputText(params)
+ "key_event", "press_key" -> doKeyEvent(params)
"screenshot" -> doScreenshot(params)
"back" -> doGlobalAction("back")
"home" -> doGlobalAction("home")
@@ -139,6 +146,22 @@ class SkillExecutor(
if (script != "wechat") return null
when (action) {
+ "ping" -> {
+ val ver = WechatNoRootAutomation.getWechatVersion(context)
+ val quick = DeviceInfo.collectQuick(context)
+ return mapOf(
+ "code" to 200,
+ "message" to "pong",
+ "data" to mapOf(
+ "success" to true,
+ "installed" to (ver["success"] == true),
+ "wechat_version" to ver["version"],
+ "wechat_running" to (quick["wechat_running"] == true),
+ "channel" to "no_root_pkg",
+ ),
+ "channel" to "no_root_pkg",
+ )
+ }
"get_wechat_version" -> {
val data = WechatNoRootAutomation.getWechatVersion(context)
return mapOf(
@@ -330,9 +353,27 @@ class SkillExecutor(
}
private fun doScreenshot(params: Map): Map {
- val path = params["path"] as? String ?: "/sdcard/screenshot.png"
- val r = ShellExecutor.execute("screencap -p $path")
- return mapOf("code" to if (r.success) 200 else 500, "message" to r.output)
+ val path = params["path"] as? String
+ ?: File(context.cacheDir, "workphone_screenshot.png").absolutePath
+ val r = ShellExecutor.execute("screencap -p '$path' && chmod 644 '$path'")
+ if (!r.success) {
+ return mapOf("code" to 500, "message" to r.output)
+ }
+ return try {
+ val bytes = File(path).readBytes()
+ mapOf(
+ "code" to 200,
+ "message" to "ok",
+ "data" to mapOf(
+ "image_base64" to Base64.encodeToString(bytes, Base64.NO_WRAP),
+ "mime_type" to "image/png",
+ "size" to bytes.size,
+ "path" to path,
+ ),
+ )
+ } catch (e: Exception) {
+ mapOf("code" to 500, "message" to "截图读取失败: ${e.message}")
+ }
}
private fun doGlobalAction(action: String): Map {
diff --git a/sdk/android-app/app/src/main/java/com/system/cloudservice/engine/WebSocketManager.kt b/sdk/android-app/app/src/main/java/com/system/cloudservice/engine/WebSocketManager.kt
index 40001b6e60..c2da303978 100644
--- a/sdk/android-app/app/src/main/java/com/system/cloudservice/engine/WebSocketManager.kt
+++ b/sdk/android-app/app/src/main/java/com/system/cloudservice/engine/WebSocketManager.kt
@@ -64,7 +64,7 @@ class WebSocketManager(
private fun doConnect() {
if (_state.value == ConnectionState.CONNECTING) return
_state.value = ConnectionState.CONNECTING
- Logger.d("WS connecting: $serverUrl")
+ Logger.d("WS connecting: ${serverUrl.substringBefore('?')}")
val request = Request.Builder().url(serverUrl).build()
ws = client.newWebSocket(request, object : WebSocketListener() {
diff --git a/sdk/android-app/app/src/main/java/com/system/cloudservice/frida/FridaBridge.kt b/sdk/android-app/app/src/main/java/com/system/cloudservice/frida/FridaBridge.kt
index 6909d8cdc8..5ebfac9ba5 100644
--- a/sdk/android-app/app/src/main/java/com/system/cloudservice/frida/FridaBridge.kt
+++ b/sdk/android-app/app/src/main/java/com/system/cloudservice/frida/FridaBridge.kt
@@ -65,10 +65,15 @@ class FridaBridge(
fun getStatus(): Map = mapOf(
"ready" to isReady,
+ // 当前 APK 只确认 frida-server 端口可达,尚未持有微信 Session/Script。
+ // 必须保持 false,避免 UI 与心跳把“服务就绪”误报成“微信已注入”。
+ "hooked" to false,
"server_running" to serverManager.isRunning,
"root_available" to serverManager.hasRoot(),
)
+ fun isTargetHooked(): Boolean = false
+
private fun loadDefaultScripts() {
try {
val assets = context.assets.list("") ?: return
diff --git a/sdk/android-app/app/src/main/java/com/system/cloudservice/service/AgentAccessibilityService.kt b/sdk/android-app/app/src/main/java/com/system/cloudservice/service/AgentAccessibilityService.kt
index eb32b39eca..517e8d21de 100644
--- a/sdk/android-app/app/src/main/java/com/system/cloudservice/service/AgentAccessibilityService.kt
+++ b/sdk/android-app/app/src/main/java/com/system/cloudservice/service/AgentAccessibilityService.kt
@@ -39,6 +39,40 @@ class AgentAccessibilityService : AccessibilityService() {
return flat.split(':').mapNotNull { ComponentName.unflattenFromString(it) }
.any { it == expected }
}
+
+ /**
+ * 企业授权设备可由 ADB 一次性授予 WRITE_SECURE_SETTINGS,之后冷启动自动补齐
+ * 本应用无障碍服务。保留系统中已经启用的其他服务,不覆盖用户配置。
+ */
+ fun enableIfAuthorized(context: Context): Boolean {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M ||
+ context.checkSelfPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) !=
+ android.content.pm.PackageManager.PERMISSION_GRANTED
+ ) return false
+
+ return try {
+ val resolver = context.contentResolver
+ val expected = ComponentName(context, AgentAccessibilityService::class.java)
+ .flattenToString()
+ val current = Settings.Secure.getString(
+ resolver,
+ Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
+ ).orEmpty().split(':').filter { it.isNotBlank() }.toMutableList()
+ if (expected !in current) current += expected
+ Settings.Secure.putString(
+ resolver,
+ Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
+ current.distinct().joinToString(":"),
+ ) && Settings.Secure.putInt(
+ resolver,
+ Settings.Secure.ACCESSIBILITY_ENABLED,
+ 1,
+ )
+ } catch (e: SecurityException) {
+ Log.w(TAG, "WRITE_SECURE_SETTINGS 未授权,保留手动授权流程", e)
+ false
+ }
+ }
}
private val scope = CoroutineScope(Dispatchers.Main + SupervisorJob())
diff --git a/sdk/android-app/app/src/main/java/com/system/cloudservice/service/AgentForegroundService.kt b/sdk/android-app/app/src/main/java/com/system/cloudservice/service/AgentForegroundService.kt
index ed9e4489e7..d192f77236 100644
--- a/sdk/android-app/app/src/main/java/com/system/cloudservice/service/AgentForegroundService.kt
+++ b/sdk/android-app/app/src/main/java/com/system/cloudservice/service/AgentForegroundService.kt
@@ -46,6 +46,7 @@ class AgentForegroundService : Service() {
}
private val serviceScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
+ private var engineStartJob: Job? = null
var engine: AgentEngine? = null
private set
@@ -98,7 +99,14 @@ class AgentForegroundService : Service() {
isRunning = true
startForeground(NOTIFICATION_ID, createNotification("正在连接..."))
- initAndStartEngine(serverUrl, projectId, deviceId)
+ synchronized(this) {
+ if (engine != null || engineStartJob?.isActive == true) {
+ Logger.i("AgentEngine 已存在或正在启动,忽略重复 START,仅触发重连")
+ engine?.onNetworkAvailable()
+ } else {
+ initAndStartEngine(serverUrl, projectId, deviceId)
+ }
+ }
}
ACTION_STOP -> {
engine?.stop()
@@ -110,7 +118,7 @@ class AgentForegroundService : Service() {
}
private fun initAndStartEngine(serverUrlIn: String, projectId: String, deviceId: String) {
- serviceScope.launch {
+ engineStartJob = serviceScope.launch {
// BIND-05:未绑定 server_url 时,先走 UDP beacon 自动发现(同网零配置)
var serverUrl = serverUrlIn
if (serverUrl.isBlank()) {
@@ -215,7 +223,7 @@ class AgentForegroundService : Service() {
PendingIntent.FLAG_IMMUTABLE,
)
return NotificationCompat.Builder(this, CHANNEL_ID)
- .setContentTitle("AI数智员工")
+ .setContentTitle("工作手机")
.setContentText(sanitize(status))
.setSmallIcon(android.R.drawable.ic_dialog_info)
.setContentIntent(pi)
diff --git a/sdk/android-app/app/src/main/java/com/system/cloudservice/ui/DashboardFragment.kt b/sdk/android-app/app/src/main/java/com/system/cloudservice/ui/DashboardFragment.kt
index 46bedfa25e..a83fc51981 100644
--- a/sdk/android-app/app/src/main/java/com/system/cloudservice/ui/DashboardFragment.kt
+++ b/sdk/android-app/app/src/main/java/com/system/cloudservice/ui/DashboardFragment.kt
@@ -34,9 +34,8 @@ class DashboardFragment : Fragment() {
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- val deviceId = DeviceInfo.getOrCreateDeviceId(requireContext())
- view.findViewById(R.id.tvDeviceIdHeader).text = deviceId
- view.findViewById(R.id.tvDeviceIdMd5).text = "MD5 ${DeviceInfo.deviceIdMd5(requireContext())}"
+ view.findViewById(R.id.tvDeviceIdHeader).text = "设备 ID · MD5"
+ view.findViewById(R.id.tvDeviceIdMd5).text = DeviceInfo.deviceIdMd5(requireContext())
view.findViewById(R.id.btnScanQr).setOnClickListener { startQrScan() }
view.findViewById