StandAPP/app/src/main/java/com/stand/standapp/AndroidInterface.kt

517 lines
19 KiB
Kotlin
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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()
}
}