diff --git a/app/build.gradle b/app/build.gradle index ba53863..603b8d5 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -84,7 +84,7 @@ dependencies { implementation 'com.google.android.material:material:1.3.0' implementation "io.noties.markwon:core:4.6.2" - implementation ('com.rokid.security:glass3.open.sdk:2.1.6-E') { + implementation ('com.rokid.security:glass3.open.sdk:2.1.7-E') { exclude group: "org.slf4j" } implementation 'com.rokid.security.sdk:online-speech:0.1.0' diff --git a/app/src/main/java/com/nova/brain/glass/helper/GlassMediaServiceHelper.kt b/app/src/main/java/com/nova/brain/glass/helper/GlassMediaServiceHelper.kt index 9d89f1c..8a62b2e 100644 --- a/app/src/main/java/com/nova/brain/glass/helper/GlassMediaServiceHelper.kt +++ b/app/src/main/java/com/nova/brain/glass/helper/GlassMediaServiceHelper.kt @@ -1,6 +1,8 @@ package com.nova.brain.glass.helper +import android.view.Surface import com.rokid.security.glass3.open.sdk.GlassSdk +import com.rokid.security.system.server.media.callback.ICameraSurfaceCallback import com.rokid.security.system.server.media.callback.PhotoFileCallback object GlassMediaServiceHelper { @@ -34,4 +36,35 @@ object GlassMediaServiceHelper { service.javaClass.getMethod("removePhotoCallback", PhotoFileCallback::class.java) .invoke(service, callback) } + + fun startCameraShare(surface: Surface, callback: ICameraSurfaceCallback) { + val service = service() ?: return + service.javaClass.getMethod( + "startCameraShare", + Surface::class.java, + ICameraSurfaceCallback::class.java + ).invoke(service, surface, callback) + } + + fun stopCameraShare(callback: ICameraSurfaceCallback) { + val service = service() ?: return + service.javaClass.getMethod("stopCameraShare", ICameraSurfaceCallback::class.java) + .invoke(service, callback) + } + + fun getMaxZoomLevel(): Int { + val service = service() ?: return 0 + return (service.javaClass.getMethod("getMaxZoomLevel").invoke(service) as? Int) ?: 0 + } + + fun getZoomLevel(): Int { + val service = service() ?: return 0 + return (service.javaClass.getMethod("getZoomLevel").invoke(service) as? Int) ?: 0 + } + + fun zoomCamera(level: Int) { + val service = service() ?: return + service.javaClass.getMethod("zoomCamera", Int::class.javaPrimitiveType) + .invoke(service, level) + } } diff --git a/app/src/main/java/com/nova/brain/glass/helper/OfflineCmdServiceHelper.kt b/app/src/main/java/com/nova/brain/glass/helper/OfflineCmdServiceHelper.kt index f34a6fb..ed0b2fa 100644 --- a/app/src/main/java/com/nova/brain/glass/helper/OfflineCmdServiceHelper.kt +++ b/app/src/main/java/com/nova/brain/glass/helper/OfflineCmdServiceHelper.kt @@ -43,6 +43,16 @@ object OfflineCmdServiceHelper { OfflineCmdBean("通过", "tong guo"), OfflineCmdBean("同意", "tong yi") ) + private val CMDS_INSPECTION_CAPTURE = listOf( + OfflineCmdBean("开始", "kai shi"), + OfflineCmdBean("开始任务", "kai shi ren wu"), + OfflineCmdBean("拍照", "pai zhao"), + OfflineCmdBean("拍摄", "pai she"), + OfflineCmdBean("放大", "fang da"), + OfflineCmdBean("拉近", "la jin"), + OfflineCmdBean("缩小", "suo xiao"), + OfflineCmdBean("拉远", "la yuan") + ) private val CMDS_SPRAYING = listOf( OfflineCmdBean("开始", "kai shi"), OfflineCmdBean("开始任务", "kai shi ren wu") @@ -158,6 +168,8 @@ object OfflineCmdServiceHelper { fun addListenerInspection() = registerBeans(CMDS_INSPECTION) + fun addListenerInspectionCapture() = registerBeans(CMDS_INSPECTION_CAPTURE) + fun addListenerSpraying() = registerBeans(CMDS_SPRAYING) fun addListenerSprayingFinish() = registerBeans(CMDS_SPRAYING_FINISH) @@ -176,6 +188,8 @@ object OfflineCmdServiceHelper { fun removeListenerInspection() = removeBeans(CMDS_INSPECTION) + fun removeListenerInspectionCapture() = removeBeans(CMDS_INSPECTION_CAPTURE) + fun removeListenerSpraying() = removeBeans(CMDS_SPRAYING) fun removeListenerSprayingFinish() = removeBeans(CMDS_SPRAYING_FINISH) diff --git a/app/src/main/java/com/nova/brain/glass/ui/InspectionActivity.kt b/app/src/main/java/com/nova/brain/glass/ui/InspectionActivity.kt index b0d8583..e5140b4 100644 --- a/app/src/main/java/com/nova/brain/glass/ui/InspectionActivity.kt +++ b/app/src/main/java/com/nova/brain/glass/ui/InspectionActivity.kt @@ -3,6 +3,9 @@ package com.nova.brain.glass.ui import android.content.Intent import android.os.Environment import android.view.WindowManager +import android.view.Surface +import android.view.TextureView +import android.view.View import androidx.recyclerview.widget.RecyclerView import com.nova.brain.glass.R import com.nova.brain.glass.databinding.ActivityInspectionBinding @@ -14,6 +17,7 @@ import com.nova.brain.glass.model.ItemItem import com.nova.brain.glass.viewmodel.InspectionValidateState import com.nova.brain.glass.viewmodel.InspectionVM import com.rokid.security.glass3.sdk.base.data.media.PhotoResolution +import com.rokid.security.system.server.media.callback.ICameraSurfaceCallback import com.rokid.security.system.server.media.callback.PhotoFileCallback import com.xuqm.base.adapter.BasePagedAdapter import com.xuqm.base.adapter.CommonPagedAdapter @@ -33,22 +37,88 @@ class InspectionActivity : private val glassTaskId: String by lazy { intent.getStringExtra("glassTaskId").orEmpty() } private var pendingPhotoPath: String? = null + private var isPreviewRequested = false + private var isPreviewActive = false + private var currentZoomLevel = 1 + private var maxZoomLevel = 1 private val listener = object : OfflineCmdListener { override fun onOfflineCmd(cmd: String) { runOnUiThread { when (cmd) { "退出", "返回", "退回" -> finish() - "开始", "开始任务", "拍照" -> startCapture() + "开始", "开始任务" -> startCapture() + "拍照", "拍摄" -> if (isPreviewActive) capturePhoto() else startCapture() + "放大", "拉近" -> adjustZoom(1) + "缩小", "拉远" -> adjustZoom(-1) } } } } + private val cameraSurfaceCallbackId = UUID.randomUUID().toString() + private val cameraSurfaceCallback = object : ICameraSurfaceCallback.Stub() { + override fun onCameraOpened(width: Int, height: Int) { + isPreviewActive = true + currentZoomLevel = GlassMediaServiceHelper.getZoomLevel().coerceAtLeast(1) + maxZoomLevel = GlassMediaServiceHelper.getMaxZoomLevel().coerceAtLeast(1) + runOnUiThread { + binding.cameraPreview.visibility = View.VISIBLE + binding.zoomText.visibility = View.VISIBLE + updateZoomText() + binding.hint.text = "预览中,单击预览或语音输入“拍照”完成拍摄" + } + } + + override fun onCameraClosed() { + isPreviewActive = false + runOnUiThread { + binding.cameraPreview.visibility = View.GONE + binding.zoomText.visibility = View.GONE + } + } + + override fun onError(code: Int, message: String?) { + isPreviewActive = false + runOnUiThread { + binding.cameraPreview.visibility = View.GONE + binding.zoomText.visibility = View.GONE + binding.hint.text = "相机预览失败,请重试" + (message ?: "相机预览失败").showMessage() + } + } + + override fun getCallbackId(): String = cameraSurfaceCallbackId + } + + private val previewTextureListener = object : TextureView.SurfaceTextureListener { + override fun onSurfaceTextureAvailable(surface: android.graphics.SurfaceTexture, width: Int, height: Int) { + if (isPreviewRequested) { + startCameraPreview() + } + } + + override fun onSurfaceTextureSizeChanged( + surface: android.graphics.SurfaceTexture, + width: Int, + height: Int + ) = Unit + + override fun onSurfaceTextureDestroyed(surface: android.graphics.SurfaceTexture): Boolean = true + + override fun onSurfaceTextureUpdated(surface: android.graphics.SurfaceTexture) = Unit + } + override fun initData() { super.initData() window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) SprayingPhotoManager.clear() + binding.cameraPreview.surfaceTextureListener = previewTextureListener + binding.cameraPreview.setOnClickListener { + if (isPreviewActive) { + capturePhoto() + } + } viewModel.taskInfo.observe(this) { item -> if (item != null) { @@ -95,11 +165,68 @@ class InspectionActivity : private fun startCapture() { if (viewModel.validateState.value == InspectionValidateState.LOADING) return - binding.hint.text = "拍照中,请稍后..." + if (isPreviewActive) { + capturePhoto() + return + } + binding.hint.text = "相机预览启动中,请稍后..." + isPreviewRequested = true SprayingPhotoManager.clear() + if (binding.cameraPreview.isAvailable) { + startCameraPreview() + } else { + binding.cameraPreview.visibility = View.VISIBLE + binding.zoomText.visibility = View.VISIBLE + } + } + + private fun startCameraPreview() { + val surfaceTexture = binding.cameraPreview.surfaceTexture ?: return + val surface = Surface(surfaceTexture) + runCatching { + GlassMediaServiceHelper.startCameraShare(surface, cameraSurfaceCallback) + }.onFailure { + isPreviewRequested = false + binding.cameraPreview.visibility = View.GONE + binding.zoomText.visibility = View.GONE + binding.hint.text = "相机预览启动失败,请重试" + (it.message ?: "相机预览启动失败").showMessage() + } + } + + private fun stopCameraPreview() { + isPreviewRequested = false + isPreviewActive = false + runCatching { + GlassMediaServiceHelper.stopCameraShare(cameraSurfaceCallback) + } + binding.cameraPreview.visibility = View.GONE + binding.zoomText.visibility = View.GONE + } + + private fun capturePhoto() { + if (viewModel.validateState.value == InspectionValidateState.LOADING) return + binding.hint.text = "拍照中,请稍后..." takePhoto() } + private fun adjustZoom(delta: Int) { + if (!isPreviewActive) return + maxZoomLevel = GlassMediaServiceHelper.getMaxZoomLevel().coerceAtLeast(1) + currentZoomLevel = (GlassMediaServiceHelper.getZoomLevel() + delta).coerceIn(1, maxZoomLevel) + runCatching { + GlassMediaServiceHelper.zoomCamera(currentZoomLevel) + }.onSuccess { + updateZoomText() + }.onFailure { + (it.message ?: "缩放失败").showMessage() + } + } + + private fun updateZoomText() { + binding.zoomText.text = "缩放 ${currentZoomLevel}x" + } + private fun takePhoto() { val fileName = "inspection_${System.currentTimeMillis()}.png" val file = File( @@ -121,6 +248,7 @@ class InspectionActivity : override fun onTakePhotoV2(path: String?, width: Int, height: Int) { if (path == null) { runOnUiThread { + stopCameraPreview() binding.hint.text = "单击或语音输入\"开始\",进入下一步" } "相机异常".showMessage() @@ -129,6 +257,7 @@ class InspectionActivity : SprayingPhotoManager.addPhoto(path) pendingPhotoPath = path runOnUiThread { + stopCameraPreview() viewModel.validateDocument(path) } } @@ -152,12 +281,15 @@ class InspectionActivity : super.onResume() GlassMediaServiceHelper.addPhotoCallback(mPhotoFileCallback) OfflineCmdServiceHelper.addOnLineListener(listener) + OfflineCmdServiceHelper.addListenerInspectionCapture() } override fun onPause() { super.onPause() + stopCameraPreview() GlassMediaServiceHelper.removePhotoCallback(mPhotoFileCallback) OfflineCmdServiceHelper.removeOnLineListener(listener) + OfflineCmdServiceHelper.removeListenerInspectionCapture() } override fun onDestroy() { diff --git a/app/src/main/res/layout/activity_inspection.xml b/app/src/main/res/layout/activity_inspection.xml index f308e70..4ce5da0 100644 --- a/app/src/main/res/layout/activity_inspection.xml +++ b/app/src/main/res/layout/activity_inspection.xml @@ -104,5 +104,37 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/baseRecyclerView" /> + + + + - \ No newline at end of file +