517 lines
19 KiB
Kotlin
517 lines
19 KiB
Kotlin
package com.stand.standapp
|
||
|
||
import android.content.Context
|
||
import android.os.Handler
|
||
import android.os.Looper
|
||
import android.widget.Toast
|
||
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.JSONArray
|
||
import org.json.JSONObject
|
||
|
||
class AndroidInterface(private val activity: MainActivity) {
|
||
|
||
private var isSpeaking = false
|
||
private var lastSpeakTime = 0L
|
||
private val MIN_SPEAK_DURATION = 200L
|
||
|
||
/**
|
||
* JS bridge 调度入口
|
||
* @param method JS 调用的方法名
|
||
* @param argsJson JSON 数组格式的参数
|
||
* @return 返回值(JSON 字符串或空串)
|
||
*/
|
||
fun dispatch(method: String, argsJson: String): String {
|
||
return try {
|
||
val args = if (argsJson.isNotEmpty()) JSONArray(argsJson) else JSONArray()
|
||
when (method) {
|
||
"pttSpeakPlay" -> { pttSpeakPlay(); "" }
|
||
"pttGetLoginStatus" -> pttGetLoginStatus()
|
||
"pttGetLoginId" -> pttGetLoginId()
|
||
"pttGetCurrentGroup" -> pttGetCurrentGroup()
|
||
"pttSpeakRelease" -> { pttSpeakRelease(); "" }
|
||
"pttForceRelease" -> { pttForceRelease(); "" }
|
||
"pttGetGroupInfo" -> { pttGetGroupInfo(); "" }
|
||
"pttGetGroupMemberInfo" -> { pttGetGroupMemberInfo(args.optString(0, "")); "" }
|
||
"simulatePttConflict" -> { simulatePttConflict(); "" }
|
||
"pttSendAtCmd" -> { pttSendAtCmd(args.optString(0, "")); "" }
|
||
"pttJumpGroup" -> { pttJumpGroup(args.optString(0, "")); "" }
|
||
"getPrinterStatus" -> getPrinterStatus()
|
||
"printText" -> {
|
||
printText(optStringOrNull(args, 0), args.optInt(1, 0), args.optInt(2, 0), args.optInt(3, 0)); ""
|
||
}
|
||
"printBarcode" -> {
|
||
printBarcode(optStringOrNull(args, 0), args.optInt(1, 0), args.optInt(2, 0), args.optInt(3, 0), args.optInt(4, 0), args.optInt(5, 0)); ""
|
||
}
|
||
"printQR" -> {
|
||
printQR(optStringOrNull(args, 0), args.optInt(1, 0), args.optInt(2, 0)); ""
|
||
}
|
||
"printImage" -> {
|
||
printImage(optStringOrNull(args, 0), args.optInt(1, 0), args.optInt(2, 0), args.optInt(3, 0)); ""
|
||
}
|
||
"printColumns" -> {
|
||
if (args.length() >= 8) {
|
||
printColumns(optStringOrNull(args, 0), args.optInt(1, 0), optStringOrNull(args, 2), args.optInt(3, 0),
|
||
optStringOrNull(args, 4), args.optInt(5, 0), optStringOrNull(args, 6), args.optInt(7, 0))
|
||
}
|
||
""
|
||
}
|
||
"printColumnsWithHeight" -> {
|
||
if (args.length() >= 9) {
|
||
printColumnsWithHeight(optStringOrNull(args, 0), args.optInt(1, 0), optStringOrNull(args, 2), args.optInt(3, 0),
|
||
optStringOrNull(args, 4), args.optInt(5, 0), optStringOrNull(args, 6), args.optInt(7, 0), args.optInt(8, 0))
|
||
}
|
||
""
|
||
}
|
||
"cutPaper" -> { cutPaper(args.optBoolean(0, false)); "" }
|
||
"printTestPage" -> { printTestPage(); "" }
|
||
"checkPaper" -> { checkPaper(); "" }
|
||
"exportLogs" -> exportLogs()
|
||
"uploadLogs" -> { uploadLogs(); "" }
|
||
"printPage" -> { printPage(); "" }
|
||
"printRawData" -> { printRawData(optStringOrNull(args, 0)); "" }
|
||
"startUpdate" -> { startUpdate(optStringOrNull(args, 0)); "" }
|
||
"exitApp" -> { exitApp(); "" }
|
||
"forceExit" -> { forceExit(); "" }
|
||
"getVersionName" -> getVersionName()
|
||
"getAboutInfo" -> getAboutInfo()
|
||
"saveMessage" -> { saveMessage(optStringOrNull(args, 0)); "" }
|
||
"getMessages" -> getMessages()
|
||
"clearMessages" -> { clearMessages(); "" }
|
||
"onPageFinished" -> { onPageFinished(); "" }
|
||
"showRestartDialog" -> { showRestartDialog(args.optString(0, ""), args.optString(1, "提示")); "" }
|
||
"showJsonDialog" -> { showJsonDialog(args.optString(0, "")); "" }
|
||
"getTempCallStatus" -> getTempCallStatus()
|
||
"maximizeTempCall" -> { maximizeTempCall(); "" }
|
||
"logout" -> { logout(); "" }
|
||
else -> {
|
||
Timber.tag("Bridge").w("Unknown method: $method")
|
||
""
|
||
}
|
||
}
|
||
} catch (e: Exception) {
|
||
Timber.tag("Bridge").e(e, "Dispatch error: $method")
|
||
""
|
||
}
|
||
}
|
||
|
||
private fun optStringOrNull(args: JSONArray, index: Int): String? {
|
||
return if (index < args.length() && !args.isNull(index)) args.getString(index) else null
|
||
}
|
||
|
||
fun pttSpeakPlay() {
|
||
if (!isSpeaking) {
|
||
isSpeaking = true
|
||
lastSpeakTime = System.currentTimeMillis()
|
||
SendAtUtil.speakPlay()
|
||
Timber.tag("PTT").d("Speak Play")
|
||
MainActivity.executeJs("updatePttTalkStatus(true)")
|
||
} else {
|
||
Timber.tag("PTT").w("Speak Play ignored: already speaking")
|
||
}
|
||
}
|
||
|
||
fun pttGetLoginStatus(): String {
|
||
return com.example.kingway.ptt.CallBackResolution.getPttLoginStatus()
|
||
}
|
||
|
||
fun pttGetLoginId(): String {
|
||
return com.example.kingway.ptt.CallBackResolution.getPttLoginId()
|
||
}
|
||
|
||
fun pttGetCurrentGroup(): String {
|
||
return try {
|
||
val json = JSONObject()
|
||
json.put("groupId", com.example.kingway.ptt.CallBackResolution.getCurrentGroupId())
|
||
json.put("groupName", com.example.kingway.ptt.CallBackResolution.getCurrentGroupName())
|
||
json.toString()
|
||
} catch (e: Exception) {
|
||
Timber.tag("PTT").e(e, "pttGetCurrentGroup failed")
|
||
"{\"groupId\":\"\",\"groupName\":\"\"}"
|
||
}
|
||
}
|
||
|
||
fun pttSpeakRelease() {
|
||
Timber.tag("PTT").d("pttSpeakRelease called, isSpeaking=%s", isSpeaking)
|
||
if (isSpeaking) {
|
||
val now = System.currentTimeMillis()
|
||
val duration = now - lastSpeakTime
|
||
|
||
if (duration < MIN_SPEAK_DURATION) {
|
||
val delay = MIN_SPEAK_DURATION - duration
|
||
Timber.tag("PTT").d("Speak duration too short ($duration ms), delaying release by $delay ms")
|
||
Handler(Looper.getMainLooper()).postDelayed({
|
||
realRelease()
|
||
}, delay)
|
||
} else {
|
||
realRelease()
|
||
}
|
||
}
|
||
}
|
||
|
||
private fun realRelease() {
|
||
if (isSpeaking) {
|
||
isSpeaking = false
|
||
SendAtUtil.speakRelease()
|
||
Timber.tag("PTT").d("Real Speak Release")
|
||
MainActivity.executeJs("updatePttTalkStatus(false)")
|
||
} else {
|
||
Timber.tag("PTT").w("realRelease skipped: already released")
|
||
}
|
||
}
|
||
|
||
fun pttForceRelease() {
|
||
val wasSpeaking = isSpeaking
|
||
isSpeaking = false
|
||
SendAtUtil.speakRelease()
|
||
Timber.tag("PTT").d("Force Release (wasSpeaking=%s)", wasSpeaking)
|
||
}
|
||
|
||
fun pttGetGroupInfo() {
|
||
Timber.tag("PTT").d("pttGetGroupInfo called")
|
||
SendAtUtil.getGroupInfo()
|
||
}
|
||
|
||
fun pttGetGroupMemberInfo(gid: String) {
|
||
Timber.tag("PTT").d("pttGetGroupMemberInfo: gid=%s", gid)
|
||
SendAtUtil.getGroupMemberInfo(gid)
|
||
}
|
||
|
||
fun simulatePttConflict() {
|
||
Timber.tag("PTT").w("simulatePttConflict: showing conflict dialog via JS bridge")
|
||
Handler(Looper.getMainLooper()).post {
|
||
MainActivity.showPttConflictDialog()
|
||
}
|
||
}
|
||
|
||
fun pttSendAtCmd(cmd: String) {
|
||
Timber.tag("PTT").d("pttSendAtCmd: %s", cmd)
|
||
SendAtUtil.sendRawCmd(cmd)
|
||
}
|
||
|
||
fun pttJumpGroup(gid: String) {
|
||
Timber.tag("PTT").d("pttJumpGroup: gid=%s", gid)
|
||
SendAtUtil.jumpGroup(gid)
|
||
if (gid.isNotEmpty()) {
|
||
AppConfig.setLastGroupId(activity, gid)
|
||
}
|
||
}
|
||
|
||
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}\"}"
|
||
}
|
||
}
|
||
|
||
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")
|
||
}
|
||
}
|
||
|
||
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")
|
||
}
|
||
}
|
||
|
||
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")
|
||
}
|
||
}
|
||
|
||
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")
|
||
}
|
||
}
|
||
|
||
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")
|
||
}
|
||
}
|
||
|
||
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")
|
||
}
|
||
}
|
||
|
||
fun cutPaper(allCut: Boolean) {
|
||
try {
|
||
if (allCut){
|
||
PrinterManager.getFactory()?.PaperAllCut();
|
||
}else{
|
||
PrinterManager.getFactory()?.PaperCut();
|
||
}
|
||
} catch (e: Throwable) {
|
||
Timber.tag("JS_Interface").e(e, "cutPaper failed")
|
||
}
|
||
}
|
||
|
||
fun printTestPage() {
|
||
try {
|
||
PrinterManager.getFactory()?.PrintTestPage()
|
||
} catch (e: Throwable) {
|
||
Timber.tag("JS_Interface").e(e, "printTestPage failed")
|
||
}
|
||
}
|
||
|
||
fun checkPaper() {
|
||
try {
|
||
PrinterManager.getFactory()?.Check_Paper()
|
||
} catch (e: Throwable) {
|
||
Timber.tag("JS_Interface").e(e, "checkPaper failed")
|
||
}
|
||
}
|
||
|
||
fun exportLogs(): String {
|
||
val zipPath = LogManager.zipLogs(activity)
|
||
if (zipPath != null) {
|
||
Handler(Looper.getMainLooper()).post {
|
||
Toast.makeText(activity.applicationContext, "日志已打包: $zipPath", Toast.LENGTH_LONG).show()
|
||
}
|
||
return zipPath
|
||
} else {
|
||
Handler(Looper.getMainLooper()).post {
|
||
Toast.makeText(activity.applicationContext, "暂无日志或打包失败", Toast.LENGTH_SHORT).show()
|
||
}
|
||
return ""
|
||
}
|
||
}
|
||
|
||
fun uploadLogs() {
|
||
LogManager.uploadLogs(activity) { success, message ->
|
||
Handler(Looper.getMainLooper()).post {
|
||
Toast.makeText(activity.applicationContext, if (success) "日志上传成功" else "上传失败: $message", Toast.LENGTH_SHORT).show()
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 系统打印(GeckoView 不支持 WebView.createPrintDocumentAdapter)
|
||
* 如需此功能,需用 GeckoSession 生成 PDF 后打印
|
||
*/
|
||
fun printPage() {
|
||
Handler(Looper.getMainLooper()).post {
|
||
Toast.makeText(activity.applicationContext, "GeckoView 暂不支持系统打印,请使用热敏打印功能", Toast.LENGTH_SHORT).show()
|
||
}
|
||
}
|
||
|
||
fun printRawData(data: String?) {
|
||
if (data.isNullOrEmpty()) return
|
||
try {
|
||
Timber.tag("Print").d("收到 H5 原始打印数据: $data")
|
||
Handler(Looper.getMainLooper()).post {
|
||
try {
|
||
Toast.makeText(activity.applicationContext, "H5指令打印: $data", Toast.LENGTH_SHORT).show()
|
||
} catch (e: Exception) {
|
||
Timber.tag("Print").e(e, "Toast显示失败")
|
||
}
|
||
}
|
||
} catch (e: Exception) {
|
||
Timber.tag("Print").e(e, "指令解析失败")
|
||
}
|
||
}
|
||
|
||
fun startUpdate(apkUrl: String?) {
|
||
Handler(Looper.getMainLooper()).post {
|
||
try {
|
||
UpdateManager(activity).checkUpdate()
|
||
} catch (e: Exception) {
|
||
Timber.tag("Update").e(e, "启动更新检查失败")
|
||
}
|
||
}
|
||
}
|
||
|
||
fun exitApp() {
|
||
Handler(Looper.getMainLooper()).post {
|
||
try {
|
||
activity.showExitDialog()
|
||
} catch (e: Exception) {
|
||
Timber.tag("App").e(e, "显示退出弹窗失败")
|
||
activity.finish()
|
||
}
|
||
}
|
||
}
|
||
|
||
fun forceExit() {
|
||
Handler(Looper.getMainLooper()).post {
|
||
try {
|
||
com.stand.standapp.utils.AppKiller.killApp(activity)
|
||
} catch (e: Exception) {
|
||
Timber.tag("App").e(e, "强制退出失败")
|
||
activity.finish()
|
||
}
|
||
}
|
||
}
|
||
|
||
fun getVersionName(): String {
|
||
return try {
|
||
val packageInfo = activity.packageManager.getPackageInfo(activity.packageName, 0)
|
||
packageInfo.versionName ?: "1.0.0"
|
||
} catch (e: Exception) {
|
||
Timber.tag("AndroidInterface").e(e, "getVersionName failed")
|
||
"1.0.0"
|
||
}
|
||
}
|
||
|
||
fun getAboutInfo(): String {
|
||
return try {
|
||
val packageInfo = activity.packageManager.getPackageInfo(activity.packageName, 0)
|
||
val json = com.stand.standapp.utils.DeviceInfoUtils.getDeviceInfoJson(activity)
|
||
json.put("appName", activity.getString(R.string.app_name))
|
||
json.put("versionName", packageInfo.versionName ?: "1.0.0")
|
||
json.toString()
|
||
} catch (e: Exception) {
|
||
Timber.tag("AndroidInterface").e(e, "getAboutInfo failed")
|
||
"{ \"appName\": \"消防值守台\", \"versionName\": \"1.0.0\" }"
|
||
}
|
||
}
|
||
|
||
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"
|
||
val list = updatedMsgs.split("|---|").take(5000)
|
||
prefs.edit().putString("history", list.joinToString("|---|")).apply()
|
||
} catch (e: Exception) {
|
||
Timber.tag("Storage").e(e, "消息保存异常")
|
||
}
|
||
}
|
||
|
||
fun getMessages(): String {
|
||
return try {
|
||
val prefs = activity.getSharedPreferences("msg_records", Context.MODE_PRIVATE)
|
||
prefs.getString("history", "") ?: ""
|
||
} catch (e: Exception) {
|
||
Timber.tag("Storage").e(e, "getMessages failed")
|
||
""
|
||
}
|
||
}
|
||
|
||
fun clearMessages() {
|
||
try {
|
||
activity.getSharedPreferences("msg_records", Context.MODE_PRIVATE).edit().clear().apply()
|
||
Handler(Looper.getMainLooper()).post {
|
||
Toast.makeText(activity.applicationContext, "记录已清空", Toast.LENGTH_SHORT).show()
|
||
}
|
||
} catch (e: Exception) {
|
||
Timber.tag("Storage").e(e, "清空记录异常")
|
||
}
|
||
}
|
||
|
||
fun onPageFinished() {
|
||
Handler(Looper.getMainLooper()).post {
|
||
try {
|
||
activity.cancelTimeoutTimer()
|
||
activity.findViewById<android.view.View>(R.id.loading_layout)?.visibility = android.view.View.GONE
|
||
|
||
// 正式界面模式下,页面加载完成后传递登录数据给前端
|
||
val isOfficial = activity.isOfficialMode()
|
||
val data = activity.getLoginData()
|
||
Timber.tag("AndroidInterface").d("onPageFinished: isOfficial=$isOfficial, data=$data")
|
||
if (isOfficial && data.isNotEmpty()) {
|
||
val escaped = data.replace("'", "\\'").replace("\n", "\\n")
|
||
com.stand.standapp.MainActivity.executeJs("if(window.appLogin){window.appLogin('$escaped');}")
|
||
Timber.tag("AndroidInterface").d("Called appLogin")
|
||
}
|
||
|
||
// 页面加载并初始化完成后触发状态
|
||
com.stand.standapp.MainActivity.onPageLoadFinished()
|
||
} catch (e: Exception) {
|
||
Timber.tag("UI").e(e, "隐藏加载布局失败")
|
||
}
|
||
}
|
||
}
|
||
|
||
fun showRestartDialog(message: String, title: String) {
|
||
Timber.tag("AndroidInterface").w("showRestartDialog from JS: title=%s msg=%s", title, message)
|
||
Handler(Looper.getMainLooper()).post {
|
||
com.stand.standapp.MainActivity.showPttUpdateDialog(message, title)
|
||
}
|
||
}
|
||
|
||
fun showJsonDialog(jsonStr: String) {
|
||
Handler(Looper.getMainLooper()).post {
|
||
try {
|
||
val formatted = try {
|
||
val obj = org.json.JSONObject(jsonStr)
|
||
obj.toString(2)
|
||
} catch (_: Exception) {
|
||
jsonStr
|
||
}
|
||
androidx.appcompat.app.AlertDialog.Builder(activity)
|
||
.setTitle("JSON 数据")
|
||
.setMessage(formatted)
|
||
.setPositiveButton("确认", null)
|
||
.show()
|
||
} catch (e: Exception) {
|
||
Timber.tag("UI").e(e, "showJsonDialog error")
|
||
}
|
||
}
|
||
}
|
||
|
||
fun getTempCallStatus(): String {
|
||
return try {
|
||
val json = JSONObject()
|
||
json.put("isOpen", TempCallActivity.isOpen())
|
||
json.put("isMinimized", TempCallActivity.isMinimizedState())
|
||
json.toString()
|
||
} catch (e: Exception) {
|
||
"{\"isOpen\":false,\"isMinimized\":false}"
|
||
}
|
||
}
|
||
|
||
fun maximizeTempCall() {
|
||
Handler(Looper.getMainLooper()).post {
|
||
try {
|
||
TempCallActivity.maximize()
|
||
} catch (e: Exception) {
|
||
Timber.tag("TempCall").e(e, "maximizeTempCall error")
|
||
}
|
||
}
|
||
}
|
||
|
||
fun logout() {
|
||
activity.logout()
|
||
}
|
||
}
|