diff --git a/app/build.gradle.kts b/app/build.gradle.kts index abda6f1..fd9f9ff 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,25 +1,47 @@ import java.text.SimpleDateFormat import java.util.Date +import java.util.Properties +import java.io.File plugins { alias(libs.plugins.android.application) alias(libs.plugins.kotlin.compose) } +// 自动版本号管理 +val versionPropsFile = file("version.properties") +fun getVersionCode(): Int { + val props = Properties() + if (versionPropsFile.exists()) { + props.load(versionPropsFile.inputStream()) + } + val code = props.getProperty("VERSION_CODE", "1").toInt() + props.setProperty("VERSION_CODE", (code + 1).toString()) + props.store(versionPropsFile.outputStream(), null) + return code +} + +fun getVersionName(): String { + val props = Properties() + if (versionPropsFile.exists()) { + props.load(versionPropsFile.inputStream()) + } + return props.getProperty("VERSION_NAME", "1.2") +} + +val autoVersionCode = getVersionCode() +val autoVersionName = getVersionName() + android { namespace = "com.example.ops_android" - compileSdk { - version = release(36) { - minorApiLevel = 1 - } - } + compileSdk = 36 defaultConfig { applicationId = "com.example.ops_android" minSdk = 24 targetSdk = 36 - versionCode = 2 - versionName = "1.1" + versionCode = autoVersionCode + versionName = autoVersionName testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" @@ -50,7 +72,6 @@ android { } buildFeatures { compose = true - aidl = true buildConfig = true } } @@ -58,11 +79,9 @@ android { androidComponents { onVariants { variant -> variant.outputs.forEach { output -> - val versionName = "1.1" - val versionCode = "2" val buildType = variant.buildType val date = SimpleDateFormat("yyyyMMdd").format(Date()) - output.outputFileName.set("PDA_Printer_Test_v${versionName}_${versionCode}_${buildType}_${date}.apk") + output.outputFileName.set("PDA_Printer_Test_v${autoVersionName}_${autoVersionCode}_${buildType}_${date}.apk") } } } @@ -76,6 +95,10 @@ dependencies { implementation(libs.androidx.compose.ui.tooling.preview) implementation(libs.androidx.core.ktx) implementation(libs.androidx.lifecycle.runtime.ktx) + + // 打印SDK + implementation(files("libs/lcprintsdk-release.aar")) + testImplementation(libs.junit) androidTestImplementation(platform(libs.androidx.compose.bom)) androidTestImplementation(libs.androidx.compose.ui.test.junit4) diff --git a/app/libs/lcprintsdk-release.aar b/app/libs/lcprintsdk-release.aar new file mode 100644 index 0000000..aaf149f Binary files /dev/null and b/app/libs/lcprintsdk-release.aar differ diff --git a/app/src/main/aidl/android/bld/IPrintManager.aidl b/app/src/main/aidl/android/bld/IPrintManager.aidl deleted file mode 100644 index f258853..0000000 --- a/app/src/main/aidl/android/bld/IPrintManager.aidl +++ /dev/null @@ -1,21 +0,0 @@ -package android.bld; - -interface IPrintManager { - void initPrinter(); - void closePrinter(); - void printText(String text); - void printText2(String text); - void printQR(String content, int size); - void printBarcode(String content, int type, int width, int height); - void printBitmap(in byte[] bitmap, int width, int height); - void printLine(int lineCount); - void start(); - void setFontSize(int size); - void setTextBold(boolean bold); - void setUnderLine(boolean underline); - void setConcentration(int level); - void setFeedPaperSpace(int space); - void setUnwindPaperLen(int length); - void printEnableMark(boolean enable); - void printGoToNextMark(); -} diff --git a/app/src/main/java/com/example/ops_android/MainActivity.kt b/app/src/main/java/com/example/ops_android/MainActivity.kt index 6f545ea..c0fe4df 100644 --- a/app/src/main/java/com/example/ops_android/MainActivity.kt +++ b/app/src/main/java/com/example/ops_android/MainActivity.kt @@ -35,6 +35,11 @@ class MainActivity : ComponentActivity() { } } + override fun onStart() { + super.onStart() + printerManager.bindService() + } + override fun onDestroy() { super.onDestroy() printerManager.unbindService() @@ -49,6 +54,14 @@ fun PrinterTestScreen( var statusMessage by remember { mutableStateOf("未连接打印服务") } var isConnected by remember { mutableStateOf(false) } + DisposableEffect(printerManager) { + printerManager.setConnectionCallback { connected, message -> + isConnected = connected + statusMessage = message + } + onDispose { } + } + Column( modifier = modifier .fillMaxSize() @@ -56,14 +69,12 @@ fun PrinterTestScreen( .verticalScroll(rememberScrollState()), verticalArrangement = Arrangement.spacedBy(12.dp) ) { - // 标题 Text( text = "PDA 打印机测试", style = MaterialTheme.typography.headlineMedium, modifier = Modifier.padding(bottom = 8.dp) ) - // 版本信息 Text( text = "v${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE}) - ${BuildConfig.AUTHOR}", style = MaterialTheme.typography.bodySmall, @@ -71,7 +82,7 @@ fun PrinterTestScreen( modifier = Modifier.padding(bottom = 8.dp) ) - // 状态显示 + // ── 状态卡片 ────────────────────────────────────── Card( colors = CardDefaults.cardColors( containerColor = if (isConnected) @@ -87,48 +98,40 @@ fun PrinterTestScreen( ) } - Divider() + HorizontalDivider() - // 连接控制区 + // ══════════════════════════════════════════════════ + // 连接控制 + // ══════════════════════════════════════════════════ Text("连接控制", style = MaterialTheme.typography.titleMedium) Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { Button( - onClick = { - val success = printerManager.bindService() - isConnected = success - statusMessage = if (success) "正在连接打印服务..." else "连接失败" - }, + onClick = { printerManager.bindService() }, modifier = Modifier.weight(1f) - ) { - Text("绑定服务") - } + ) { Text("绑定服务") } Button( onClick = { - printerManager.initPrinter().fold( + printerManager.initDevice().fold( onSuccess = { statusMessage = "打印机初始化成功" }, onFailure = { statusMessage = "初始化失败: ${it.message}" } ) }, modifier = Modifier.weight(1f) - ) { - Text("初始化") - } + ) { Text("初始化") } } Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { Button( onClick = { - printerManager.closePrinter().fold( + printerManager.closeDevice().fold( onSuccess = { statusMessage = "打印机已关闭" }, onFailure = { statusMessage = "关闭失败: ${it.message}" } ) }, modifier = Modifier.weight(1f) - ) { - Text("关闭打印机") - } + ) { Text("关闭打印机") } Button( onClick = { @@ -137,186 +140,248 @@ fun PrinterTestScreen( statusMessage = "服务已解绑" }, modifier = Modifier.weight(1f) - ) { - Text("解绑服务") - } + ) { Text("解绑服务") } } - Divider() + HorizontalDivider() - // 基础打印测试 - Text("基础打印测试", style = MaterialTheme.typography.titleMedium) + // ══════════════════════════════════════════════════ + // 快速打印 + // ══════════════════════════════════════════════════ + Text("快速打印", style = MaterialTheme.typography.titleMedium) Button( onClick = { - printerManager.quickPrintText("Hello Printer!\n测试打印\n").fold( + printerManager.quickText("Hello Printer!\n测试打印\n").fold( onSuccess = { statusMessage = "快速打印成功" }, onFailure = { statusMessage = "打印失败: ${it.message}" } ) }, modifier = Modifier.fillMaxWidth() - ) { - Text("快速打印测试") + ) { Text("快速打印测试") } + + Button( + onClick = { + printerManager.buildJob() + .text("工单号: WO-001\n操作员: 张三\n") + .build() + .fold( + onSuccess = { statusMessage = "PrintJob 打印成功" }, + onFailure = { statusMessage = "失败: ${it.message}" } + ) + }, + modifier = Modifier.fillMaxWidth() + ) { Text("PrintJob 构建器测试") } + + HorizontalDivider() + + // ══════════════════════════════════════════════════ + // 单步打印(手动组合) + // ══════════════════════════════════════════════════ + Text("单步打印", style = MaterialTheme.typography.titleMedium) + + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + Button( + onClick = { + printerManager.ensureOpen().fold( + onSuccess = { + printerManager.addText("手动 text\n").fold( + onSuccess = { statusMessage = "文字已加入缓冲区" }, + onFailure = { statusMessage = "失败: ${it.message}" } + ) + }, + onFailure = { statusMessage = "失败: ${it.message}" } + ) + }, + modifier = Modifier.weight(1f) + ) { Text("添加文字") } + + Button( + onClick = { + printerManager.ensureOpen().fold( + onSuccess = { + printerManager.addQR("https://example.com").fold( + onSuccess = { statusMessage = "QR 已加入缓冲区" }, + onFailure = { statusMessage = "失败: ${it.message}" } + ) + }, + onFailure = { statusMessage = "失败: ${it.message}" } + ) + }, + modifier = Modifier.weight(1f) + ) { Text("添加QR") } } Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { Button( onClick = { - printerManager.printText("使用 printText\n").fold( - onSuccess = { statusMessage = "printText 调用成功" }, + printerManager.ensureOpen().fold( + onSuccess = { + printerManager.addBarcode("1234567890").fold( + onSuccess = { statusMessage = "条码已加入缓冲区" }, + onFailure = { statusMessage = "失败: ${it.message}" } + ) + }, onFailure = { statusMessage = "失败: ${it.message}" } ) }, modifier = Modifier.weight(1f) - ) { - Text("printText") - } + ) { Text("添加条码") } Button( onClick = { - printerManager.printText2("使用 printText2\n").fold( - onSuccess = { statusMessage = "printText2 调用成功" }, + printerManager.ensureOpen().fold( + onSuccess = { + printerManager.addLineFeed(3).fold( + onSuccess = { statusMessage = "空行已加入缓冲区" }, + onFailure = { statusMessage = "失败: ${it.message}" } + ) + }, onFailure = { statusMessage = "失败: ${it.message}" } ) }, modifier = Modifier.weight(1f) - ) { - Text("printText2") - } + ) { Text("添加空行") } } Button( onClick = { - printerManager.start().fold( - onSuccess = { statusMessage = "开始打印(出纸)" }, + printerManager.ensureOpen().fold( + onSuccess = { + printerManager.commit().fold( + onSuccess = { statusMessage = "已提交打印" }, + onFailure = { statusMessage = "失败: ${it.message}" } + ) + }, onFailure = { statusMessage = "失败: ${it.message}" } ) }, modifier = Modifier.fillMaxWidth() - ) { - Text("start() - 提交打印") - } + ) { Text("提交打印 (start)") } - Divider() + HorizontalDivider() - // 样式测试 + // ══════════════════════════════════════════════════ + // 样式设置 + // ══════════════════════════════════════════════════ Text("样式设置", style = MaterialTheme.typography.titleMedium) Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { Button( onClick = { - printerManager.setFontSize(24).fold( - onSuccess = { statusMessage = "字体大小已设置" }, + printerManager.ensureOpen().fold( + onSuccess = { statusMessage = "设备已打开,可设置样式" }, + onFailure = { statusMessage = "失败: ${it.message}" } + ) + printerManager.setFontSize(32).fold( + onSuccess = { statusMessage = "大字体已设置" }, onFailure = { statusMessage = "失败: ${it.message}" } ) }, modifier = Modifier.weight(1f) - ) { - Text("大字体") - } + ) { Text("大字体") } Button( onClick = { - printerManager.setTextBold(true).fold( - onSuccess = { statusMessage = "已设置粗体" }, + printerManager.ensureOpen().fold( + onSuccess = { statusMessage = "设备已打开" }, + onFailure = { statusMessage = "失败: ${it.message}" } + ) + printerManager.setBold(true).fold( + onSuccess = { statusMessage = "粗体已设置" }, onFailure = { statusMessage = "失败: ${it.message}" } ) }, modifier = Modifier.weight(1f) - ) { - Text("粗体") - } + ) { Text("粗体") } } Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { Button( onClick = { - printerManager.setUnderLine(true).fold( - onSuccess = { statusMessage = "已设置下划线" }, + printerManager.setUnderline(true).fold( + onSuccess = { statusMessage = "下划线已设置" }, onFailure = { statusMessage = "失败: ${it.message}" } ) }, modifier = Modifier.weight(1f) - ) { - Text("下划线") - } + ) { Text("下划线") } Button( onClick = { - printerManager.setConcentration(5).fold( + printerManager.setDensity(5).fold( onSuccess = { statusMessage = "浓度已设置" }, onFailure = { statusMessage = "失败: ${it.message}" } ) }, modifier = Modifier.weight(1f) - ) { - Text("浓度") - } + ) { Text("浓度") } } - Divider() + HorizontalDivider() - // 二维码/条形码测试 - Text("二维码与条形码", style = MaterialTheme.typography.titleMedium) + // ══════════════════════════════════════════════════ + // 标签打印 + // ══════════════════════════════════════════════════ + Text("标签打印(黑标对齐)", style = MaterialTheme.typography.titleMedium) Button( onClick = { - printerManager.printQR("https://example.com", 6).fold( - onSuccess = { statusMessage = "二维码已加入队列" }, - onFailure = { statusMessage = "失败: ${it.message}" } + printerManager.quickLabel("标签打印测试\n工单号: WO-001\n").fold( + onSuccess = { statusMessage = "标签打印成功" }, + onFailure = { statusMessage = "标签打印失败: ${it.message}" } ) }, modifier = Modifier.fillMaxWidth() - ) { - Text("打印二维码") - } - - Button( - onClick = { - printerManager.printBarcode("1234567890", 0, 2, 60).fold( - onSuccess = { statusMessage = "条形码已加入队列" }, - onFailure = { statusMessage = "失败: ${it.message}" } - ) - }, - modifier = Modifier.fillMaxWidth() - ) { - Text("打印条形码") - } - - Divider() - - // 走纸控制 - Text("走纸控制", style = MaterialTheme.typography.titleMedium) + ) { Text("快速标签打印") } Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { Button( onClick = { - printerManager.printLine(3).fold( - onSuccess = { statusMessage = "打印空行" }, + printerManager.enableMarkDetection(true).fold( + onSuccess = { statusMessage = "已开启黑标检测" }, onFailure = { statusMessage = "失败: ${it.message}" } ) }, modifier = Modifier.weight(1f) - ) { - Text("打印3空行") - } + ) { Text("开启黑标") } Button( onClick = { - printerManager.setFeedPaperSpace(100).fold( - onSuccess = { statusMessage = "走纸间距已设置" }, + printerManager.enableMarkDetection(false).fold( + onSuccess = { statusMessage = "已关闭黑标检测" }, onFailure = { statusMessage = "失败: ${it.message}" } ) }, modifier = Modifier.weight(1f) - ) { - Text("走纸间距") - } + ) { Text("关闭黑标") } } - Divider() + Button( + onClick = { + printerManager.ensureOpen().fold( + onSuccess = { + printerManager.enableMarkDetection(true).fold( + onSuccess = { + printerManager.gotoNextMark().fold( + onSuccess = { statusMessage = "已移动到下一标签" }, + onFailure = { statusMessage = "失败: ${it.message}" } + ) + }, + onFailure = { statusMessage = "失败: ${it.message}" } + ) + }, + onFailure = { statusMessage = "失败: ${it.message}" } + ) + }, + modifier = Modifier.fillMaxWidth() + ) { Text("下一标签") } - // 综合测试 + HorizontalDivider() + + // ══════════════════════════════════════════════════ + // 综合测试 + // ══════════════════════════════════════════════════ Text("综合测试", style = MaterialTheme.typography.titleMedium) Button( @@ -327,9 +392,7 @@ fun PrinterTestScreen( colors = ButtonDefaults.buttonColors( containerColor = MaterialTheme.colorScheme.secondary ) - ) { - Text("运行完整测试") - } + ) { Text("运行完整测试") } } } @@ -339,54 +402,47 @@ private fun runComprehensiveTest( ) { onStatus("开始综合测试...") - printerManager.initPrinter().onFailure { + printerManager.initDevice().onFailure { onStatus("初始化失败: ${it.message}") return } - // 标题 printerManager.setFontSize(32) - printerManager.setTextBold(true) - printerManager.printText2("=== 打印测试 ===\n") + printerManager.setBold(true) + printerManager.addText("=== 打印测试 ===\n") - // 普通文本 printerManager.setFontSize(24) - printerManager.setTextBold(false) - printerManager.printText2("普通文本测试\n") + printerManager.setBold(false) + printerManager.addText("普通文本测试\n") - // 粗体文本 - printerManager.setTextBold(true) - printerManager.printText2("粗体文本测试\n") - printerManager.setTextBold(false) + printerManager.setBold(true) + printerManager.addText("粗体文本测试\n") + printerManager.setBold(false) - // 下划线文本 - printerManager.setUnderLine(true) - printerManager.printText2("下划线文本测试\n") - printerManager.setUnderLine(false) + printerManager.setUnderline(true) + printerManager.addText("下划线文本测试\n") + printerManager.setUnderline(false) - // 空行 - printerManager.printLine(1) + printerManager.addLineFeed(1) - // 二维码 - printerManager.printText2("二维码测试:\n") - printerManager.printQR("https://example.com", 6) - printerManager.printLine(1) + printerManager.addText("二维码测试:\n") + printerManager.addQR("https://example.com") + printerManager.addLineFeed(1) - // 条形码 - printerManager.printText2("条形码测试:\n") - printerManager.printBarcode("123456789012", 0, 2, 60) - printerManager.printLine(3) + printerManager.addText("条形码测试:\n") + printerManager.addBarcode("123456789012") + printerManager.addLineFeed(3) - // 分隔线 - printerManager.printText2("--------------------------------\n") - printerManager.printText2("测试完成\n") - printerManager.printLine(2) + printerManager.addText("--------------------------------\n") + printerManager.addText("测试完成\n") + printerManager.addLineFeed(2) - // 提交打印 - printerManager.start().fold( - onSuccess = { onStatus("综合测试完成,开始打印") }, + printerManager.commit().fold( + onSuccess = { + onStatus("综合测试完成,开始打印") + Thread.sleep(1500) + printerManager.closeDevice() + }, onFailure = { onStatus("提交打印失败: ${it.message}") } ) - - printerManager.closePrinter() } diff --git a/app/src/main/java/com/example/ops_android/printer/PrintJob.kt b/app/src/main/java/com/example/ops_android/printer/PrintJob.kt new file mode 100644 index 0000000..3c9eb64 --- /dev/null +++ b/app/src/main/java/com/example/ops_android/printer/PrintJob.kt @@ -0,0 +1,67 @@ +package com.example.ops_android.printer + +class PrintJob(private val manager: PrinterManager) { + + private var textContent: String? = null + private var qrContent: String? = null + private var barcodeContent: String? = null + private var lineFeeds: Int = 0 + private var useLabelMode: Boolean = false + + fun text(text: String): PrintJob { + textContent = text + return this + } + + fun qr(content: String): PrintJob { + qrContent = content + return this + } + + fun barcode(content: String): PrintJob { + barcodeContent = content + return this + } + + fun lineFeed(count: Int = 1): PrintJob { + lineFeeds = count + return this + } + + fun labelMode(): PrintJob { + useLabelMode = true + return this + } + + /** 初始化 → 添加内容 → 提交 → 等待 → 关闭 */ + fun build(): Result = runCatching { + manager.initDevice().getOrThrow() + + if (useLabelMode) + manager.enableMarkDetection(true).getOrThrow() + + textContent?.let { manager.addText(it).getOrThrow() } + qrContent?.let { manager.addQR(it).getOrThrow() } + barcodeContent?.let { manager.addBarcode(it).getOrThrow() } + if (lineFeeds > 0) manager.addLineFeed(lineFeeds).getOrThrow() + + manager.commit().getOrThrow() + Thread.sleep(1500) + manager.closeDevice().getOrThrow() + } + + /** 初始化 → 添加内容 → 提交(不关闭) */ + fun print(): Result = runCatching { + manager.ensureOpen().getOrThrow() + + if (useLabelMode) + manager.enableMarkDetection(true).getOrThrow() + + textContent?.let { manager.addText(it).getOrThrow() } + qrContent?.let { manager.addQR(it).getOrThrow() } + barcodeContent?.let { manager.addBarcode(it).getOrThrow() } + if (lineFeeds > 0) manager.addLineFeed(lineFeeds).getOrThrow() + + manager.commit().getOrThrow() + } +} diff --git a/app/src/main/java/com/example/ops_android/printer/PrinterManager.kt b/app/src/main/java/com/example/ops_android/printer/PrinterManager.kt index 5dd5cc2..67256c9 100644 --- a/app/src/main/java/com/example/ops_android/printer/PrinterManager.kt +++ b/app/src/main/java/com/example/ops_android/printer/PrinterManager.kt @@ -1,136 +1,322 @@ package com.example.ops_android.printer -import android.content.ComponentName import android.content.Context -import android.content.Intent -import android.content.ServiceConnection -import android.os.IBinder +import android.graphics.Bitmap +import android.os.Handler +import android.os.Looper import android.util.Log -import android.bld.IPrintManager +import android.bld.PrintManager +import android.bld.print.aidl.PrinterBinderListener +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.TimeoutCancellationException +import kotlinx.coroutines.withContext +import kotlinx.coroutines.withTimeout class PrinterManager(private val context: Context) { - private var printService: IPrintManager? = null - private var isBound = false + + // ── 内部状态 ──────────────────────────────────────────── + internal var printManager: PrintManager? = null + private set + @Volatile internal var isOpen = false + private set + @Volatile private var intentionalClose = false + private var connectionCallback: ((Boolean, String) -> Unit)? = null + private val mainHandler = Handler(Looper.getMainLooper()) companion object { private const val TAG = "PrinterManager" - private const val PRINT_SERVICE_ACTION = "android.bld.PrintManager" - private const val PRINT_SERVICE_PACKAGE = "android.bld" - } + private const val CONNECT_TIMEOUT_MS = 10_000L - private val serviceConnection = object : ServiceConnection { - override fun onServiceConnected(name: ComponentName?, service: IBinder?) { - printService = IPrintManager.Stub.asInterface(service) - isBound = true - Log.d(TAG, "Print service connected") - } + // 错误码 + internal const val ERROR_NO_ERROR = 0 + internal const val ERROR_DEV_IS_BUSY = 1 + internal const val ERROR_PRINT_HOT = 2 + internal const val ERROR_PRINT_NOPAPER = 3 + internal const val ERROR_DEV_NO_BATTERY = 4 + internal const val ERROR_DEV_FEED = 5 + internal const val ERROR_DEV_PRINT = 6 + internal const val ERROR_DEV_BMARK = 7 + internal const val ERROR_DEV_NOT_OPEN = 16 + internal const val ERROR_NO_DATA = 17 + internal const val ERROR_DATA_INVALID = 18 + internal const val ERROR_CMD = 19 + internal const val ERROR_GRAY_INVALID = 20 - override fun onServiceDisconnected(name: ComponentName?) { - printService = null - isBound = false - Log.d(TAG, "Print service disconnected") + fun getErrorMessage(errorCode: Int): String = when (errorCode) { + ERROR_NO_ERROR -> "正常" + ERROR_DEV_IS_BUSY -> "设备忙" + ERROR_PRINT_HOT -> "打印头过热" + ERROR_PRINT_NOPAPER -> "缺纸" + ERROR_DEV_NO_BATTERY -> "电量不足" + ERROR_DEV_FEED -> "走纸错误" + ERROR_DEV_PRINT -> "打印错误" + ERROR_DEV_BMARK -> "黑标错误" + ERROR_DEV_NOT_OPEN -> "设备未打开" + ERROR_NO_DATA -> "无数据" + ERROR_DATA_INVALID -> "数据无效" + ERROR_CMD -> "指令错误" + ERROR_GRAY_INVALID -> "浓度无效" + 165 -> "数据输入错误" + else -> "未知错误($errorCode)" } } - fun bindService(): Boolean { - return try { - val intent = Intent(PRINT_SERVICE_ACTION).apply { - setPackage(PRINT_SERVICE_PACKAGE) + // ── 连接回调 ───(运行在 Binder 线程,需切换到主线程)────── + private val printerListener = object : PrinterBinderListener { + override fun onPrintCallback(status: Int) { + Log.d(TAG, "Printer callback: status=$status (${getErrorMessage(status)})") + mainHandler.post { + when (status) { + ERROR_NO_ERROR -> { + isOpen = true + connectionCallback?.invoke(true, "打印机已就绪") + } + ERROR_DEV_NOT_OPEN -> { + if (intentionalClose) return@post + isOpen = false + connectionCallback?.invoke(false, "设备未打开,请重新连接") + } + else -> connectionCallback?.invoke(false, getErrorMessage(status)) + } } - context.bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE) - } catch (e: Exception) { - Log.e(TAG, "Failed to bind print service", e) - false } } + fun setConnectionCallback(callback: (Boolean, String) -> Unit) { + connectionCallback = callback + } + + // ══════════════════════════════════════════════════════════ + // 连接管理 + // ══════════════════════════════════════════════════════════ + + fun bindService() { + notifyConnection(false, "正在连接打印服务...") + Thread { + try { + kotlinx.coroutines.runBlocking { + withTimeout(CONNECT_TIMEOUT_MS) { + withContext(Dispatchers.IO) { + intentionalClose = false + Log.d(TAG, "Attempting to get PrintManager instance...") + val manager = PrintManager.getDefaultInstance(context) + if (manager == null) { + notifyConnection(false, "设备不支持打印功能") + return@withContext + } + printManager = manager + manager.addPrintListener(printerListener) + Log.d(TAG, "Opening printer...") + val success = manager.open() + Log.d(TAG, "open() returned: $success") + if (success) { + isOpen = true + val version = manager.getPrinterVer() + if (version != null) { + notifyConnection(true, "打印机已连接 (固件: $version)") + } else { + notifyConnection(true, "打印机已连接") + } + } else { + isOpen = false + notifyConnection(false, "打印机打开失败,请检查设备") + } + } + } + } + } catch (e: TimeoutCancellationException) { + Log.e(TAG, "bindService timeout after ${CONNECT_TIMEOUT_MS}ms") + notifyConnection(false, "连接超时,请检查打印服务是否运行") + } catch (e: Exception) { + Log.e(TAG, "Failed to open print service", e) + notifyConnection(false, "连接异常: ${e.message}") + } + }.start() + } + fun unbindService() { - if (isBound) { - context.unbindService(serviceConnection) - isBound = false + Thread { + try { + if (isOpen) { + printManager?.removePrintListener(printerListener) + printManager?.close() + isOpen = false + notifyConnection(false, "服务已断开") + } + } catch (e: Exception) { + Log.e(TAG, "Failed to close print service", e) + } + }.start() + } + + fun isConnected(): Boolean = isOpen && printManager != null + + fun getFirmwareVersion(): String? { + return try { + printManager?.getPrinterVer() + } catch (e: Exception) { + Log.e(TAG, "Failed to get printer version", e) + null } } - // 基础连接方法 - fun initPrinter(): Result = runCatching { - printService?.initPrinter() ?: throw Exception("Print service not connected") + // ══════════════════════════════════════════════════════════ + // 设备控制 + // ══════════════════════════════════════════════════════════ + + fun ensureOpen(): Result = runCatching { + checkPrinter() + if (!isOpen) { + intentionalClose = false + val opened = printManager?.open() ?: false + if (!opened) throw Exception("设备未打开,请重新绑定服务") + isOpen = true + } } - fun closePrinter(): Result = runCatching { - printService?.closePrinter() ?: throw Exception("Print service not connected") + fun initDevice(): Result = runCatching { + ensureOpen().getOrThrow() + val result = printManager!!.reset() + if (result != ERROR_NO_ERROR) throw Exception("初始化失败: ${getErrorMessage(result)}") } - // 打印方法 - fun printText(text: String): Result = runCatching { - printService?.printText(text) ?: throw Exception("Print service not connected") + fun closeDevice(): Result = runCatching { + checkOpen() + isOpen = false + intentionalClose = true + printManager?.close() } - fun printText2(text: String): Result = runCatching { - printService?.printText2(text) ?: throw Exception("Print service not connected") + fun commit(): Result = runCatching { + checkOpen() + printManager!!.start() } - fun printQR(content: String, size: Int = 6): Result = runCatching { - printService?.printQR(content, size) ?: throw Exception("Print service not connected") + // ══════════════════════════════════════════════════════════ + // 内容添加(供 PrintJob 使用) + // ══════════════════════════════════════════════════════════ + + internal fun addText(text: String): Result = runCatching { + checkOpen() + val pm = printManager!! + pm.addText(0, pm.getFontSize() ?: 24, pm.isFontBold() ?: false, pm.isUnderLine() ?: false, text) } - fun printBarcode(content: String, type: Int = 0, width: Int = 2, height: Int = 60): Result = runCatching { - printService?.printBarcode(content, type, width, height) ?: throw Exception("Print service not connected") + internal fun addQR(content: String): Result = runCatching { + checkOpen() + printManager!!.addQRCode(2, 384, content) } - fun printBitmap(bitmap: ByteArray, width: Int, height: Int): Result = runCatching { - printService?.printBitmap(bitmap, width, height) ?: throw Exception("Print service not connected") + internal fun addBarcode(content: String): Result = runCatching { + checkOpen() + printManager!!.addBarcode(2, 60, content, 73, 3) } - fun printLine(lineCount: Int = 1): Result = runCatching { - printService?.printLine(lineCount) ?: throw Exception("Print service not connected") + internal fun addLineFeed(count: Int = 1): Result = runCatching { + checkOpen() + printManager!!.addLineFeed(count) } - fun start(): Result = runCatching { - printService?.start() ?: throw Exception("Print service not connected") - } + // ══════════════════════════════════════════════════════════ + // 样式设置 + // ══════════════════════════════════════════════════════════ - // 样式设置方法 fun setFontSize(size: Int): Result = runCatching { - printService?.setFontSize(size) ?: throw Exception("Print service not connected") + checkOpen() + printManager!!.setFontSize(size) } - fun setTextBold(bold: Boolean): Result = runCatching { - printService?.setTextBold(bold) ?: throw Exception("Print service not connected") + fun setBold(bold: Boolean): Result = runCatching { + checkOpen() + printManager!!.setFontBold(bold) } - fun setUnderLine(underline: Boolean): Result = runCatching { - printService?.setUnderLine(underline) ?: throw Exception("Print service not connected") + fun setUnderline(underline: Boolean): Result = runCatching { + checkOpen() + printManager!!.setUnderLine(underline) } - fun setConcentration(level: Int): Result = runCatching { - printService?.setConcentration(level) ?: throw Exception("Print service not connected") + fun setDensity(level: Int): Result = runCatching { + checkOpen() + printManager!!.setDensity(level) } - // 走纸控制 - fun setFeedPaperSpace(space: Int): Result = runCatching { - printService?.setFeedPaperSpace(space) ?: throw Exception("Print service not connected") + // ══════════════════════════════════════════════════════════ + // 走纸控制 + // ══════════════════════════════════════════════════════════ + + fun setPaperGap(dots: Int): Result = runCatching { + checkOpen() + printManager!!.setFeedPaperSpace(dots) } - fun setUnwindPaperLen(length: Int): Result = runCatching { - printService?.setUnwindPaperLen(length) ?: throw Exception("Print service not connected") + fun setUnwindLength(len: Int): Result = runCatching { + checkOpen() + printManager!!.setUnwindPaperLen(len) } - // 标签纸控制 - fun printEnableMark(enable: Boolean): Result = runCatching { - printService?.printEnableMark(enable) ?: throw Exception("Print service not connected") + // ══════════════════════════════════════════════════════════ + // 黑标 / 标签 + // ══════════════════════════════════════════════════════════ + + fun enableMarkDetection(enable: Boolean): Result = runCatching { + checkOpen() + printManager!!.setBlackLabel(enable) } - fun printGoToNextMark(): Result = runCatching { - printService?.printGoToNextMark() ?: throw Exception("Print service not connected") + fun gotoNextMark(): Result = runCatching { + checkOpen() + printManager!!.addLineFeed(1) + printManager!!.start() } - // 便捷组合方法 - fun quickPrintText(text: String): Result = runCatching { - initPrinter().getOrThrow() - printText2(text).getOrThrow() - start().getOrThrow() - closePrinter().getOrThrow() + // ══════════════════════════════════════════════════════════ + // 图片 + // ══════════════════════════════════════════════════════════ + + fun printImage(bitmap: Bitmap, align: Int = 1): Result = runCatching { + checkOpen() + printManager!!.addImage(align, bitmap) } - fun isServiceConnected(): Boolean = isBound && printService != null + // ══════════════════════════════════════════════════════════ + // 便捷方法 + // ══════════════════════════════════════════════════════════ + + fun quickText(text: String): Result = runCatching { + initDevice().getOrThrow() + addText(text).getOrThrow() + commit().getOrThrow() + Thread.sleep(1500) + closeDevice().getOrThrow() + } + + fun quickLabel(text: String): Result = runCatching { + initDevice().getOrThrow() + enableMarkDetection(true).getOrThrow() + addText(text).getOrThrow() + commit().getOrThrow() + Thread.sleep(1500) + closeDevice().getOrThrow() + } + + /** 创建链式打印任务 */ + fun buildJob(): PrintJob = PrintJob(this) + + // ── 内部工具 ──────────────────────────────────────────── + + internal fun checkPrinter() { + if (printManager == null) + throw Exception("打印服务未初始化,请先点击'绑定服务'") + } + + private fun checkOpen() { + checkPrinter() + if (!isOpen) + throw Exception("打印服务未连接,请先点击'绑定服务'") + } + + private fun notifyConnection(connected: Boolean, message: String) { + mainHandler.post { connectionCallback?.invoke(connected, message) } + } } diff --git a/app/version.properties b/app/version.properties new file mode 100644 index 0000000..d3beaed --- /dev/null +++ b/app/version.properties @@ -0,0 +1,3 @@ +#Wed Jun 24 20:47:38 CST 2026 +VERSION_CODE=23 +VERSION_NAME=1.2