package com.stand.standapp import android.content.Context import android.os.Handler import android.os.Looper import android.webkit.JavascriptInterface import android.widget.Toast import android.print.PrintAttributes import android.print.PrintManager import com.stand.standapp.R import com.stand.standapp.utils.LogManager import com.stand.standapp.printer.PrinterManager import timber.log.Timber import com.example.kingway.ptt.SendAtUtil import org.json.JSONObject class AndroidInterface(private val activity: MainActivity) { private var isSpeaking = false /** * PTT 登录 */ @JavascriptInterface fun pttLogin(account: String, pwd: String, ip: String) { SendAtUtil.toLogin(account, pwd, ip, 1, 1) } /** * PTT 开始说话 (带状态保护) */ @JavascriptInterface fun pttSpeakPlay() { if (!isSpeaking) { isSpeaking = true SendAtUtil.speakPlay() Timber.tag("PTT").d("Speak Play") } } /** * PTT 停止说话 */ @JavascriptInterface fun pttSpeakRelease() { if (isSpeaking) { isSpeaking = false SendAtUtil.speakRelease() Timber.tag("PTT").d("Speak Release") } } /** * 强制释放 PTT (用于异常恢复) */ @JavascriptInterface fun pttForceRelease() { isSpeaking = false SendAtUtil.speakRelease() Timber.tag("PTT").d("Force Release") } /** * PTT 获取群组 */ @JavascriptInterface fun pttGetGroupInfo() { SendAtUtil.getGroupInfo() } /** * PTT 获取成员 */ @JavascriptInterface fun pttGetGroupMemberInfo(gid: String) { SendAtUtil.getGroupMemberInfo(gid) } /** * PTT 切换群组 */ @JavascriptInterface fun pttJumpGroup(gid: String) { SendAtUtil.jumpGroup(gid) } /** * JS 调用:获取打印机当前连接状态 */ @JavascriptInterface fun getPrinterStatus(): String { return try { val json = JSONObject() val connected = PrinterManager.isConnected() json.put("connected", connected) json.put("hasPaper", PrinterManager.isPaperReady()) json.put("port", AppConfig.getSerialPort(activity)) json.toString() } catch (e: Exception) { "{\"connected\":false, \"hasPaper\":false, \"error\":\"${e.message}\"}" } } /** * JS 调用:通用打印文本 */ @JavascriptInterface fun printText(text: String?, size: Int, align: Int, space: Int) { if (text.isNullOrEmpty()) return try { PrinterManager.getFactory()?.let { if (it.isConnection) { it.PrintText(text, size, align, space) } } } catch (e: Throwable) { Timber.tag("JS_Interface").e(e, "printText failed") } } /** * JS 调用:打印条码 */ @JavascriptInterface fun printBarcode(data: String?, size: Int, height: Int, align: Int, text_pos: Int, type: Int) { if (data.isNullOrEmpty()) return try { PrinterManager.getFactory()?.PrintBarcode(data, size, height, align, text_pos, type) } catch (e: Throwable) { Timber.tag("JS_Interface").e(e, "printBarcode failed") } } /** * JS 调用:打印二维码 */ @JavascriptInterface fun printQR(data: String?, size: Int, l_m: Int) { if (data.isNullOrEmpty()) return try { PrinterManager.getFactory()?.PrintQR(data, size, l_m) } catch (e: Throwable) { Timber.tag("JS_Interface").e(e, "printQR failed") } } /** * JS 调用:打印图片 (从 Assets 获取) */ @JavascriptInterface fun printImage(fileName: String?, width: Int, align: Int, mode: Int) { if (fileName.isNullOrEmpty()) return try { val stream = activity.assets.open(fileName) val bitmap = android.graphics.BitmapFactory.decodeStream(stream) PrinterManager.getFactory()?.PrintImage(bitmap, width, align, mode) stream.close() } catch (e: Exception) { Timber.tag("JS_Interface").e(e, "printImage failed: $fileName") } } /** * JS 调用:分栏打印 (4列) */ @JavascriptInterface fun printColumns(c1: String?, p1: Int, c2: String?, p2: Int, c3: String?, p3: Int, c4: String?, p4: Int) { try { PrinterManager.getFactory()?.Printcolumncontent(c1 ?: "", p1, c2 ?: "", p2, c3 ?: "", p3, c4 ?: "", p4) } catch (e: Throwable) { Timber.tag("JS_Interface").e(e, "printColumns failed") } } /** * JS 调用:分栏打印 (带行高) */ @JavascriptInterface fun printColumnsWithHeight(c1: String?, p1: Int, c2: String?, p2: Int, c3: String?, p3: Int, c4: String?, p4: Int, height: Int) { try { PrinterManager.getFactory()?.Printcolumncontent(c1 ?: "", p1, c2 ?: "", p2, c3 ?: "", p3, c4 ?: "", p4, height) } catch (e: Throwable) { Timber.tag("JS_Interface").e(e, "printColumnsWithHeight failed") } } /** * JS 调用:切纸 */ @JavascriptInterface fun cutPaper(allCut: Boolean) { try { PrinterManager.getFactory()?.let { if (it.isConnection) { if (allCut) it.PaperAllCut() else it.PaperCut() } } } catch (e: Throwable) { Timber.tag("JS_Interface").e(e, "cutPaper failed") } } /** * JS 调用:打印自检页 */ @JavascriptInterface fun printTestPage() { try { PrinterManager.getFactory()?.PrintTestPage() } catch (e: Throwable) { Timber.tag("JS_Interface").e(e, "printTestPage failed") } } /** * JS 调用:主动检查纸张状态 (会触发回调) */ @JavascriptInterface fun checkPaper() { try { PrinterManager.getFactory()?.Check_Paper() } catch (e: Throwable) { Timber.tag("JS_Interface").e(e, "checkPaper failed") } } /** * JS 调用:压缩并导出日志 * 返回压缩后的文件路径 */ @JavascriptInterface fun exportLogs(): String { val zipPath = LogManager.zipLogs(activity) if (zipPath != null) { Handler(Looper.getMainLooper()).post { Toast.makeText(activity, "日志已打包: $zipPath", Toast.LENGTH_LONG).show() } return zipPath } else { Handler(Looper.getMainLooper()).post { Toast.makeText(activity, "暂无日志或打包失败", Toast.LENGTH_SHORT).show() } return "" } } /** * JS 调用:直接上传日志到后台 */ @JavascriptInterface fun uploadLogs() { LogManager.uploadLogs(activity) { success, message -> Handler(Looper.getMainLooper()).post { Toast.makeText(activity, if (success) "日志上传成功" else "上传失败: $message", Toast.LENGTH_SHORT).show() } } } /** * JS 调用:标准系统打印 (PDF/网络打印机/AirPrint) * 适用于打印精美的 A4 单据或页面 */ @JavascriptInterface fun printPage() { Handler(Looper.getMainLooper()).post { try { val printManager = activity.getSystemService(Context.PRINT_SERVICE) as PrintManager // 获取 WebView 的打印适配器 val webView = activity.getAgentWeb()?.webCreator?.getWebView() if (webView == null) { Toast.makeText(activity, "WebView 未就绪", Toast.LENGTH_SHORT).show() return@post } val printAdapter = webView.createPrintDocumentAdapter("Document") val jobName = activity.getString(R.string.app_name) + " Print Job" printManager.print(jobName, printAdapter, PrintAttributes.Builder().build()) } catch (e: Exception) { Timber.tag("Print").e(e, "系统打印失败") Toast.makeText(activity, "打印服务启动失败", Toast.LENGTH_SHORT).show() } } } /** * JS 调用:原始数据打印 * 适用于热敏小票打印机 (蓝牙、USB、网络) * @param data String 可以是 JSON 字符串,包含打印的指令或文本 */ @JavascriptInterface fun printRawData(data: String?) { if (data.isNullOrEmpty()) return try { Timber.tag("Print").d("收到 H5 原始打印数据: $data") Handler(Looper.getMainLooper()).post { try { Toast.makeText(activity, "H5指令打印: $data", Toast.LENGTH_SHORT).show() } catch (e: Exception) { Timber.tag("Print").e(e, "Toast显示失败") } } } catch (e: Exception) { Timber.tag("Print").e(e, "指令解析失败") } } /** * JS 调用:在线升级 (手动触发检查) */ @JavascriptInterface fun startUpdate(apkUrl: String?) { Handler(Looper.getMainLooper()).post { try { UpdateManager(activity).checkUpdate() } catch (e: Exception) { Timber.tag("Update").e(e, "启动更新检查失败") } } } /** * JS 调用:退出应用 */ @JavascriptInterface fun exitApp() { Handler(Looper.getMainLooper()).post { try { activity.showExitDialog() } catch (e: Exception) { Timber.tag("App").e(e, "显示退出弹窗失败") activity.finish() } } } /** * 获取版本号给 H5 判断是否需要升级 */ @JavascriptInterface fun getVersionName(): String { return try { val packageInfo = activity.packageManager.getPackageInfo(activity.packageName, 0) packageInfo.versionName ?: "1.0.0" } catch (e: Exception) { "1.0.0" } } /** * JS 调用:获取“关于”信息 */ @JavascriptInterface fun getAboutInfo(): String { return try { val packageInfo = activity.packageManager.getPackageInfo(activity.packageName, 0) val json = JSONObject() json.put("appName", activity.getString(R.string.app_name)) json.put("versionName", packageInfo.versionName) json.put("model", android.os.Build.MODEL) json.put("osVersion", android.os.Build.VERSION.RELEASE) // 增加 CPU 架构信息 val abis = android.os.Build.SUPPORTED_ABIS json.put("cpuAbi", abis.joinToString(",")) // 增加当前进程是否为 64 位的判断 val is64Bit = if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) { android.os.Process.is64Bit() } else { abis.any { it.contains("64") } } json.put("is64Bit", is64Bit) json.toString() } catch (e: Exception) { "{ \"appName\": \"消防值守台\", \"versionName\": \"1.0.0\" }" } } /** * JS 调用:保存消息记录 */ @JavascriptInterface fun saveMessage(message: String?) { if (message.isNullOrEmpty()) return try { val prefs = activity.getSharedPreferences("msg_records", Context.MODE_PRIVATE) val currentMsgs = prefs.getString("history", "") ?: "" val updatedMsgs = if (currentMsgs.isEmpty()) message else "$message|---|$currentMsgs" // 限制保存最近5000条 val list = updatedMsgs.split("|---|").take(5000) prefs.edit().putString("history", list.joinToString("|---|")).apply() } catch (e: Exception) { Timber.tag("Storage").e(e, "消息保存异常") } } /** * JS 调用:获取消息记录 */ @JavascriptInterface fun getMessages(): String { return try { val prefs = activity.getSharedPreferences("msg_records", Context.MODE_PRIVATE) prefs.getString("history", "") ?: "" } catch (e: Exception) { "" } } /** * JS 调用:清空消息记录 */ @JavascriptInterface fun clearMessages() { try { activity.getSharedPreferences("msg_records", Context.MODE_PRIVATE).edit().clear().apply() Handler(Looper.getMainLooper()).post { Toast.makeText(activity, "记录已清空", Toast.LENGTH_SHORT).show() } } catch (e: Exception) { Timber.tag("Storage").e(e, "清空记录异常") } } /** * JS 回调:告知 App 页面加载完成 */ @JavascriptInterface fun onPageFinished() { Handler(Looper.getMainLooper()).post { try { activity.cancelTimeoutTimer() activity.findViewById(R.id.loading_layout)?.visibility = android.view.View.GONE } catch (e: Exception) { Timber.tag("UI").e(e, "隐藏加载布局失败") } } } }