442 lines
13 KiB
Kotlin
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, "隐藏加载布局失败")
|
|
}
|
|
}
|
|
}
|
|
}
|