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

442 lines
13 KiB
Kotlin

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<android.view.View>(R.id.loading_layout)?.visibility = android.view.View.GONE
} catch (e: Exception) {
Timber.tag("UI").e(e, "隐藏加载布局失败")
}
}
}
}