feat(spraying): 添加喷涂功能的拍照和OCR识别支持

- 在OfflineCmdServiceHelper中新增喷涂相关的语音命令
- 为InspectionActivity、ReviewActivity和SprayingActivity添加屏幕常亮功能
- 新增SprayingOCRActivity用于OCR识别和图像显示
- 实现拍照功能,包括语音控制拍照和手动点击拍照
- 集成GlassSdk媒体服务进行照片拍摄和回调处理
- 添加照片预览和重拍功能
- 更新布局文件以支持拍照提示和图像显示
- 移除MainActivity并将启动目标改为SprayingOCRActivity
- 清理WelcomeActivity中的调试日志输出
这个提交包含在:
徐勤民 2026-04-14 20:34:20 +08:00
父节点 866b80b29f
当前提交 989ce90cff
共有 11 个文件被更改,包括 283 次插入33 次删除

查看文件

@ -82,7 +82,7 @@
android:name=".ui.SprayingActivity" android:name=".ui.SprayingActivity"
android:exported="false" /> android:exported="false" />
<activity <activity
android:name=".ui.MainActivity" android:name=".ui.SprayingOCRActivity"
android:exported="false" /> android:exported="false" />
<activity <activity
android:name=".ui.WelcomeActivity" android:name=".ui.WelcomeActivity"

查看文件

@ -137,6 +137,15 @@ object OfflineCmdServiceHelper {
add(OfflineCmdBean("打开第十项任务", "da kai di shi xiang ren wu")) add(OfflineCmdBean("打开第十项任务", "da kai di shi xiang ren wu"))
add(OfflineCmdBean("处理第十条任务", "chu li di shi tiao ren wu")) add(OfflineCmdBean("处理第十条任务", "chu li di shi tiao ren wu"))
add(OfflineCmdBean("处理第十项任务", "chu li di shi xiang ren wu")) add(OfflineCmdBean("处理第十项任务", "chu li di shi xiang ren wu"))
//喷涂
add(OfflineCmdBean("开始", "kai shi"))
add(OfflineCmdBean("开始拍照", "kai shi pai zhao"))
add(OfflineCmdBean("拍照", "pai zhao"))
add(OfflineCmdBean("开始任务", "kai shi ren wu"))
add(OfflineCmdBean("重拍", "chong pai"))
add(OfflineCmdBean("重新拍", "chong xin pai"))
add(OfflineCmdBean("再拍一次", "zai pai yi ci"))
} }
fun init(){ fun init(){
val service = GlassSdk.getGlassOfflineCmdService() val service = GlassSdk.getGlassOfflineCmdService()

查看文件

@ -37,6 +37,7 @@ class InspectionActivity : BaseListFormLayoutNormalActivity<ItemItem, ItemListVM
} }
override fun initData() { override fun initData() {
super.initData() super.initData()
window.addFlags(android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
OfflineCmdServiceHelper.addOnLineListener(listener) OfflineCmdServiceHelper.addOnLineListener(listener)
} }

查看文件

@ -1,22 +0,0 @@
package com.nova.brain.glass.ui
import android.os.Bundle
import androidx.activity.viewModels
import com.nova.brain.glass.R
import com.nova.brain.glass.databinding.ActivityMainBinding
import com.nova.brain.glass.viewmodel.MainVM
import com.xuqm.base.ui.BaseActivity
class MainActivity : BaseActivity<ActivityMainBinding>() {
override fun fullscreen(): Boolean = true
override fun getLayoutId(): Int = R.layout.activity_main
private val vm: MainVM by viewModels()
override fun initView(savedInstanceState: Bundle?) {
super.initView(savedInstanceState)
}
}

查看文件

@ -37,6 +37,7 @@ class ReviewActivity : BaseListFormLayoutNormalActivity<ItemItem, ItemListVM, Ac
} }
override fun initData() { override fun initData() {
super.initData() super.initData()
window.addFlags(android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
OfflineCmdServiceHelper.addOnLineListener(listener) OfflineCmdServiceHelper.addOnLineListener(listener)
} }

查看文件

@ -1,19 +1,29 @@
package com.nova.brain.glass.ui package com.nova.brain.glass.ui
import android.content.Intent
import android.graphics.BitmapFactory
import android.os.Environment
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.nova.brain.glass.R import com.nova.brain.glass.R
import com.nova.brain.glass.databinding.ActivitySprayingBinding import com.nova.brain.glass.databinding.ActivitySprayingBinding
import com.nova.brain.glass.helper.OfflineCmdListener import com.nova.brain.glass.helper.OfflineCmdListener
import com.nova.brain.glass.helper.OfflineCmdServiceHelper import com.nova.brain.glass.helper.OfflineCmdServiceHelper
import com.nova.brain.glass.model.ItemItem import com.nova.brain.glass.model.ItemItem
import com.nova.brain.glass.viewmodel.ItemListVM
import com.nova.brain.glass.viewmodel.SprayingVM import com.nova.brain.glass.viewmodel.SprayingVM
import com.rokid.security.glass3.open.sdk.GlassSdk
import com.rokid.security.glass3.sdk.base.data.media.PhotoResolution
import com.rokid.security.system.server.media.callback.PhotoFileCallback
import com.xuqm.base.adapter.BasePagedAdapter import com.xuqm.base.adapter.BasePagedAdapter
import com.xuqm.base.adapter.CommonPagedAdapter import com.xuqm.base.adapter.CommonPagedAdapter
import com.xuqm.base.adapter.ViewHolder import com.xuqm.base.adapter.ViewHolder
import com.xuqm.base.common.LogHelper
import com.xuqm.base.extensions.showMessage
import com.xuqm.base.ui.BaseListFormLayoutNormalActivity import com.xuqm.base.ui.BaseListFormLayoutNormalActivity
import java.io.File
import java.util.UUID
class SprayingActivity : BaseListFormLayoutNormalActivity<ItemItem, SprayingVM, ActivitySprayingBinding>() { class SprayingActivity :
BaseListFormLayoutNormalActivity<ItemItem, SprayingVM, ActivitySprayingBinding>() {
override fun getLayoutId(): Int = R.layout.activity_spraying override fun getLayoutId(): Int = R.layout.activity_spraying
override fun fullscreen(): Boolean = true override fun fullscreen(): Boolean = true
@ -26,28 +36,91 @@ class SprayingActivity : BaseListFormLayoutNormalActivity<ItemItem, SprayingVM,
"退出", "返回", "退回" -> { "退出", "返回", "退回" -> {
finish() finish()
} }
"开始", "拍照", "开始拍照", "开始任务" -> {
runOnUiThread {
binding.hint.text = "拍照中,请稍后..."
}
isPhoto = true
takePhoto()
} }
} }
} }
} }
}
fun takePhoto() {
val fileName = "test_${System.currentTimeMillis()}.png"
val publicPicturesDir =
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)
val file = File(publicPicturesDir, fileName)
GlassSdk.getGlassMediaService()
?.takePhoto(PhotoResolution.RESOLUTION_480P, file.absolutePath)
}
private val photoCallbackId = UUID.randomUUID().toString()
private val mPhotoFileCallback = object : PhotoFileCallback.Stub() {
override fun onTakePhoto(path: String) {
LogHelper.d("onTakePhoto-->path = $path")
}
override fun getCallbackId(): String {
return photoCallbackId
}
override fun onTakePhotoV2(path: String?, width: Int, height: Int) {
LogHelper.d("width:$width--height:$height")
if (path == null) {
if (isPhoto) {
isPhoto = false
takePhoto()
} else {
runOnUiThread {
binding.hint.text = "单击或语音输入“开始”,进入下一步"
}
"相机异常".showMessage()
}
} else {
startActivity(Intent(this@SprayingActivity, SprayingOCRActivity::class.java).apply {
putExtra("path", path)
})
finish()
// runOnUiThread {
// binding.hint.text = "单击或语音输入“开始”,进入下一步"
// binding.iv.setImageBitmap(BitmapFactory.decodeFile(path))
// }
}
}
}
override fun initData() { override fun initData() {
super.initData() super.initData()
window.addFlags(android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
OfflineCmdServiceHelper.addOnLineListener(listener) OfflineCmdServiceHelper.addOnLineListener(listener)
GlassSdk.getGlassMediaService()?.addPhotoCallback(mPhotoFileCallback)
} }
override fun onDestroy() { override fun onDestroy() {
super.onDestroy() super.onDestroy()
OfflineCmdServiceHelper.removeOnLineListener(listener) OfflineCmdServiceHelper.removeOnLineListener(listener)
GlassSdk.getGlassMediaService()?.removePhotoCallback(mPhotoFileCallback)
} }
private var isPhoto = false
private val adapter = object : CommonPagedAdapter<ItemItem>(R.layout.item_photo) { private val adapter = object : CommonPagedAdapter<ItemItem>(R.layout.item_photo) {
override fun convert(holder: ViewHolder, item: ItemItem, position: Int) { override fun convert(holder: ViewHolder, item: ItemItem, position: Int) {
holder holder
.setClickListener(R.id.photo) { .setClickListener(R.id.photo) {
when (item.text) { when (item.text) {
"拍照" -> { "拍照" -> {
runOnUiThread {
binding.hint.text = "拍照中,请稍后..."
}
isPhoto = true
takePhoto()
} }
} }
} }

查看文件

@ -0,0 +1,129 @@
package com.nova.brain.glass.ui
import android.R.attr.path
import android.graphics.BitmapFactory
import android.os.Environment
import android.view.WindowManager
import androidx.recyclerview.widget.RecyclerView
import com.nova.brain.glass.R
import com.nova.brain.glass.databinding.ActivitySprayingBinding
import com.nova.brain.glass.databinding.ActivitySprayingOcrBinding
import com.nova.brain.glass.helper.OfflineCmdListener
import com.nova.brain.glass.helper.OfflineCmdServiceHelper
import com.nova.brain.glass.model.ItemItem
import com.nova.brain.glass.viewmodel.SprayingVM
import com.rokid.security.glass3.open.sdk.GlassSdk
import com.rokid.security.glass3.sdk.base.data.media.PhotoResolution
import com.rokid.security.system.server.media.callback.PhotoFileCallback
import com.xuqm.base.adapter.BasePagedAdapter
import com.xuqm.base.adapter.CommonPagedAdapter
import com.xuqm.base.adapter.ViewHolder
import com.xuqm.base.common.FileHelper
import com.xuqm.base.common.LogHelper
import com.xuqm.base.extensions.showMessage
import com.xuqm.base.ui.BaseListFormLayoutNormalActivity
import java.io.File
import java.util.UUID
class SprayingOCRActivity :
BaseListFormLayoutNormalActivity<ItemItem, SprayingVM, ActivitySprayingOcrBinding>() {
override fun getLayoutId(): Int = R.layout.activity_spraying_ocr
override fun fullscreen(): Boolean = true
override fun getRecyclerOrientation(): Int = RecyclerView.VERTICAL
private val listener = object : OfflineCmdListener {
override fun onOfflineCmd(cmd: String) {
runOnUiThread {
when (cmd) {
"退出", "返回", "退回" -> {
finish()
}
"开始", "拍照", "开始拍照", "开始任务", "重拍", "重新拍", "在拍一次" -> {
runOnUiThread {
binding.hint.text = "拍照中,请稍后..."
}
isPhoto = true
takePhoto()
}
}
}
}
}
fun takePhoto() {
val fileName = "test_${System.currentTimeMillis()}.png"
val publicPicturesDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)
val file = File(publicPicturesDir, fileName)
GlassSdk.getGlassMediaService()?.takePhoto(PhotoResolution.RESOLUTION_480P, file.absolutePath)
}
private val photoCallbackId = UUID.randomUUID().toString()
private val mPhotoFileCallback = object : PhotoFileCallback.Stub() {
override fun onTakePhoto(path: String) {
LogHelper.d("onTakePhoto-->path = $path")
}
override fun getCallbackId(): String {
return photoCallbackId
}
override fun onTakePhotoV2(path: String?, width: Int, height: Int) {
LogHelper.d("width:$width--height:$height")
if (path == null) {
if (isPhoto) {
isPhoto = false
takePhoto()
} else {
runOnUiThread {
binding.hint.text = "单击或语音输入“重拍”,可重新拍摄"
}
"相机异常".showMessage()
}
} else {
runOnUiThread {
binding.hint.text = "单击或语音输入“重拍”,可重新拍摄"
binding.content.setImageBitmap(BitmapFactory.decodeFile(path))
}
}
}
}
override fun initData() {
super.initData()
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
OfflineCmdServiceHelper.addOnLineListener(listener)
GlassSdk.getGlassMediaService()?.addPhotoCallback(mPhotoFileCallback)
intent.getStringExtra("path")?.apply {
binding.content.setImageBitmap(BitmapFactory.decodeFile(this))
}
}
override fun onDestroy() {
super.onDestroy()
OfflineCmdServiceHelper.removeOnLineListener(listener)
GlassSdk.getGlassMediaService()?.removePhotoCallback(mPhotoFileCallback)
}
private var isPhoto = false
private val adapter = object : CommonPagedAdapter<ItemItem>(R.layout.item_photo) {
override fun convert(holder: ViewHolder, item: ItemItem, position: Int) {
holder
.setClickListener(R.id.photo) {
when (item.text) {
"拍照" -> {
runOnUiThread {
binding.hint.text = "拍照中,请稍后..."
}
isPhoto = true
takePhoto()
}
}
}
}
}
override fun adapter(): BasePagedAdapter<ItemItem> = adapter
}

查看文件

@ -39,13 +39,11 @@ class WelcomeActivity : BaseActivity<ActivityWelcomeBinding>() {
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
LogHelper.d(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>")
OfflineCmdServiceHelper.addOnLineListener(offlineCmdListener) OfflineCmdServiceHelper.addOnLineListener(offlineCmdListener)
} }
override fun onPause() { override fun onPause() {
super.onPause() super.onPause()
LogHelper.d("---------------------------")
OfflineCmdServiceHelper.removeOnLineListener(offlineCmdListener) OfflineCmdServiceHelper.removeOnLineListener(offlineCmdListener)
} }

查看文件

@ -94,6 +94,7 @@
<TextView <TextView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_marginStart="15dp" android:layout_marginStart="15dp"
android:id="@+id/hint"
android:layout_marginTop="10dp" android:layout_marginTop="10dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@+id/baseRecyclerView" app:layout_constraintTop_toBottomOf="@+id/baseRecyclerView"
@ -102,6 +103,13 @@
android:text="单击或语音输入“开始”,进入下一步" android:text="单击或语音输入“开始”,进入下一步"
android:textColor="#ff40FF5E" android:textColor="#ff40FF5E"
android:textSize="14sp"/> android:textSize="14sp"/>
<ImageView
android:layout_width="120dp"
android:id="@+id/iv"
android:layout_height="150dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/hint"/>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</layout> </layout>

查看文件

@ -0,0 +1,58 @@
<?xml version="1.0" encoding="utf-8"?>
<layout>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/app_color_black">
<TextView
android:id="@+id/tvTaskHeader"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="67dp"
android:background="@drawable/bg_item"
android:text="OCR识别中,请稍后..."
android:gravity="center"
android:textColor="#ff40FF5E"
android:textSize="20sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:layout_width="120dp"
android:id="@+id/content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tvTaskHeader"/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/baseRecyclerView"
android:layout_width="0dp"
android:layout_height="88dp"
android:clipToPadding="false"
android:layout_marginTop="12dp"
android:overScrollMode="never"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/content" />
<TextView
android:layout_width="match_parent"
android:layout_marginStart="15dp"
android:id="@+id/hint"
android:layout_marginTop="10dp"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@+id/baseRecyclerView"
android:layout_marginVertical="4dp"
android:gravity="center"
android:text="单击或语音输入“重拍”,可重新拍摄"
android:textColor="#ff40FF5E"
android:textSize="14sp"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

查看文件

@ -8,18 +8,13 @@
android:id="@+id/tv" android:id="@+id/tv"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="@color/app_color_black"
android:layout_alignParentTop="true"
android:textColor="#ff3FFF5F" android:textColor="#ff3FFF5F"
android:textSize="19sp" android:textSize="19sp"
android:text="您可以说Nova,我的任务有哪些?" android:text="您可以说Nova,我的任务有哪些?"
android:fontFamily="Source Han Sans"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
android:gravity="left|top"
android:alpha="1"
/> />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>