Merge branch 'dev_robotaxi-d-app-module_290_220715_2.9.0' into dev_robotaxi-d-app-module-videoprocess

# Conflicts:
#	OCH/mogo-och-taxi-passenger/src/main/res/values/strings.xml
This commit is contained in:
yangyakun
2022-07-19 17:06:42 +08:00
188 changed files with 5113 additions and 768 deletions

View File

@@ -315,6 +315,10 @@ class MoGoAutopilotProvider :
return AdasManager.getInstance().startRecordPackage(id, duration, type)
}
override fun recordPackage(type: Int, id: Int, duration: Int, bduration: Int): Boolean {
return AdasManager.getInstance().startRecordPackage(id,duration, type, bduration)
}
override fun stopRecord(type: Int, id: Int): Boolean {
return AdasManager.getInstance().stopRecordPackage(id, type)
}
@@ -353,17 +357,14 @@ class MoGoAutopilotProvider :
}
}
/**
* 演示模式(美化模式)
* 演示模式(美化模式)设置只限定于鹰眼
* isEnable = true 开启
* isEnable = false 关闭
*/
override fun setDemoMode(isEnable: Boolean) {
if (isEnable) {
AdasManager.getInstance().sendDemoModeReq(1)
} else {
AdasManager.getInstance().sendDemoModeReq(0)
}
// 同步给乘客端
if (AppIdentityModeUtils.isDriver(FunctionBuildConfig.appIdentityMode)) {
var byteArray = if (isEnable) byteArrayOf(1) else byteArrayOf(0)
@@ -382,6 +383,19 @@ class MoGoAutopilotProvider :
}
}
/**
* 设置工控机演示模式(美化模式)开启、关闭
* isEnable = true 开启
* isEnable = false 关闭
*/
override fun setIPCDemoMode(isEnable: Boolean) {
if (isEnable) {
AdasManager.getInstance().sendDemoModeReq(1)
} else {
AdasManager.getInstance().sendDemoModeReq(0)
}
}
/**
* 雨天模式
* isEnable = true 开启
@@ -395,6 +409,13 @@ class MoGoAutopilotProvider :
}
}
/**
* 获取数据采集录制模式配置列表
*/
override fun getBadCaseConfig() {
AdasManager.getInstance().sendRecordDataConfigReq()
}
/**
* 发送工控机所有节点重启命令
*/
@@ -508,4 +529,9 @@ class MoGoAutopilotProvider :
override fun getTeleTimeStamp(): Long {
return msgHandler.getTeleTimeStamp()
}
override fun sendStatusQueryReq() {
Log.d(TAG, "---- sendStatusQueryReq ----")
AdasManager.getInstance().sendStatusQueryReq()
}
}

View File

@@ -31,6 +31,7 @@ import com.mogo.eagle.core.function.call.autopilot.CallerAutoPilotStatusListener
import com.mogo.eagle.core.function.call.autopilot.CallerAutoPilotStatusListenerManager.invokeAutoPilotStatus
import com.mogo.eagle.core.function.call.autopilot.CallerAutoPilotStatusListenerManager.invokeAutopilotGuardian
import com.mogo.eagle.core.function.call.autopilot.CallerAutoPilotStatusListenerManager.invokeAutopilotSNRequest
import com.mogo.eagle.core.function.call.autopilot.CallerAutoPilotStatusListenerManager.invokeAutopilotStatusRespByQuery
import com.mogo.eagle.core.function.call.autopilot.CallerAutopilotCarConfigListenerManager.invokeAutopilotCarConfigData
import com.mogo.eagle.core.function.call.autopilot.CallerAutopilotCarStatusListenerManager.invokeAutopilotCarStateData
import com.mogo.eagle.core.function.call.autopilot.CallerAutopilotIdentifyListenerManager
@@ -43,6 +44,7 @@ import com.mogo.eagle.core.function.call.autopilot.CallerAutopilotPointCloudList
import com.mogo.eagle.core.function.call.autopilot.CallerAutopilotRecordListenerManager.invokeAutopilotRecordResult
import com.mogo.eagle.core.function.call.autopilot.CallerAutopilotVehicleStateListenerManager
import com.mogo.eagle.core.function.call.map.CallerMapUIServiceManager
import com.mogo.eagle.core.network.utils.*
import com.zhidao.support.adas.high.AdasManager
import com.zhidao.support.adas.high.OnAdasListener
import com.zhidao.support.adas.high.common.ProtocolStatus
@@ -244,10 +246,11 @@ class MoGoAdasListenerImpl : OnAdasListener {
//他车轨迹预测
}
override fun onPointCloud(header: MessagePad.Header?, pointCloud: MogoPointCloudOuterClass.MogoPointCloud?) {
//点云数据透传
//Logger.d("pointCloud","pointCloud"+pointCloud);
}
// override fun onPointCloud(header: MessagePad.Header?, pointCloud: MogoPointCloudOuterClass.MogoPointCloud?) {
// //点云数据透传
// //Logger.d("pointCloud","pointCloud"+pointCloud);
// CallerAutopilotPointCloudListenerManager.invokeAutopilotPointCloudDataUpdate(header, pointCloud)
// }
override fun onPointCloud(pointCloud: ByteArray?) {
//点云数据透传
@@ -369,6 +372,20 @@ class MoGoAdasListenerImpl : OnAdasListener {
statusInfo: SystemStatusInfo.StatusInfo?
) {
//状态查询应答
statusInfo?.let {
Log.d(TAG, GsonUtil.jsonFromObject(it))
}
invokeAutopilotStatusRespByQuery(statusInfo)
}
/**
* 数据采集配置应答
*/
override fun onRecordDataConfigResp(
header: MessagePad.Header?,
config: MessagePad.RecordDataConfig?
) {
}

View File

@@ -54,6 +54,10 @@ class MoGoAdasMsgConnectStatusListenerImpl : OnAdasConnectStatusListener,
updateDriveStatusTask()
//每次工控机连接成功后,需同步当前设置的美化模式状态
CallerAutoPilotManager.setDemoMode(FunctionBuildConfig.isDemoMode)
//当连接状态是关闭美化模式时,同步给工控机
if(!FunctionBuildConfig.isDemoMode){
CallerAutoPilotManager.setIPCDemoMode(FunctionBuildConfig.isDemoMode)
}
//每次工控机连接成功后,需同步当前设置的雨天模式状态
CallerAutoPilotManager.setRainMode(FunctionBuildConfig.isRainMode)
} else if (ipcConnectionStatus == Constants.IPC_CONNECTION_STATUS.DISCONNECTED) {

View File

@@ -90,6 +90,7 @@ dependencies {
implementation rootProject.ext.dependencies.mogo_core_function_api
implementation rootProject.ext.dependencies.mogo_core_function_call
implementation rootProject.ext.dependencies.mogo_core_data
implementation rootProject.ext.dependencies.mogo_core_res
}else {
implementation project(':services:mogo-service-api')
implementation project(':modules:mogo-module-common')
@@ -98,6 +99,7 @@ dependencies {
implementation project(':core:mogo-core-function-api')
implementation project(':core:mogo-core-function-call')
implementation project(':core:mogo-core-data')
implementation project(':core:mogo-core-res')
}
}

View File

@@ -78,8 +78,12 @@ class DevaToolsProvider : IDevaToolsProvider {
sceneManager.updateSceneTAG(sceneTag)
}
override fun initBadCase(view: View, onShow: (() -> Unit)?, onHide: (() -> Unit)?) {
BadCaseManager.init(view, onShow, onHide)
override fun initBadCase(view: View) {
BadCaseManager.initBadCase(view)
}
override fun initAiCollect(view: View) {
BadCaseManager.initAiCollect(view)
}
override fun onReceiveBadCaseRecord(record: RecordPanelOuterClass.RecordPanel) {
@@ -87,7 +91,7 @@ class DevaToolsProvider : IDevaToolsProvider {
}
override fun showFeedbackWindow(ctx: Context) {
FeedbackManager.showFeedbackWindow(ctx)
BadCaseManager.showBadCaseConfigWindow(ctx)
}
override fun getUpgradeVersionUrls(versionName: String) {

View File

@@ -1,9 +1,8 @@
package com.zhjt.mogo_core_function_devatools.badcase
import android.transition.AutoTransition
import android.transition.TransitionManager
import android.content.Context
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.Lifecycle.Event
@@ -15,24 +14,24 @@ import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import com.mogo.cloud.passport.MoGoAiCloudClientConfig
import com.mogo.eagle.core.function.call.autopilot.CallerAutopilotVehicleStateListenerManager
import com.mogo.eagle.core.utilcode.mogo.logger.scene.SceneConstant.Companion.M_DEVA
import com.mogo.eagle.core.utilcode.kotlin.PX
import com.mogo.eagle.core.utilcode.mogo.logger.CallerLogger
import com.mogo.eagle.core.utilcode.kotlin.lifecycleOwner
import com.mogo.eagle.core.utilcode.kotlin.onClick
import com.mogo.eagle.core.utilcode.mogo.logger.CallerLogger
import com.mogo.eagle.core.utilcode.mogo.logger.scene.SceneConstant.Companion.M_DEVA
import com.mogo.eagle.core.utilcode.reminder.Reminder
import com.mogo.eagle.core.utilcode.reminder.api.IReminder
import com.mogo.eagle.core.utilcode.reminder.api.IReminder.IGlobalStateChangeListener
import com.zhjt.mogo_core_function_devatools.badcase.repository.net.api.entity.BadCaseResponse.Reason
import com.zhjt.mogo_core_function_devatools.badcase.biz.BadCasePresenter
import com.zhjt.mogo_core_function_devatools.badcase.biz.BadCaseView
import com.mogo.eagle.core.utilcode.util.ClickUtils
import com.mogo.eagle.core.utilcode.util.ToastUtils
import com.zhjt.mogo_core_function_devatools.badcase.biz.*
import com.zhjt.mogo_core_function_devatools.badcase.repository.db.entity.AutoPilotRecord
import com.zhjt.mogo_core_function_devatools.badcase.repository.net.api.entity.BadCaseResponse.Reason
import com.zhjt.mogo_core_function_devatools.ext.enqueuePop
import com.zhjt.mogo_core_function_devatools.ext.toast
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.Channel
import record_cache.RecordPanelOuterClass
import java.lang.IllegalStateException
import java.lang.ref.WeakReference
import java.util.concurrent.TimeUnit
@@ -46,9 +45,6 @@ internal object BadCaseManager : LifecycleEventObserver {
private val CASE_EXPIRE_DURATION: Long = TimeUnit.HOURS.toMillis(4)/* TimeUnit.SECONDS.toMillis(10) */
private var onShow: (() -> Unit)? = null
private var onHide: (() -> Unit)? = null
private var hideFloat: (() -> Unit)? = null
@Volatile
@@ -85,19 +81,69 @@ internal object BadCaseManager : LifecycleEventObserver {
field
}
fun init(view: View, onShow: (() -> Unit)?, onHide: (() -> Unit)?) {
this.viewHolder = WeakReference(view)
view.lifecycleOwner.lifecycle.addObserver(this)
this.onShow = onShow
this.onHide = onHide
register()
recoverBadCase()
/**
* 展示BadCase配置页面
*/
fun showBadCaseConfigWindow(context: Context){
val badCaseConfigView = BadCaseConfigView(context)
badCaseConfigView.setClickListener(object: BadCaseConfigView.ClickListener{
override fun onClose() {
hideFloat?.invoke()
hideFloat = null
}
})
context.enqueuePop(badCaseConfigView,960.PX, WindowManager.LayoutParams.MATCH_PARENT, key = "BadCaseConfigView").also {
hideFloat = it
}
}
/**
* 主动采集BadCase
*/
fun initBadCase(view: View){
val activity = view.context as? FragmentActivity ?: throw IllegalStateException("please ensure context is FragmentActivity.")
view.setOnClickListener {
if(ClickUtils.isFastClick()){
val initiativeBadCaseWindow = InitiativeBadCaseWindow(activity)
initiativeBadCaseWindow.setClickListener(object: InitiativeBadCaseWindow.ClickListener{
override fun closeWindow() {
initiativeBadCaseWindow.hideFloatWindow()
}
})
initiativeBadCaseWindow.showFloatWindow()
}else{
ToastUtils.showShort("请勿连续点击,稍后再试")
}
}
}
/**
* AI数据采集
*/
fun initAiCollect(view: View){
val activity = view.context as? FragmentActivity ?: throw IllegalStateException("please ensure context is FragmentActivity.")
view.setOnClickListener {
if(ClickUtils.isFastClick()){
val aiDataCollectWindow = AIDataCollectWindow(activity)
aiDataCollectWindow.setClickListener(object: AIDataCollectWindow.ClickListener{
override fun closeWindow() {
aiDataCollectWindow.hideFloatWindow()
}
})
aiDataCollectWindow.showFloatWindow()
}else{
ToastUtils.showShort("请勿连续点击,稍后再试")
}
}
}
@OptIn(ExperimentalCoroutinesApi::class)
private fun register() {
scope?.launch(Dispatchers.Default) {
while (true) {
showBadCaseInternal(AutoPilotRecord())
CallerLogger.d("$M_DEVA$TAG", "---- 开始监听BadCase事件 ----")
val old = record
if (old == null || old.consumed) {
@@ -131,12 +177,6 @@ internal object BadCaseManager : LifecycleEventObserver {
showBadCaseInternal(it)
}
}
} else {
CallerLogger.d("$M_DEVA$TAG", "record: [$old] hasn't been consumed~~~~")
withContext(Dispatchers.Main) {
showEntry()
}
delay(1000)
}
}
}
@@ -195,7 +235,7 @@ internal object BadCaseManager : LifecycleEventObserver {
private fun CoroutineScope.showBadCaseInternal(record: AutoPilotRecord) = launch {
viewHolder?.get()?.also { itx ->
presenter.updateLastModified(CallerAutopilotVehicleStateListenerManager.getAutopilotTimeStamp())
showEntry()
itx.onClick {
showBadCaseFloat(
onDismiss = {
@@ -221,7 +261,7 @@ internal object BadCaseManager : LifecycleEventObserver {
withContext(Dispatchers.IO) {
presenter.deleteRecord(record)
}
hideEntry()
hideFloat?.invoke()
hideFloat = null
}
@@ -235,7 +275,6 @@ internal object BadCaseManager : LifecycleEventObserver {
dismissJob?.takeIf { it.isActive }?.cancel()
return scope?.launch {
delay(CASE_EXPIRE_DURATION)
hideEntry()
record?.also {
it.consumed = true
withContext(Dispatchers.IO) {
@@ -245,20 +284,6 @@ internal object BadCaseManager : LifecycleEventObserver {
}
}
private fun showEntry() {
viewHolder?.get()?.takeIf { it.visibility != View.VISIBLE }?.also {
it.toggle(true)
onShow?.invoke()
}
}
private fun hideEntry() {
viewHolder?.get()?.takeIf { it.visibility == View.VISIBLE }?.also {
it.toggle(false)
onHide?.invoke()
}
}
private fun showBadCaseFloat(onDismiss: () -> Unit, onSelect:suspend (reason: Reason) -> Unit) {
val activity = viewHolder?.get()?.context as? FragmentActivity ?: throw IllegalStateException("please ensure context is FragmentActivity.")
BadCaseView(activity).also { itx ->
@@ -291,21 +316,12 @@ internal object BadCaseManager : LifecycleEventObserver {
if (event == ON_DESTROY) {
Reminder.unRegisterGlobalStateChangeListener(listener)
dismissJob?.takeIf { it.isActive }?.cancel()
onHide = null
onShow = null
hideFloat = null
}
}
}
fun <T : View> T.toggle(show: Boolean) {
val group = (parent as? ViewGroup) ?: return
val target = if (show) View.VISIBLE else View.GONE
takeIf { it.visibility != target }?.also {
TransitionManager.beginDelayedTransition(group, AutoTransition())
it.visibility = target
}
}
internal fun RecordPanelOuterClass.RecordPanel.toRecord(): AutoPilotRecord =
AutoPilotRecord().also {

View File

@@ -0,0 +1,348 @@
package com.zhjt.mogo_core_function_devatools.badcase.biz
import android.annotation.SuppressLint
import android.app.Activity
import android.graphics.PixelFormat
import android.os.SystemClock
import android.util.DisplayMetrics
import android.view.*
import android.widget.RadioButton
import android.widget.TextView
import com.mogo.cloud.passport.MoGoAiCloudClientConfig
import com.mogo.eagle.core.function.api.autopilot.IMoGoAutopilotRecordListener
import com.mogo.eagle.core.function.call.autopilot.CallerAutoPilotManager
import com.mogo.eagle.core.utilcode.mogo.logger.CallerLogger
import com.mogo.eagle.core.utilcode.mogo.logger.scene.SceneConstant.Companion.M_DEVA
import com.mogo.eagle.core.utilcode.mogo.toast.TipToast
import com.mogo.eagle.core.utilcode.util.TimeUtils
import com.mogo.eagle.core.utilcode.util.TimeUtils.millis2String
import com.zhjt.mogo_core_function_devatools.R
import com.zhjt.mogo_core_function_devatools.badcase.consts.BadCaseConfig
import com.zhjt.mogo_core_function_devatools.badcase.toRecord
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import record_cache.RecordPanelOuterClass
import java.lang.reflect.Field
import java.util.*
/**
* @author XuXinChao
* @description AI数据采集弹窗
* @since: 2022/7/12
*/
class AIDataCollectWindow constructor(activity: Activity) : View.OnTouchListener,
IMoGoAutopilotRecordListener {
companion object {
const val TAG = "AIDataCollectWindow"
}
private var mActivity: Activity = activity
private var mWindowParams: WindowManager.LayoutParams? = null
private var mWindowManager: WindowManager? = null
private lateinit var tvCollectNum: TextView //采集弹窗数量
private lateinit var tvCollectTime: TextView //采集时间
private lateinit var rbLargeCar: RadioButton //大型车
private lateinit var rbTrafficLight: RadioButton //交通灯
private lateinit var rbWater: RadioButton //积水
private lateinit var rbConstruction: RadioButton //施工
private lateinit var rbAccident: RadioButton //车祸路段
private lateinit var rbRain: RadioButton //中雨交通流
private lateinit var rbNightTraffic: RadioButton //夜间交通流
private lateinit var tvCollectReport: TextView //上报按钮
private lateinit var tvCollectCancel: TextView //取消按钮
private var collectReason: String = "大型车:大货、大巴、特种车辆"
private lateinit var mFloatLayout: View
private var mInViewX = 0f
private var mInViewY = 0f
private var mDownInScreenX = 0f
private var mDownInScreenY = 0f
private var mInScreenX = 0f
private var mInScreenY = 0f
private var clickListener: ClickListener? = null
init {
initFloatWindow();
}
private val presenter by lazy {
BadCasePresenter()
}
@SuppressLint("SetTextI18n")
private fun initFloatWindow(){
mFloatLayout = LayoutInflater.from(mActivity).inflate(R.layout.view_ai_data_collect, null) as View
mFloatLayout.setOnTouchListener(this)
tvCollectNum = mFloatLayout.findViewById(R.id.tvCollectNum)
tvCollectTime = mFloatLayout.findViewById(R.id.tvCollectTime)
rbLargeCar = mFloatLayout.findViewById(R.id.rbLargeCar)
rbTrafficLight = mFloatLayout.findViewById(R.id.rbTrafficLight)
rbWater = mFloatLayout.findViewById(R.id.rbWater)
rbConstruction = mFloatLayout.findViewById(R.id.rbConstruction)
rbAccident = mFloatLayout.findViewById(R.id.rbAccident)
rbRain = mFloatLayout.findViewById(R.id.rbRain)
rbNightTraffic = mFloatLayout.findViewById(R.id.rbNightTraffic)
tvCollectReport = mFloatLayout.findViewById(R.id.tvCollectReport)
tvCollectCancel = mFloatLayout.findViewById(R.id.tvCollectCancel)
tvCollectNum.text = BadCaseConfig.windowNum.toString()
BadCaseConfig.windowNum++
tvCollectTime.text ="时间:${millis2String(System.currentTimeMillis(),TimeUtils.getHourMinSecondFormat())}"
CallerAutoPilotManager.recordPackage(
99,
Random(SystemClock.elapsedRealtime()).nextInt(),
20,
12
)
//大型车
rbLargeCar.setOnClickListener{
setRadioButtonStatus(
largeCarStatus = true,
trafficLightStatus = false,
waterStatus = false,
constructionStatus = false,
accidentStatus = false,
rainStatus = false,
nightTrafficStatus = false
)
collectReason = "大型车:大货、大巴、特种车辆"
}
//交通灯
rbTrafficLight.setOnClickListener {
setRadioButtonStatus(
largeCarStatus = false,
trafficLightStatus = true,
waterStatus = false,
constructionStatus = false,
accidentStatus = false,
rainStatus = false,
nightTrafficStatus = false
)
collectReason = "交通灯:水平、箭头、雨天交通灯"
}
//积水
rbWater.setOnClickListener {
setRadioButtonStatus(
largeCarStatus = false,
trafficLightStatus = false,
waterStatus = true,
constructionStatus = false,
accidentStatus = false,
rainStatus = false,
nightTrafficStatus = false
)
collectReason = "积水距离10米内面积大于1平米"
}
//施工
rbConstruction.setOnClickListener {
setRadioButtonStatus(
largeCarStatus = false,
trafficLightStatus = false,
waterStatus = false,
constructionStatus = true,
accidentStatus = false,
rainStatus = false,
nightTrafficStatus = false
)
collectReason = "施工:锥桶、路障"
}
//车祸路段
rbAccident.setOnClickListener {
setRadioButtonStatus(
largeCarStatus = false,
trafficLightStatus = false,
waterStatus = false,
constructionStatus = false,
accidentStatus = true,
rainStatus = false,
nightTrafficStatus = false
)
collectReason = "车祸路段:有三角板"
}
//中雨交通流
rbRain.setOnClickListener {
setRadioButtonStatus(
largeCarStatus = false,
trafficLightStatus = false,
waterStatus = false,
constructionStatus = false,
accidentStatus = false,
rainStatus = true,
nightTrafficStatus = false
)
collectReason = "中雨交通流"
}
//夜间交通流
rbNightTraffic.setOnClickListener {
setRadioButtonStatus(
largeCarStatus = false,
trafficLightStatus = false,
waterStatus = false,
constructionStatus = false,
accidentStatus = false,
rainStatus = false,
nightTrafficStatus = true
)
collectReason = "夜间交通流"
}
//上报
tvCollectReport.setOnClickListener {
GlobalScope.launch{
val uploadResult = presenter.upload(mutableMapOf<String, String>().also { itx ->
itx["carLicense"] = "DFD02313"
itx["filename"] = "/home/mogo/data/bags/badcase/20220706145143/20220706145143-265939904-rosmaster-XXXX000000.bag"
itx["filesize"] = "0"
itx["key"] = "265939904"
itx["reason"] = collectReason
itx["duration"] = "16"
itx["startTime"] = "20220706145203"
itx["channel"] = "AI"
itx["carSn"] = MoGoAiCloudClientConfig.getInstance().sn
itx["userRole"] = "安全员"
itx["audioUrl"] = "http://petchfile-1255510688.cos.ap-beijing.myqcloud.com/CarPad/mogopadlog/deviceId/2022-07-07/AUDIO_myTest.wav"
itx["mapVersion"] = "MAP-taxi-hq_RoboTaxi_hq_H9_2.3.0.5_20220410"
itx["eyeVersion"] = "2.8.0"
itx["coordinate"] = ""
})
if (uploadResult == null || uploadResult.code != 200) {
TipToast.shortTip("上报失败")
} else {
TipToast.shortTip("上报成功")
}
}
}
//取消
tvCollectCancel.setOnClickListener {
BadCaseConfig.windowNum--
clickListener?.closeWindow()
}
mWindowParams = WindowManager.LayoutParams()
mWindowManager = mActivity.windowManager
mWindowParams?.let {
it.format = PixelFormat.RGBA_8888
it.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
it.gravity = Gravity.START or Gravity.TOP
it.width = 924
it.height = 668
it.alpha = 0.9f
}
}
override fun onAutopilotRecordResult(recordPanel: RecordPanelOuterClass.RecordPanel) {
CallerLogger.d("${M_DEVA}${TAG}", "-- 收到工控机录制任务回调 -- $recordPanel")
when(recordPanel.toRecord().stat){
100, 101 ->{
//成功结束录制
TipToast.shortTip("bag录制成功")
}
300 ->{
//开始录制
}
200 ->{
//录制失败
TipToast.shortTip("bag录制失败")
}
}
}
private fun setRadioButtonStatus(largeCarStatus: Boolean,trafficLightStatus: Boolean,waterStatus: Boolean,
constructionStatus: Boolean,accidentStatus: Boolean,rainStatus: Boolean,nightTrafficStatus: Boolean){
rbLargeCar.isChecked = largeCarStatus
rbTrafficLight.isChecked = trafficLightStatus
rbWater.isChecked = waterStatus
rbConstruction.isChecked = constructionStatus
rbAccident.isChecked = accidentStatus
rbRain.isChecked = rainStatus
rbNightTraffic.isChecked = nightTrafficStatus
}
override fun onTouch(v: View?, motionEvent: MotionEvent?): Boolean {
when (motionEvent?.action) {
MotionEvent.ACTION_DOWN -> {
// 获取相对View的坐标即以此View左上角为原点
mInViewX = motionEvent.x
mInViewY = motionEvent.y
// 获取相对屏幕的坐标,即以屏幕左上角为原点
mDownInScreenX = motionEvent.rawX
mDownInScreenY = motionEvent.rawY - getSysBarHeight(mActivity)
mInScreenX = motionEvent.rawX
mInScreenY = motionEvent.rawY - getSysBarHeight(mActivity)
}
MotionEvent.ACTION_MOVE -> {
// 更新浮动窗口位置参数
mInScreenX = motionEvent.rawX
mInScreenY = motionEvent.rawY - getSysBarHeight(mActivity)
mWindowParams!!.x = (mInScreenX - mInViewX).toInt()
mWindowParams!!.y = (mInScreenY - mInViewY).toInt()
// 手指移动的时候更新小悬浮窗的位置
mWindowManager!!.updateViewLayout(mFloatLayout, mWindowParams)
}
// MotionEvent.ACTION_UP -> // 如果手指离开屏幕时xDownInScreen和xInScreen相等且yDownInScreen和yInScreen相等则视为触发了单击事件。
// if (mDownInScreenX === mInScreenX && mDownInScreenY === mInScreenY) {
// }
}
return true
}
fun showFloatWindow() {
if (mFloatLayout.parent == null) {
val metrics = DisplayMetrics()
// 默认固定位置,靠屏幕右边缘的中间
mWindowManager!!.defaultDisplay.getMetrics(metrics)
mWindowParams!!.x = metrics.widthPixels
mWindowParams!!.y = metrics.heightPixels / 2 - getSysBarHeight(mActivity)
mWindowManager!!.addView(mFloatLayout, mWindowParams)
}
}
fun hideFloatWindow() {
if (mFloatLayout.parent != null) mWindowManager!!.removeView(mFloatLayout)
}
// 获取系统状态栏高度
private fun getSysBarHeight(activity: Activity): Int {
val c: Class<*>
val obj: Any
val field: Field
val x: Int
var sbar = 0
try {
c = Class.forName("com.android.internal.R\$dimen")
obj = c.newInstance()
field = c.getField("status_bar_height")
x = field.get(obj).toString().toInt()
sbar = activity.resources.getDimensionPixelSize(x)
} catch (e1: Exception) {
e1.printStackTrace()
}
return sbar
}
fun setClickListener(clickListener: ClickListener) {
this.clickListener = clickListener
}
interface ClickListener {
fun closeWindow()
}
}

View File

@@ -0,0 +1,252 @@
package com.zhjt.mogo_core_function_devatools.badcase.biz
import android.content.Context
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.util.AttributeSet
import android.view.LayoutInflater
import android.widget.RadioButton
import android.widget.RadioGroup
import androidx.constraintlayout.widget.ConstraintLayout
import com.mogo.eagle.core.function.api.autopilot.IMoGoAutopilotRecordListener
import com.mogo.eagle.core.function.call.autopilot.CallerAutoPilotManager
import com.mogo.eagle.core.function.call.autopilot.CallerAutopilotRecordListenerManager
import com.mogo.eagle.core.utilcode.util.SizeUtils
import com.mogo.eagle.core.utilcode.util.ThreadUtils
import com.mogo.eagle.core.utilcode.util.ToastUtils
import com.zhjt.mogo_core_function_devatools.R
import com.zhjt.mogo_core_function_devatools.badcase.consts.BadCaseConfig
import kotlinx.android.synthetic.main.layout_badcase_config.view.*
import mogo.telematics.pad.MessagePad
import java.lang.Exception
/**
* @author XuXinChao
* @description BadCase上报信息配置页面
* @since: 2022/7/5
*/
internal class BadCaseConfigView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : ConstraintLayout(context, attrs, defStyleAttr), IMoGoAutopilotRecordListener {
companion object {
const val TAG = "BadCaseConfigView"
}
private var clickListener: ClickListener? = null
private var mIdentity = "安全员"
private var mPreviousDuration = 12
private var mBackDuration = 8
private var mType = 1
init{
LayoutInflater.from(context).inflate(R.layout.layout_badcase_config, this, true)
background = ColorDrawable(Color.parseColor("#F0151D41"))
initView()
}
private fun initView(){
//关闭BadCase配置窗口
ivConfigClose.setOnClickListener {
clickListener?.onClose()
}
//安全员选项
rbSafetyOfficer.setOnClickListener {
mIdentity = "安全员"
rbDeveloper.isChecked = false
rbProduct.isChecked = false
}
//QA、研发选项
rbDeveloper.setOnClickListener {
mIdentity = "QA、研发"
rbSafetyOfficer.isChecked = false
rbProduct.isChecked = false
}
//产品、运营、演示选项
rbProduct.setOnClickListener {
mIdentity = "产品、运营、演示"
rbSafetyOfficer.isChecked = false
rbDeveloper.isChecked = false
}
//保存配置按钮
tvConfigSave.setOnClickListener {
//判断、保存录制时间信息
val preTimeStr=etInitiativePreTime.text.toString()
val afterTimeStr=etInitiativeAfterTime.text.toString()
try {
if(preTimeStr.isEmpty()){
mPreviousDuration = 12
}else{
mPreviousDuration = preTimeStr.toInt()
}
if(afterTimeStr.isEmpty()){
mBackDuration = 8
}else{
mBackDuration = afterTimeStr.toInt()
}
if(mPreviousDuration<0 || mPreviousDuration>300){
ToastUtils.showLong("采集时长最长300S")
return@setOnClickListener
}
if(mBackDuration<0 || mBackDuration>300){
ToastUtils.showLong("采集时长最长300S")
return@setOnClickListener
}
if((mPreviousDuration+mBackDuration)<5){
ToastUtils.showLong("采集时长最短5S")
return@setOnClickListener
}
if((mPreviousDuration+mBackDuration)>300){
ToastUtils.showLong("采集时长最长300S")
return@setOnClickListener
}
BadCaseConfig.previousDuration = mPreviousDuration
BadCaseConfig.backDuration = mBackDuration
BadCaseConfig.totalDuration = BadCaseConfig.previousDuration + BadCaseConfig.backDuration
}catch (e: Exception){
ToastUtils.showLong("输入时间格式不合法,请重新输入")
etInitiativePreTime.text = null
etInitiativeAfterTime.text = null
return@setOnClickListener
}
//保存身份信息
BadCaseConfig.identity = mIdentity
//保存录制模板采集类型
BadCaseConfig.type = mType
//吐司提示保存成功
ToastUtils.showLong("保存成功")
//关闭配置窗口
clickListener?.onClose()
}
// val test1 = TestBean(1,"人工接管自动录制")
// val test2 = TestBean(2,"地图采集")
// val test3 = TestBean(3,"画龙问题排查")
// val test4 = TestBean(4,"误识别问题排查")
// val test5 = TestBean(5,"lidar+planning")
// val test6 = TestBean(6,"camera+planning")
// val test7 = TestBean(7,"bus lidar+planning")
// val test8 = TestBean(8,"bus camera+planning")
// val test99 = TestBean(99,"ai data")
//
// val list = ArrayList<TestBean>()
// list.add(test1)
// list.add(test2)
// list.add(test3)
// list.add(test4)
// list.add(test5)
// list.add(test6)
// list.add(test7)
// list.add(test8)
// list.add(test99)
// list.iterator().forEach {
// if(it.id!=99){
// val radioButton = RadioButton(context)
// val lp = RadioGroup.LayoutParams(
// RadioGroup.LayoutParams.WRAP_CONTENT,
// RadioGroup.LayoutParams.WRAP_CONTENT
// )
// //设置RadioButton边距 (int left, int top, int right, int bottom)
// lp.setMargins(
// SizeUtils.dp2px(0f),
// SizeUtils.dp2px(8f),
// SizeUtils.dp2px(10f),
// SizeUtils.dp2px(8f)
// )
// //设置RadioButton背景
// radioButton.setTextColor(Color.WHITE)
//
// radioButton.buttonDrawable = resources.getDrawable(R.drawable.badcase_radio_button_style)
// //设置文字距离四周的距离
// radioButton.setPadding(
// SizeUtils.dp2px(12f),
// SizeUtils.dp2px(5f),
// SizeUtils.dp2px(10f),
// SizeUtils.dp2px(5f)
// )
// radioButton.textSize = SizeUtils.sp2px(9f).toFloat()
// radioButton.id = it.id
// radioButton.isChecked = it.id == 1
// //设置文字
// radioButton.text = it.src
// //将radioButton添加到radioGroup中
// rgRecordConfig.addView(radioButton, lp)
// rgRecordConfig.setOnCheckedChangeListener { _, checkedId ->
// mType = checkedId
// }
// }
// }
}
fun setClickListener(clickListener: ClickListener) {
this.clickListener = clickListener
}
override fun onAutopilotRecordConfig(config: MessagePad.RecordDataConfig) {
super.onAutopilotRecordConfig(config)
ThreadUtils.runOnUiThread{
config.recordTypesList.iterator().forEach {
if(it.id!=99){
val radioButton = RadioButton(context)
val lp = RadioGroup.LayoutParams(
RadioGroup.LayoutParams.WRAP_CONTENT,
RadioGroup.LayoutParams.WRAP_CONTENT
)
//设置RadioButton边距 (int left, int top, int right, int bottom)
lp.setMargins(
SizeUtils.dp2px(0f),
SizeUtils.dp2px(8f),
SizeUtils.dp2px(10f),
SizeUtils.dp2px(8f)
)
//设置RadioButton背景
radioButton.setTextColor(Color.WHITE)
radioButton.buttonDrawable = resources.getDrawable(R.drawable.badcase_radio_button_style)
//设置文字距离四周的距离
radioButton.setPadding(
SizeUtils.dp2px(12f),
SizeUtils.dp2px(5f),
SizeUtils.dp2px(10f),
SizeUtils.dp2px(5f)
)
radioButton.textSize = SizeUtils.sp2px(9f).toFloat()
radioButton.id = it.id
radioButton.isChecked = it.id == 1
//设置文字
radioButton.text = it.desc
//将radioButton添加到radioGroup中
rgRecordConfig.addView(radioButton, lp)
rgRecordConfig.setOnCheckedChangeListener { _, checkedId ->
mType = checkedId
}
}
}
}
}
override fun onAttachedToWindow() {
super.onAttachedToWindow()
CallerAutopilotRecordListenerManager.addListener(TAG, this)
//获取数据采集录制模式配置列表
CallerAutoPilotManager.getBadCaseConfig()
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
CallerAutopilotRecordListenerManager.removeListener(TAG)
}
interface ClickListener{
fun onClose()
}
}

View File

@@ -0,0 +1,187 @@
package com.zhjt.mogo_core_function_devatools.badcase.biz
import android.annotation.SuppressLint
import android.app.Activity
import android.graphics.PixelFormat
import android.os.SystemClock
import android.util.DisplayMetrics
import android.view.*
import android.widget.TextView
import com.mogo.eagle.core.function.call.autopilot.CallerAutoPilotManager
import com.mogo.eagle.core.utilcode.util.TimeUtils
import com.mogo.eagle.core.utilcode.util.TimeUtils.millis2String
import com.zhjt.mogo_core_function_devatools.R
import com.zhjt.mogo_core_function_devatools.badcase.consts.BadCaseConfig
import com.zhjt.mogo_core_function_devatools.badcase.record.RecordManager
import java.lang.reflect.Field
import java.util.*
/**
* @author XuXinChao
* @description BadCase主动录包
* @since: 2022/7/13
*/
class InitiativeBadCaseWindow constructor(activity: Activity) : View.OnTouchListener{
private var mActivity: Activity = activity
private var mWindowParams: WindowManager.LayoutParams? = null
private var mWindowManager: WindowManager? = null
private lateinit var mFloatLayout: View
private lateinit var tvInitiativeNum: TextView
private lateinit var tvInitiativeTime: TextView
private lateinit var tvInitiativeIdentity: TextView
private lateinit var viewAudioButton: View
private lateinit var tvInitiativeReport: TextView
private lateinit var tvInitiativeCancel: TextView
private var audioStatus = false
private var mInViewX = 0f
private var mInViewY = 0f
private var mDownInScreenX = 0f
private var mDownInScreenY = 0f
private var mInScreenX = 0f
private var mInScreenY = 0f
private var clickListener: ClickListener? = null
init {
initFloatWindow();
}
private val presenter by lazy {
BadCasePresenter()
}
@SuppressLint("SetTextI18n")
private fun initFloatWindow(){
mFloatLayout = LayoutInflater.from(mActivity).inflate(R.layout.view_initiative_bad_case, null) as View
mFloatLayout.setOnTouchListener(this)
tvInitiativeNum = mFloatLayout.findViewById(R.id.tvInitiativeNum)
tvInitiativeTime = mFloatLayout.findViewById(R.id.tvInitiativeTime)
tvInitiativeIdentity = mFloatLayout.findViewById(R.id.tvInitiativeIdentity)
viewAudioButton = mFloatLayout.findViewById(R.id.viewAudioButton)
tvInitiativeReport = mFloatLayout.findViewById(R.id.tvInitiativeReport)
tvInitiativeCancel = mFloatLayout.findViewById(R.id.tvInitiativeCancel)
tvInitiativeNum.text = BadCaseConfig.windowNum.toString()
BadCaseConfig.windowNum++
tvInitiativeTime.text = "时间:${millis2String(System.currentTimeMillis(),TimeUtils.getHourMinSecondFormat())}"
tvInitiativeIdentity.text = "身份:${BadCaseConfig.identity}"
viewAudioButton.setOnClickListener {
audioStatus = !audioStatus
if(audioStatus){
viewAudioButton.background = mActivity.getDrawable(R.drawable.bad_case_audio_select)
//开始录音
RecordManager.getInstance().start("audio_test")
}else{
viewAudioButton.background = mActivity.getDrawable(R.drawable.bad_case_audio_normal)
//结束录音
RecordManager.getInstance().stop()
}
}
tvInitiativeReport.setOnClickListener {
CallerAutoPilotManager.recordPackage(BadCaseConfig.type,
Random(SystemClock.elapsedRealtime()).nextInt(),
BadCaseConfig.totalDuration,
BadCaseConfig.previousDuration
)
}
tvInitiativeCancel.setOnClickListener {
BadCaseConfig.windowNum--
clickListener?.closeWindow()
}
mWindowParams = WindowManager.LayoutParams()
mWindowManager = mActivity.windowManager
mWindowParams?.let {
it.format = PixelFormat.RGBA_8888
it.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
it.gravity = Gravity.START or Gravity.TOP
it.width = 924
it.height = 668
it.alpha = 0.9f
}
}
override fun onTouch(v: View?, motionEvent: MotionEvent?): Boolean {
when (motionEvent?.action) {
MotionEvent.ACTION_DOWN -> {
// 获取相对View的坐标即以此View左上角为原点
mInViewX = motionEvent.x
mInViewY = motionEvent.y
// 获取相对屏幕的坐标,即以屏幕左上角为原点
mDownInScreenX = motionEvent.rawX
mDownInScreenY = motionEvent.rawY - getSysBarHeight(mActivity)
mInScreenX = motionEvent.rawX
mInScreenY = motionEvent.rawY - getSysBarHeight(mActivity)
}
MotionEvent.ACTION_MOVE -> {
// 更新浮动窗口位置参数
mInScreenX = motionEvent.rawX
mInScreenY = motionEvent.rawY - getSysBarHeight(mActivity)
mWindowParams!!.x = (mInScreenX - mInViewX).toInt()
mWindowParams!!.y = (mInScreenY - mInViewY).toInt()
// 手指移动的时候更新小悬浮窗的位置
mWindowManager!!.updateViewLayout(mFloatLayout, mWindowParams)
}
// MotionEvent.ACTION_UP -> // 如果手指离开屏幕时xDownInScreen和xInScreen相等且yDownInScreen和yInScreen相等则视为触发了单击事件。
// if (mDownInScreenX === mInScreenX && mDownInScreenY === mInScreenY) {
// }
}
return true
}
fun showFloatWindow() {
if (mFloatLayout.parent == null) {
val metrics = DisplayMetrics()
// 默认固定位置,靠屏幕右边缘的中间
mWindowManager!!.defaultDisplay.getMetrics(metrics)
mWindowParams!!.x = metrics.widthPixels
mWindowParams!!.y = metrics.heightPixels / 2 - getSysBarHeight(mActivity)
mWindowManager!!.addView(mFloatLayout, mWindowParams)
}
}
fun hideFloatWindow() {
if (mFloatLayout.parent != null) mWindowManager!!.removeView(mFloatLayout)
}
// 获取系统状态栏高度
private fun getSysBarHeight(activity: Activity): Int {
val c: Class<*>
val obj: Any
val field: Field
val x: Int
var sbar = 0
try {
c = Class.forName("com.android.internal.R\$dimen")
obj = c.newInstance()
field = c.getField("status_bar_height")
x = field.get(obj).toString().toInt()
sbar = activity.resources.getDimensionPixelSize(x)
} catch (e1: Exception) {
e1.printStackTrace()
}
return sbar
}
fun setClickListener(clickListener: ClickListener) {
this.clickListener = clickListener
}
interface ClickListener {
fun closeWindow()
}
}

View File

@@ -0,0 +1,13 @@
package com.zhjt.mogo_core_function_devatools.badcase.biz
/**
* @author xuxinchao
* @description
* @since: 2022/7/11
*/
data class TestBean(
var id: Int,
var src: String
) {
}

View File

@@ -0,0 +1,28 @@
package com.zhjt.mogo_core_function_devatools.badcase.consts
/**
* @author XuXinChao
* @description 录包配置参数
* @since: 2022/7/11
*/
object BadCaseConfig {
//身份安全员QA、研发产品、运营、演示默认身份是安全员
@JvmField
var identity: String = "安全员"
//录制前溯时长
@JvmField
var previousDuration: Int = 12
//录制后溯时长
@JvmField
var backDuration: Int = 8
//BadCase录制时长默认时长为20秒时长区间为大于等于5秒小于等于300秒
@JvmField
var totalDuration: Int = 20
//采集类型
@JvmField
var type: Int = 1
//BadCase采集和AI数据采集弹窗数量
@JvmField
var windowNum = 1
}

View File

@@ -4,5 +4,17 @@ import com.mogo.commons.debug.DebugConfig
internal object BadCaseHost {
fun getHost(): String = "http://dzt.zhidaozhixing.com"/*if (DebugConfig.getNetMode() == DebugConfig.NET_MODE_RELEASE) "http://dzt.zhidaozhixing.com" else "http://front.zdjs-private-test.myghost.zhidaoauto.com"*/
private const val HOST_DEV = "http://dzt.zhidaozhixing.com/"
private const val HOST_RELEASE = "https://dzt-test.zhidaozhixing.com/"
fun getHost(): String{
return when (DebugConfig.getNetMode()) {
DebugConfig.NET_MODE_DEV -> HOST_DEV
DebugConfig.NET_MODE_QA -> HOST_DEV
DebugConfig.NET_MODE_DEMO -> HOST_RELEASE
DebugConfig.NET_MODE_RELEASE -> HOST_RELEASE
else -> HOST_RELEASE
}
}
}

View File

@@ -0,0 +1,179 @@
package com.zhjt.mogo_core_function_devatools.badcase.record;
import android.media.AudioFormat;
import java.io.Serializable;
import java.util.Locale;
public class RecordConfig implements Serializable {
/**
* 录音格式 默认WAV格式
*/
private RecordFormat format = RecordFormat.WAV;
/**
* 通道数:默认双通道
*/
private int channelConfig = AudioFormat.CHANNEL_IN_STEREO;
/**
* 位宽
*/
private int encodingConfig = AudioFormat.ENCODING_PCM_16BIT;
/**
* 采样率
*/
private int sampleRate = 16000;
public RecordConfig() {
}
public RecordConfig(RecordFormat format) {
this.format = format;
}
/**
* @param format 录音文件的格式
* @param channelConfig 声道配置
* 单声道See {@link AudioFormat#CHANNEL_IN_MONO}
* 双声道See {@link AudioFormat#CHANNEL_IN_STEREO}
* @param encodingConfig 位宽配置
* 8Bit See {@link AudioFormat#ENCODING_PCM_8BIT}
* 16Bit: See {@link AudioFormat#ENCODING_PCM_16BIT},
* @param sampleRate 采样率 hz: 8000/16000/44100
*/
public RecordConfig(RecordFormat format, int channelConfig, int encodingConfig, int sampleRate) {
this.format = format;
this.channelConfig = channelConfig;
this.encodingConfig = encodingConfig;
this.sampleRate = sampleRate;
}
/**
* 获取当前录音的采样位宽 单位bit
*
* @return 采样位宽 0: error
*/
public int getEncoding() {
if (format == RecordFormat.MP3) {//mp3后期转换
return 16;
}
if (encodingConfig == AudioFormat.ENCODING_PCM_8BIT) {
return 8;
} else if (encodingConfig == AudioFormat.ENCODING_PCM_16BIT) {
return 16;
} else {
return 0;
}
}
/**
* 获取当前录音的采样位宽 单位bit
*
* @return 采样位宽 0: error
*/
public int getRealEncoding() {
if (encodingConfig == AudioFormat.ENCODING_PCM_8BIT) {
return 8;
} else if (encodingConfig == AudioFormat.ENCODING_PCM_16BIT) {
return 16;
} else {
return 0;
}
}
/**
* 当前的声道数
*
* @return 声道数: 0error
*/
public int getChannelCount() {
if (channelConfig == AudioFormat.CHANNEL_IN_MONO) {
return 1;
} else if (channelConfig == AudioFormat.CHANNEL_IN_STEREO) {
return 2;
} else {
return 0;
}
}
//get&set
public RecordFormat getFormat() {
return format;
}
public RecordConfig setFormat(RecordFormat format) {
this.format = format;
return this;
}
public int getChannelConfig() {
return channelConfig;
}
public RecordConfig setChannelConfig(int channelConfig) {
this.channelConfig = channelConfig;
return this;
}
public int getEncodingConfig() {
if (format == RecordFormat.MP3) {//mp3后期转换
return AudioFormat.ENCODING_PCM_16BIT;
}
return encodingConfig;
}
public RecordConfig setEncodingConfig(int encodingConfig) {
this.encodingConfig = encodingConfig;
return this;
}
public int getSampleRate() {
return sampleRate;
}
public RecordConfig setSampleRate(int sampleRate) {
this.sampleRate = sampleRate;
return this;
}
@Override
public String toString() {
return String.format(Locale.getDefault(), "录制格式: %s,采样率:%sHz,位宽:%s bit,声道数:%s", format, sampleRate, getEncoding(), getChannelCount());
}
public enum RecordFormat {
/**
* mp3格式
*/
MP3(".mp3"),
/**
* wav格式
*/
WAV(".wav"),
/**
* pcm格式
*/
PCM(".pcm");
private String extension;
public String getExtension() {
return extension;
}
RecordFormat(String extension) {
this.extension = extension;
}
}
}

View File

@@ -0,0 +1,423 @@
package com.zhjt.mogo_core_function_devatools.badcase.record;
import android.media.AudioRecord;
import android.media.MediaRecorder;
import android.os.Environment;
import android.util.Log;
import com.mogo.eagle.core.utilcode.mogo.logger.CallerLogger;
import com.mogo.eagle.core.utilcode.mogo.logger.Logger;
import com.mogo.eagle.core.utilcode.util.FileUtils;
import com.zhjt.mogo_core_function_devatools.badcase.record.fft.FftFactory;
import com.zhjt.mogo_core_function_devatools.badcase.record.listener.RecordListener;
import com.zhjt.mogo_core_function_devatools.badcase.record.mp3.Mp3EncodeThread;
import com.zhjt.mogo_core_function_devatools.badcase.record.utils.ByteUtils;
import com.zhjt.mogo_core_function_devatools.badcase.record.utils.WavUtils;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
public class RecordHelper {
private static final String TAG = RecordHelper.class.getSimpleName();
private volatile RecordState state = RecordState.IDLE;
private static final int RECORD_AUDIO_BUFFER_TIMES = 1;
/*
* 录音文件存放路径
*/
public static final String ROOT_PATH = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "Mogo" + File.separator + "DataCollection" + File.separator;//程序外部存储跟目录
private static final String TEMP_PATH = ROOT_PATH + "temp" + File.separator;
private RecordListener listener;
private final RecordConfig currentConfig;
private AudioRecordThread audioRecordThread;
private File resultFile = null;
private File tmpFile = null;
private List<File> files = new ArrayList<>();
private Mp3EncodeThread mp3EncodeThread;
public RecordHelper(RecordConfig config) {
this.currentConfig = config;
}
public RecordState getState() {
return state;
}
public void registerRecordListener(RecordListener listener) {
this.listener = listener;
}
public void unregisterRecordListener() {
this.listener = null;
}
public void start(String fileName) {
if (state != RecordState.IDLE && state != RecordState.STOP) {
return;
}
String path = getFilePath(fileName);
resultFile = new File(path);
String tempFilePath = getTempFilePath();
tmpFile = new File(tempFilePath);
audioRecordThread = new AudioRecordThread();
audioRecordThread.start();
}
public void stop() {
if (state == RecordState.IDLE) {
return;
}
if (state == RecordState.PAUSE) {
makeFile();
state = RecordState.IDLE;
notifyState();
stopMp3Encoded();
} else {
state = RecordState.STOP;
notifyState();
}
}
public void pause() {
if (state != RecordState.RECORDING) {
return;
}
state = RecordState.PAUSE;
notifyState();
}
public void resume() {
if (state != RecordState.PAUSE) {
return;
}
String tempFilePath = getTempFilePath();
tmpFile = new File(tempFilePath);
audioRecordThread = new AudioRecordThread();
audioRecordThread.start();
}
private void notifyState() {
if (listener == null) {
return;
}
listener.onStateChange(state);
if (state == RecordState.STOP || state == RecordState.PAUSE) {
listener.onSoundSize(0);
}
}
private void notifyFinish() {
if (listener != null) {
listener.onStateChange(RecordState.FINISH);
listener.onResult(resultFile);
}
}
private void notifyError(final String error) {
if (listener != null) {
listener.onError(error);
}
}
private FftFactory fftFactory = new FftFactory(FftFactory.Level.Original);
private void notifyData(final byte[] data) {
if (listener != null) {
listener.onData(data);
byte[] fftData = fftFactory.makeFftData(data);
if (fftData != null) {
listener.onSoundSize(getDb(fftData));
listener.onFftData(fftData);
}
}
}
private int getDb(byte[] data) {
double sum = 0;
double ave;
int length = Math.min(data.length, 128);
int offsetStart = 8;
for (int i = offsetStart; i < length; i++) {
sum += data[i];
}
ave = (sum / (length - offsetStart)) * 65536 / 128f;
int i = (int) (Math.log10(ave) * 20);
return i < 0 ? 27 : i;
}
private void initMp3EncoderThread(int bufferSize) {
try {
mp3EncodeThread = new Mp3EncodeThread(resultFile, bufferSize, currentConfig);
mp3EncodeThread.start();
} catch (Exception e) {
// Log.e(e, TAG, e.getMessage());
CallerLogger.INSTANCE.d("$M_DEVA$TAG", e.getMessage());
}
}
private class AudioRecordThread extends Thread {
private final AudioRecord audioRecord;
private int bufferSize;
AudioRecordThread() {
bufferSize = AudioRecord.getMinBufferSize(currentConfig.getSampleRate(),
currentConfig.getChannelConfig(), currentConfig.getEncodingConfig()) * RECORD_AUDIO_BUFFER_TIMES;
Logger.d(TAG, "record buffer size = %s", bufferSize);
audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, currentConfig.getSampleRate(),
currentConfig.getChannelConfig(), currentConfig.getEncodingConfig(), bufferSize);
if (currentConfig.getFormat() == RecordConfig.RecordFormat.MP3) {
if (mp3EncodeThread == null) {
initMp3EncoderThread(bufferSize);
} else {
Logger.e(TAG, "mp3EncodeThread != null, 请检查代码");
}
}
}
@Override
public void run() {
super.run();
switch (currentConfig.getFormat()) {
case MP3:
startMp3Recorder();
break;
default:
startPcmRecorder();
break;
}
}
private void startPcmRecorder() {
state = RecordState.RECORDING;
notifyState();
Logger.d(TAG, "开始录制 Pcm");
FileOutputStream fos = null;
try {
fos = new FileOutputStream(tmpFile);
audioRecord.startRecording();
byte[] byteBuffer = new byte[bufferSize];
while (state == RecordState.RECORDING) {
int end = audioRecord.read(byteBuffer, 0, byteBuffer.length);
notifyData(byteBuffer);
fos.write(byteBuffer, 0, end);
fos.flush();
}
audioRecord.stop();
files.add(tmpFile);
if (state == RecordState.STOP) {
makeFile();
} else {
Logger.i(TAG, "暂停!");
}
} catch (Exception e) {
notifyError("录音失败");
} finally {
try {
if (fos != null) {
fos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
if (state != RecordState.PAUSE) {
state = RecordState.IDLE;
notifyState();
Logger.d(TAG, "录音结束");
}
}
private void startMp3Recorder() {
state = RecordState.RECORDING;
notifyState();
try {
audioRecord.startRecording();
short[] byteBuffer = new short[bufferSize];
while (state == RecordState.RECORDING) {
int end = audioRecord.read(byteBuffer, 0, byteBuffer.length);
if (mp3EncodeThread != null) {
mp3EncodeThread.addChangeBuffer(new Mp3EncodeThread.ChangeBuffer(byteBuffer, end));
}
notifyData(ByteUtils.toBytes(byteBuffer));
}
audioRecord.stop();
} catch (Exception e) {
notifyError("录音失败");
}
if (state != RecordState.PAUSE) {
state = RecordState.IDLE;
notifyState();
stopMp3Encoded();
} else {
Logger.d(TAG, "暂停");
}
}
}
private void stopMp3Encoded() {
if (mp3EncodeThread != null) {
mp3EncodeThread.stopSafe(new Mp3EncodeThread.EncordFinishListener() {
@Override
public void onFinish() {
notifyFinish();
mp3EncodeThread = null;
}
});
} else {
Logger.e(TAG, "mp3EncodeThread is null, 代码业务流程有误,请检查!! ");
}
}
private void makeFile() {
switch (currentConfig.getFormat()) {
case MP3:
return;
case WAV:
mergePcmFile();
makeWav();
break;
case PCM:
mergePcmFile();
break;
default:
break;
}
notifyFinish();
Logger.i(TAG, "录音完成! path: %s 大小:%s", resultFile.getAbsoluteFile(), resultFile.length());
}
/**
* 添加Wav头文件
*/
private void makeWav() {
if (!FileUtils.isFile(resultFile) || resultFile.length() == 0) {
return;
}
byte[] header = WavUtils.generateWavFileHeader((int) resultFile.length(), currentConfig.getSampleRate(), currentConfig.getChannelCount(), currentConfig.getEncoding());
WavUtils.writeHeader(resultFile, header);
}
/**
* 合并文件
*/
private void mergePcmFile() {
boolean mergeSuccess = mergePcmFiles(resultFile, files);
if (!mergeSuccess) {
notifyError("合并失败");
}
}
/**
* 合并Pcm文件
*
* @param recordFile 输出文件
* @param files 多个文件源
* @return 是否成功
*/
private boolean mergePcmFiles(File recordFile, List<File> files) {
if (recordFile == null || files == null || files.size() <= 0) {
return false;
}
FileOutputStream fos = null;
BufferedOutputStream outputStream = null;
byte[] buffer = new byte[1024];
try {
fos = new FileOutputStream(recordFile);
outputStream = new BufferedOutputStream(fos);
for (int i = 0; i < files.size(); i++) {
BufferedInputStream inputStream = new BufferedInputStream(new FileInputStream(files.get(i)));
int readCount;
while ((readCount = inputStream.read(buffer)) > 0) {
outputStream.write(buffer, 0, readCount);
}
inputStream.close();
}
} catch (Exception e) {
Log.e(TAG, e.getMessage());
return false;
} finally {
try {
if (outputStream != null) {
outputStream.close();
}
if (fos != null) {
fos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
for (int i = 0; i < files.size(); i++) {
files.get(i).delete();
}
files.clear();
return true;
}
private String getFilePath(String fileName) {
if (!FileUtils.createOrExistsDir(ROOT_PATH)) {
Logger.w(TAG, "文件夹创建失败:%s", ROOT_PATH);
return null;
}
String format = currentConfig.getFormat().getExtension();
String filePath = String.format(Locale.getDefault(), "%s%s%s", ROOT_PATH, fileName, format);
return filePath;
}
private String getTempFilePath() {
if (!FileUtils.createOrExistsDir(TEMP_PATH)) {
Logger.e(TAG, "文件夹创建失败:%s", TEMP_PATH);
}
String fileName = String.format(Locale.getDefault(), "tmp_%s", FileUtils.getNowString(new SimpleDateFormat("yyyyMMddHHmmssSSS", Locale.SIMPLIFIED_CHINESE)));
return String.format(Locale.getDefault(), "%s%s.pcm", TEMP_PATH, fileName);
}
/**
* 表示当前状态
*/
public enum RecordState {
/**
* 空闲状态
*/
IDLE,
/**
* 录音中
*/
RECORDING,
/**
* 暂停中
*/
PAUSE,
/**
* 正在停止
*/
STOP,
/**
* 录音流程结束(转换结束)
*/
FINISH
}
}

View File

@@ -0,0 +1,106 @@
package com.zhjt.mogo_core_function_devatools.badcase.record;
import android.annotation.SuppressLint;
import com.zhjt.mogo_core_function_devatools.badcase.record.listener.RecordListener;
public class RecordManager {
private static final String TAG = RecordManager.class.getSimpleName();
@SuppressLint("StaticFieldLeak")
private volatile static RecordManager instance;
private final RecordHelper recordHelper;
/**
* 录音配置
*/
private final RecordConfig currentConfig = new RecordConfig();
private RecordManager() {
recordHelper = new RecordHelper(currentConfig);
}
public static RecordManager getInstance() {
if (instance == null) {
synchronized (RecordManager.class) {
if (instance == null) {
instance = new RecordManager();
}
}
}
return instance;
}
// /**
// * @param showLog 是否开启日志
// */
// public void setISDebug(boolean showLog) {
// Logger.IsDebug = showLog;
// }
public void start(String fileName) {
recordHelper.start(fileName);
}
public void stop() {
recordHelper.stop();
}
public void resume() {
recordHelper.resume();
}
public void pause() {
recordHelper.pause();
}
public RecordConfig getRecordConfig() {
return currentConfig;
}
/**
* 录音数据监听回调
*/
public void registerRecordListener(RecordListener listener) {
recordHelper.registerRecordListener(listener);
}
public void unregisterRecordListener() {
recordHelper.unregisterRecordListener();
}
public boolean changeFormat(RecordConfig.RecordFormat recordFormat) {
if (getState() == RecordHelper.RecordState.IDLE) {
currentConfig.setFormat(recordFormat);
return true;
}
return false;
}
public boolean changeSampleRate(int sampleRate) {
if (getState() == RecordHelper.RecordState.IDLE) {
currentConfig.setSampleRate(sampleRate);
return true;
}
return false;
}
public boolean changeEncodingConfig(int encodingConfig) {
if (getState() == RecordHelper.RecordState.IDLE) {
currentConfig.setEncodingConfig(encodingConfig);
return true;
}
return false;
}
/**
* 获取当前的录音状态
*
* @return 状态
*/
public RecordHelper.RecordState getState() {
return recordHelper.getState();
}
}

View File

@@ -0,0 +1,139 @@
package com.zhjt.mogo_core_function_devatools.badcase.record.fft;
import java.util.Objects;
/**
* 复数
*
* @author test
*/
public class Complex {
/**
* 实数部分
*/
private final double real;
/**
* 虚数部分 imaginary
*/
private final double im;
public Complex(double real, double imag) {
this.real = real;
im = imag;
}
@Override
public String toString() {
return String.format("hypot: %s, complex: %s+%si", hypot(), real, im);
}
public double hypot() {
return Math.hypot(real, im);
}
public double phase() {
return Math.atan2(im, real);
}
/**
* 复数求和
*/
public Complex plus(Complex b) {
double real = this.real + b.real;
double imag = this.im + b.im;
return new Complex(real, imag);
}
// return a new Complex object whose value is (this - b)
public Complex minus(Complex b) {
double real = this.real - b.real;
double imag = this.im - b.im;
return new Complex(real, imag);
}
// return a new Complex object whose value is (this * b)
public Complex times(Complex b) {
Complex a = this;
double real = a.real * b.real - a.im * b.im;
double imag = a.real * b.im + a.im * b.real;
return new Complex(real, imag);
}
// return a new object whose value is (this * alpha)
public Complex scale(double alpha) {
return new Complex(alpha * real, alpha * im);
}
// return a new Complex object whose value is the conjugate of this
public Complex conjugate() {
return new Complex(real, -im);
}
// return a new Complex object whose value is the reciprocal of this
public Complex reciprocal() {
double scale = real * real + im * im;
return new Complex(real / scale, -im / scale);
}
// return the real or imaginary part
public double re() {
return real;
}
public double im() {
return im;
}
// return a / b
public Complex divides(Complex b) {
Complex a = this;
return a.times(b.reciprocal());
}
// return a new Complex object whose value is the complex exponential of this
public Complex exp() {
return new Complex(Math.exp(real) * Math.cos(im), Math.exp(real) * Math.sin(im));
}
// return a new Complex object whose value is the complex sine of this
public Complex sin() {
return new Complex(Math.sin(real) * Math.cosh(im), Math.cos(real) * Math.sinh(im));
}
// return a new Complex object whose value is the complex cosine of this
public Complex cos() {
return new Complex(Math.cos(real) * Math.cosh(im), -Math.sin(real) * Math.sinh(im));
}
// return a new Complex object whose value is the complex tangent of this
public Complex tan() {
return sin().divides(cos());
}
// a static version of plus
public static Complex plus(Complex a, Complex b) {
double real = a.real + b.real;
double imag = a.im + b.im;
Complex sum = new Complex(real, imag);
return sum;
}
// See Section 3.3.
@Override
public boolean equals(Object x) {
if (x == null) return false;
if (this.getClass() != x.getClass()) return false;
Complex that = (Complex) x;
return (this.real == that.real) && (this.im == that.im);
}
// See Section 3.3.
@Override
public int hashCode() {
return Objects.hash(real, im);
}
}

View File

@@ -0,0 +1,165 @@
package com.zhjt.mogo_core_function_devatools.badcase.record.fft;
public class FFT {
// compute the FFT of x[], assuming its length is a power of 2
public static Complex[] fft(Complex[] x) {
int n = x.length;
// base case
if (n == 1) return new Complex[]{x[0]};
// radix 2 Cooley-Tukey FFT
if (n % 2 != 0) {
throw new IllegalArgumentException("n is not a power of 2");
}
// fft of even terms
Complex[] even = new Complex[n / 2];
for (int k = 0; k < n / 2; k++) {
even[k] = x[2 * k];
}
Complex[] q = fft(even);
// fft of odd terms
for (int k = 0; k < n / 2; k++) {
even[k] = x[2 * k + 1];
}
Complex[] r = fft(even);
// combine
Complex[] y = new Complex[n];
for (int k = 0; k < n / 2; k++) {
double kth = -2 * k * Math.PI / n;
Complex wk = new Complex(Math.cos(kth), Math.sin(kth));
y[k] = q[k].plus(wk.times(r[k]));
y[k + n / 2] = q[k].minus(wk.times(r[k]));
}
return y;
}
public static double[] fft(double[] x, int sc) {
int len = x.length;
if (len == 1) {
return x;
}
Complex[] cs = new Complex[len];
double[] ds = new double[len / 2];
for (int i = 0; i < len; i++) {
cs[i] = new Complex(x[i], 0);
}
Complex[] ffts = fft(cs);
for (int i = 0; i < ds.length; i++) {
ds[i] = Math.sqrt(Math.pow(ffts[i].re(), 2) + Math.pow(ffts[i].im(), 2)) / x.length;
}
return ds;
}
// compute the inverse FFT of x[], assuming its length is a power of 2
public static Complex[] ifft(Complex[] x) {
int n = x.length;
Complex[] y = new Complex[n];
// take conjugate
for (int i = 0; i < n; i++) {
y[i] = x[i].conjugate();
}
// compute forward FFT
y = fft(y);
// take conjugate again
for (int i = 0; i < n; i++) {
y[i] = y[i].conjugate();
}
// divide by n
for (int i = 0; i < n; i++) {
y[i] = y[i].scale(1.0 / n);
}
return y;
}
// compute the circular convolution of x and y
public static Complex[] cconvolve(Complex[] x, Complex[] y) {
// should probably pad x and y with 0s so that they have same length
// and are powers of 2
if (x.length != y.length) {
throw new IllegalArgumentException("Dimensions don't agree");
}
int n = x.length;
// compute FFT of each sequence
Complex[] a = fft(x);
Complex[] b = fft(y);
// point-wise multiply
Complex[] c = new Complex[n];
for (int i = 0; i < n; i++) {
c[i] = a[i].times(b[i]);
}
// compute inverse FFT
return ifft(c);
}
// compute the linear convolution of x and y
public static Complex[] convolve(Complex[] x, Complex[] y) {
Complex ZERO = new Complex(0, 0);
Complex[] a = new Complex[2 * x.length];
for (int i = 0; i < x.length; i++) a[i] = x[i];
for (int i = x.length; i < 2 * x.length; i++) a[i] = ZERO;
Complex[] b = new Complex[2 * y.length];
for (int i = 0; i < y.length; i++) b[i] = y[i];
for (int i = y.length; i < 2 * y.length; i++) b[i] = ZERO;
return cconvolve(a, b);
}
// display an array of Complex numbers to standard output
public static void show(Complex[] x, String title) {
System.out.println(title);
System.out.println("-------------------");
for (int i = 0; i < SIZE; i++) {
System.out.println(x[i]);
}
System.out.println();
}
private static final int SIZE = 16384 / 4;
public static double fun(int x) {
return Math.sin(15f * x);//f= 3.142
}
public static double getY(double[] d) {
double y = 0;
int x = 0;
for (int i = 0; i < d.length; i++) {
if (d[i] > y) {
y = d[i];
x = i;
}
}
x++;
log(String.format("x %s y: %s", x, y));
log(String.format("频率: %sHz", (float) x / SIZE));
log(String.format("频率2 %sHz", (float) (SIZE - x) / SIZE));
log(String.format("振幅: %s", y));
return y;
}
public static void log(String s) {
System.out.println(s);
}
}

View File

@@ -0,0 +1,98 @@
package com.zhjt.mogo_core_function_devatools.badcase.record.fft;
import com.zhjt.mogo_core_function_devatools.badcase.record.utils.ByteUtils;
/**
* FFT 数据处理工厂
*/
public class FftFactory {
private static final String TAG = FftFactory.class.getSimpleName();
private Level level = Level.Original;
public FftFactory(Level level) {
// this.level = level;
}
public byte[] makeFftData(byte[] pcmData) {
// Logger.d(TAG, "pcmData length: %s", pcmData.length);
if (pcmData.length < 1024) {
return null;
}
double[] doubles = ByteUtils.toHardDouble(ByteUtils.toShorts(pcmData));
double[] fft = FFT.fft(doubles, 0);
switch (level) {
case Original:
return ByteUtils.toSoftBytes(fft);
case Maximal:
// return doFftMaximal(fft);
default:
return ByteUtils.toHardBytes(fft);
}
}
private byte[] doFftMaximal(double[] fft) {
byte[] bytes = ByteUtils.toSoftBytes(fft);
byte[] result = new byte[bytes.length];
for (int i = 0; i < bytes.length; i++) {
if (isSimpleData(bytes, i)) {
result[i] = bytes[i];
} else {
result[Math.max(i - 1, 0)] = (byte) (bytes[i] / 2);
result[Math.min(i + 1, result.length - 1)] = (byte) (bytes[i] / 2);
}
}
return result;
}
private boolean isSimpleData(byte[] data, int i) {
int start = Math.max(0, i - 5);
int end = Math.min(data.length, i + 5);
byte max = 0, min = 127;
for (int j = start; j < end; j++) {
if (data[j] > max) {
max = data[j];
}
if (data[j] < min) {
min = data[j];
}
}
return data[i] == min || data[i] == max;
}
/**
* FFT 处理等级
*/
public enum Level {
/**
* 原始数据,不做任何优化
*/
Original,
/**
* 对音乐进行优化
*/
Music,
/**
* 对人声进行优化
*/
People,
/**
* 极限优化
*/
Maximal
}
}

View File

@@ -0,0 +1,50 @@
package com.zhjt.mogo_core_function_devatools.badcase.record.listener;
import com.zhjt.mogo_core_function_devatools.badcase.record.RecordHelper;
import java.io.File;
public interface RecordListener {
/**
* 实时返回音量大小
*
* @param soundSize 当前音量大小
*/
void onSoundSize(int soundSize);
/**
* @param data 录音可视化数据即傅里叶转换后的数据fftData
*/
void onFftData(byte[] data);
/**
* 当前的录音状态发生变化
*
* @param data 当前音频数据
*/
void onData(byte[] data);
/**
* 录音完成回调
* 录音文件
*
* @param result 录音文件
*/
void onResult(File result);
/**
* 当前的录音状态发生变化
*
* @param state 当前状态
*/
void onStateChange(RecordHelper.RecordState state);
/**
* 录音错误
*
* @param error 错误
*/
void onError(String error);
}

View File

@@ -0,0 +1,148 @@
package com.zhjt.mogo_core_function_devatools.badcase.record.mp3;
import com.zhjt.mogo_core_function_devatools.badcase.record.RecordConfig;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
public class Mp3EncodeThread extends Thread {
private static final String TAG = Mp3EncodeThread.class.getSimpleName();
private List<ChangeBuffer> cacheBufferList = Collections.synchronizedList(new LinkedList<ChangeBuffer>());
private File file;
private FileOutputStream os;
private byte[] mp3Buffer;
private EncordFinishListener encordFinishListener;
/**
* 是否已停止录音
*/
private volatile boolean isOver = false;
/**
* 是否继续轮询数据队列
*/
private volatile boolean start = true;
public Mp3EncodeThread(File file, int bufferSize, RecordConfig currentConfig) {
this.file = file;
mp3Buffer = new byte[(int) (7200 + (bufferSize * 2 * 1.25))];
int sampleRate = currentConfig.getSampleRate();
Mp3Encoder.init(sampleRate, currentConfig.getChannelCount(), sampleRate, currentConfig.getRealEncoding());
}
@Override
public void run() {
try {
this.os = new FileOutputStream(file);
} catch (FileNotFoundException e) {
return;
}
while (start) {
ChangeBuffer next = next();
lameData(next);
}
}
public void addChangeBuffer(ChangeBuffer changeBuffer) {
if (changeBuffer != null) {
cacheBufferList.add(changeBuffer);
synchronized (this) {
notify();
}
}
}
public void stopSafe(EncordFinishListener encordFinishListener) {
this.encordFinishListener = encordFinishListener;
isOver = true;
synchronized (this) {
notify();
}
}
private ChangeBuffer next() {
for (; ; ) {
if (cacheBufferList == null || cacheBufferList.size() == 0) {
try {
if (isOver) {
finish();
}
synchronized (this) {
wait();
}
} catch (Exception e) {
e.printStackTrace();
}
} else {
return cacheBufferList.remove(0);
}
}
}
private void lameData(ChangeBuffer changeBuffer) {
if (changeBuffer == null) {
return;
}
short[] buffer = changeBuffer.getData();
int readSize = changeBuffer.getReadSize();
if (readSize > 0) {
int encodedSize = Mp3Encoder.encode(buffer, buffer, readSize, mp3Buffer);
// if (encodedSize < 0) {
// Logger.e(TAG, "Lame encoded size: " + encodedSize);
// }
try {
os.write(mp3Buffer, 0, encodedSize);
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void finish() {
start = false;
final int flushResult = Mp3Encoder.flush(mp3Buffer);
if (flushResult > 0) {
try {
os.write(mp3Buffer, 0, flushResult);
os.close();
} catch (final IOException e) {
e.printStackTrace();
}
}
if (encordFinishListener != null) {
encordFinishListener.onFinish();
}
}
public static class ChangeBuffer {
private short[] rawData;
private int readSize;
public ChangeBuffer(short[] rawData, int readSize) {
this.rawData = rawData.clone();
this.readSize = readSize;
}
short[] getData() {
return rawData;
}
int getReadSize() {
return readSize;
}
}
public interface EncordFinishListener {
/**
* 格式转换完毕
*/
void onFinish();
}
}

View File

@@ -0,0 +1,28 @@
package com.zhjt.mogo_core_function_devatools.badcase.record.mp3;
public class Mp3Encoder {
static {
System.loadLibrary("lamemp3");
}
/**
* 获取lame版本号
*
* @return
*/
public static native String getVersion();
public native static void close();
public native static int encode(short[] buffer_l, short[] buffer_r, int samples, byte[] mp3buf);
public native static int flush(byte[] mp3buf);
public native static void init(int inSampleRate, int outChannel, int outSampleRate, int outBitrate, int quality);
public static void init(int inSampleRate, int outChannel, int outSampleRate, int outBitrate) {
init(inSampleRate, outChannel, outSampleRate, outBitrate, 7);
}
}

View File

@@ -0,0 +1,45 @@
package com.zhjt.mogo_core_function_devatools.badcase.record.mp3;
import android.media.MediaExtractor;
import android.media.MediaFormat;
import android.util.Log;
import com.mogo.eagle.core.utilcode.util.FileUtils;
import com.zhjt.mogo_core_function_devatools.badcase.record.RecordConfig;
import java.io.IOException;
public class Mp3Utils {
private static final String TAG = Mp3Utils.class.getSimpleName();
/**
* 获取mp3音频的总时长 单位ms
*
* @param mp3FilePath MP3文件路径
* @return 时长
*/
public static long getDuration(String mp3FilePath) {
if (!FileUtils.isFileExists(mp3FilePath)) {
return 0;
}
if (!mp3FilePath.endsWith(RecordConfig.RecordFormat.MP3.getExtension())) {
return 0;
}
MediaExtractor mex = null;
try {
mex = new MediaExtractor();
mex.setDataSource(mp3FilePath);
MediaFormat mf = mex.getTrackFormat(0);
long duration = mf.getLong(MediaFormat.KEY_DURATION) / 1000L;
return duration;
} catch (IOException e) {
Log.e(TAG, e.getMessage());
} finally {
if (mex != null) {
mex.release();
}
}
return 0;
}
}

View File

@@ -0,0 +1,152 @@
package com.zhjt.mogo_core_function_devatools.badcase.record.utils;
import java.nio.ByteBuffer;
import java.util.Arrays;
public class ByteUtils {
public static double[] toHardDouble(short[] shorts) {
int length = 512;
double[] ds = new double[length];
for (int i = 0; i < length; i++) {
ds[i] = shorts[i];
}
return ds;
}
public static byte[] toHardBytes(double[] doubles) {
byte[] bytes = new byte[doubles.length];
for (int i = 0; i < doubles.length; i++) {
double item = doubles[i];
bytes[i] = (byte) (item > 127 ? 127 : item);
}
return bytes;
}
public static byte[] toSoftBytes(double[] doubles) {
double max = getMax(doubles);
double sc = 1f;
if (max > 127) {
sc = (max / 128f);
}
byte[] bytes = new byte[doubles.length];
for (int i = 0; i < doubles.length; i++) {
double item = doubles[i] / sc;
bytes[i] = (byte) (item > 127 ? 127 : item);
}
return bytes;
}
public static double getMax(double[] data) {
double max = 0;
for (double datum : data) {
if (datum > max) {
max = datum;
}
}
return max;
}
/**
* short[] 转 byte[]
*/
public static byte[] toBytes(short[] src) {
int count = src.length;
byte[] dest = new byte[count << 1];
for (int i = 0; i < count; i++) {
dest[i * 2] = (byte) (src[i]);
dest[i * 2 + 1] = (byte) (src[i] >> 8);
}
return dest;
}
/**
* short[] 转 byte[]
*/
public static byte[] toBytes(short src) {
byte[] dest = new byte[2];
dest[0] = (byte) (src);
dest[1] = (byte) (src >> 8);
return dest;
}
/**
* int 转 byte[]
*/
public static byte[] toBytes(int i) {
byte[] b = new byte[4];
b[0] = (byte) (i & 0xff);
b[1] = (byte) ((i >> 8) & 0xff);
b[2] = (byte) ((i >> 16) & 0xff);
b[3] = (byte) ((i >> 24) & 0xff);
return b;
}
/**
* String 转 byte[]
*/
public static byte[] toBytes(String str) {
return str.getBytes();
}
/**
* long类型转成byte数组
*/
public static byte[] toBytes(long number) {
ByteBuffer buffer = ByteBuffer.allocate(8);
buffer.putLong(0, number);
return buffer.array();
}
public static int toInt(byte[] src, int offset) {
return ((src[offset] & 0xFF)
| ((src[offset + 1] & 0xFF) << 8)
| ((src[offset + 2] & 0xFF) << 16)
| ((src[offset + 3] & 0xFF) << 24));
}
public static int toInt(byte[] src) {
return toInt(src, 0);
}
/**
* 字节数组到long的转换.
*/
public static long toLong(byte[] b) {
ByteBuffer buffer = ByteBuffer.allocate(8);
buffer.put(b, 0, b.length);
return buffer.getLong();
}
/**
* byte[] 转 short[]
* short 2字节
*/
public static short[] toShorts(byte[] src) {
int count = src.length >> 1;
short[] dest = new short[count];
for (int i = 0; i < count; i++) {
dest[i] = (short) ((src[i * 2] & 0xff) | ((src[2 * i + 1] & 0xff) << 8));
}
return dest;
}
public static byte[] merger(byte[] bt1, byte[] bt2) {
byte[] bt3 = new byte[bt1.length + bt2.length];
System.arraycopy(bt1, 0, bt3, 0, bt1.length);
System.arraycopy(bt2, 0, bt3, bt1.length, bt2.length);
return bt3;
}
public static String toString(byte[] b) {
return Arrays.toString(b);
}
}

View File

@@ -0,0 +1,257 @@
package com.zhjt.mogo_core_function_devatools.badcase.record.utils;
import android.util.Log;
import com.mogo.eagle.core.utilcode.util.FileUtils;
import com.zhjt.mogo_core_function_devatools.badcase.record.RecordConfig;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
/**
* @author zhaolewei on 2018/7/3.
* pcm 转 wav 工具类
* http://soundfile.sapp.org/doc/WaveFormat/
*/
public class WavUtils {
private static final String TAG = WavUtils.class.getSimpleName();
/**
* 生成wav格式的Header
* wave是RIFF文件结构每一部分为一个chunk其中有RIFF WAVE chunk
* FMT ChunkFact chunk可选,Data chunk
*
* @param totalAudioLen 不包括header的音频数据总长度
* @param sampleRate 采样率,也就是录制时使用的频率
* @param channels audioRecord的频道数量
* @param sampleBits 位宽
*/
public static byte[] generateWavFileHeader(int totalAudioLen, int sampleRate, int channels, int sampleBits) {
WavHeader wavHeader = new WavHeader(totalAudioLen, sampleRate, (short) channels, (short) sampleBits);
return wavHeader.getHeader();
}
/**
* 将header写入到pcm文件中 不修改文件名
*
* @param file 写入的pcm文件
* @param header wav头数据
*/
public static void writeHeader(File file, byte[] header) {
if (!FileUtils.isFile(file)) {
return;
}
RandomAccessFile wavRaf = null;
try {
wavRaf = new RandomAccessFile(file, "rw");
wavRaf.seek(0);
wavRaf.write(header);
wavRaf.close();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (wavRaf != null) {
wavRaf.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* Pcm 转 WAV 文件
*
* @param pcmFile File
* @param header wavHeader
* @throws IOException Exception
*/
public static void pcmToWav(File pcmFile, byte[] header) throws IOException {
if (!FileUtils.isFile(pcmFile)) {
return;
}
String pcmPath = pcmFile.getAbsolutePath();
String wavPath = pcmPath.substring(0, pcmPath.length() - 4) + ".wav";
writeHeader(new File(wavPath), header);
}
/**
* 获取WAV文件的头信息
*
* @param wavFilePath 文件地址
* @return header
*/
private static byte[] getHeader(String wavFilePath) {
if (!new File(wavFilePath).isFile()) {
return null;
}
byte[] buffer = null;
File file = new File(wavFilePath);
final int size = 44;
FileInputStream fis = null;
ByteArrayOutputStream bos = null;
try {
fis = new FileInputStream(file);
bos = new ByteArrayOutputStream(size);
byte[] b = new byte[size];
int len;
if ((len = fis.read(b)) != size) {
return null;
}
bos.write(b, 0, len);
buffer = bos.toByteArray();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (fis != null) {
fis.close();
fis = null;
}
if (bos != null) {
bos.close();
bos = null;
}
} catch (IOException e) {
e.printStackTrace();
}
}
return buffer;
}
/**
* 获取wav音频时长 ms
*
* @param filePath wav文件路径
* @return 时长 -1: 获取失败
*/
public static long getWavDuration(String filePath) {
if (!filePath.endsWith(RecordConfig.RecordFormat.WAV.getExtension())) {
return -1;
}
byte[] header = getHeader(filePath);
return getWavDuration(header);
}
/**
* 获取wav音频时长 ms
*
* @param header wav音频文件字节数组
* @return 时长 -1: 获取失败
*/
public static long getWavDuration(byte[] header) {
if (header == null || header.length < 44) {
Log.e(TAG, "header size有误");
return -1;
}
int byteRate = ByteUtils.toInt(header, 28);//28-31
int waveSize = ByteUtils.toInt(header, 40);//40-43
return waveSize * 1000L / byteRate;
}
public static String headerToString(byte[] header) {
if (header == null || header.length < 44) {
return null;
}
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < 4; i++) {
stringBuilder.append((char) header[i]);
}
stringBuilder.append(",");
stringBuilder.append(ByteUtils.toInt(header, 4));
stringBuilder.append(",");
for (int i = 8; i < 16; i++) {
stringBuilder.append((char) header[i]);
}
stringBuilder.append(",");
for (int i = 16; i < 24; i++) {
stringBuilder.append(header[i]);
}
stringBuilder.append(",");
stringBuilder.append(ByteUtils.toInt(header, 24));
stringBuilder.append(",");
stringBuilder.append(ByteUtils.toInt(header, 28));
stringBuilder.append(",");
for (int i = 32; i < 36; i++) {
stringBuilder.append(header[i]);
}
stringBuilder.append(",");
for (int i = 36; i < 40; i++) {
stringBuilder.append((char) header[i]);
}
stringBuilder.append(",");
stringBuilder.append(ByteUtils.toInt(header, 40));
return stringBuilder.toString();
}
public static class WavHeader {
/**
* RIFF数据块
*/
final String riffChunkId = "RIFF";
int riffChunkSize;
final String riffType = "WAVE";
/**
* FORMAT 数据块
*/
final String formatChunkId = "fmt ";
final int formatChunkSize = 16;
final short audioFormat = 1;
short channels;
int sampleRate;
int byteRate;
short blockAlign;
short sampleBits;
/**
* FORMAT 数据块
*/
final String dataChunkId = "data";
int dataChunkSize;
WavHeader(int totalAudioLen, int sampleRate, short channels, short sampleBits) {
this.riffChunkSize = totalAudioLen;
this.channels = channels;
this.sampleRate = sampleRate;
this.byteRate = sampleRate * sampleBits / 8 * channels;
this.blockAlign = (short) (channels * sampleBits / 8);
this.sampleBits = sampleBits;
this.dataChunkSize = totalAudioLen - 44;
}
public byte[] getHeader() {
byte[] result;
result = ByteUtils.merger(ByteUtils.toBytes(riffChunkId), ByteUtils.toBytes(riffChunkSize));
result = ByteUtils.merger(result, ByteUtils.toBytes(riffType));
result = ByteUtils.merger(result, ByteUtils.toBytes(formatChunkId));
result = ByteUtils.merger(result, ByteUtils.toBytes(formatChunkSize));
result = ByteUtils.merger(result, ByteUtils.toBytes(audioFormat));
result = ByteUtils.merger(result, ByteUtils.toBytes(channels));
result = ByteUtils.merger(result, ByteUtils.toBytes(sampleRate));
result = ByteUtils.merger(result, ByteUtils.toBytes(byteRate));
result = ByteUtils.merger(result, ByteUtils.toBytes(blockAlign));
result = ByteUtils.merger(result, ByteUtils.toBytes(sampleBits));
result = ByteUtils.merger(result, ByteUtils.toBytes(dataChunkId));
result = ByteUtils.merger(result, ByteUtils.toBytes(dataChunkSize));
return result;
}
}
}

View File

@@ -8,7 +8,7 @@ import retrofit2.http.*
internal interface BadCaseApi {
@FormUrlEncoded
@POST("/yycp-vehicle-management-service/tool/badcase/add")
@POST("/yycp-vehicle-management-service/tool/badcase/add/v2")
suspend fun post(@FieldMap map: Map<String, String>): Response<UploadResult>
@GET("/yycp-vehicle-management-service/tool/badcase/reasons")

View File

@@ -0,0 +1,97 @@
package com.zhjt.mogo_core_function_devatools.env
import android.content.Context.MODE_PRIVATE
import android.os.Process
import com.mogo.commons.constants.*
import com.mogo.commons.debug.*
import com.mogo.eagle.core.function.call.map.*
import com.mogo.eagle.core.utilcode.mogo.storage.*
import com.mogo.eagle.core.utilcode.util.*
object EnvChangeManager {
private val sp = Utils.getApp().getSharedPreferences("env_change", MODE_PRIVATE)
private fun updateCityCode(cityCode: String?) {
sp.edit().putString("city_code", cityCode).commit()
}
private fun updateNetMode(netMode: Int) {
sp.edit().putInt("net_mode", netMode).commit()
}
private fun getConfig() : Pair<String, Int>? {
val cityCode = sp.getString("city_code", null)
val severType = sp.getInt("net_mode", -1)
if (cityCode == null || severType == -1) {
return null
}
return Pair(cityCode, severType)
}
fun getCityName(): String {
val cache = getConfig()
return if (cache == null) {
val cityCode = CallerMapLocationListenerManager.getCurrentLocation()?.cityCode ?: SharedPrefsMgr.getInstance(Utils.getApp()).getString(SharedPrefsConstants.LOCATION_CITY_CODE) ?: "010"
updateCityCode(cityCode)
when(cityCode) {
"010" -> "北京"
"0734" -> "衡阳"
else -> "未知"
}
} else {
when(cache.first) {
"010" -> "北京"
"0734" -> "衡阳"
else -> "未知"
}
}
}
fun getNetMode(): String {
val cache = getConfig()
if (cache == null) {
val mode = DebugConfig.getNetMode()
updateNetMode(mode)
return when(mode) {
DebugConfig.NET_MODE_RELEASE -> "生产"
DebugConfig.NET_MODE_QA -> "测试"
DebugConfig.NET_MODE_DEMO -> "演示"
else -> "未知"
}
} else {
return when(cache.second) {
DebugConfig.NET_MODE_RELEASE -> "生产"
DebugConfig.NET_MODE_QA -> "测试"
DebugConfig.NET_MODE_DEMO -> "演示"
else -> "未知"
}
}
}
fun changeTo(cityCode: String, netMode: Int) {
updateCityCode(cityCode)
updateNetMode(netMode)
restartApp()
}
fun reset() {
updateCityCode(null)
updateNetMode(-1)
restartApp()
}
private fun restartApp() {
Utils.getApp().startActivity(Utils.getApp().packageManager.getLaunchIntentForPackage(Utils.getApp().packageName))
Process.killProcess(Process.myPid())
}
fun getEnvConfig(): EnvConfig? = getConfig()?.let {
EnvConfig(it.first, it.second,
if (it.first == "010") 116.397446 else 112.582654,
if (it.first == "010") 39.909004 else 26.816478)
}
data class EnvConfig(val cityCode: String, val netMode: Int, val lat: Double, val lon: Double)
}

View File

@@ -7,7 +7,11 @@ import androidx.lifecycle.*
import androidx.lifecycle.Lifecycle.Event
import androidx.lifecycle.Lifecycle.Event.ON_CREATE
import androidx.lifecycle.Lifecycle.Event.ON_DESTROY
import com.mogo.eagle.core.function.api.autopilot.IMoGoAutopilotStatusListener
import com.mogo.eagle.core.function.call.autopilot.*
import com.mogo.eagle.core.utilcode.kotlin.*
import com.mogo.eagle.core.utilcode.util.AppStateManager
import com.mogo.eagle.core.utilcode.util.IAppStateListener
import com.zhjt.mogo_core_function_devatools.ext.*
import com.zhjt.mogo_core_function_devatools.status.entity.CanStatus
import com.zhjt.mogo_core_function_devatools.status.entity.GpsStatus
@@ -27,17 +31,57 @@ import com.zhjt.mogo_core_function_devatools.status.flow.trace.TracingImpl
import com.zhjt.mogo_core_function_devatools.status.ui.*
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
import mogo_msg.MogoReportMsg
object StatusManager {
private const val TAG = "StatusManager"
private lateinit var model: StatusModel
private var hidePop: (() -> Unit)? = null
private var timer: Job? = null
private val listener = object : IMoGoAutopilotStatusListener {
override fun onAutopilotGuardian(guardianInfo: MogoReportMsg.MogoReportMessage?) {
super.onAutopilotGuardian(guardianInfo)
guardianInfo?.code?.takeIf {
it.contains("RTK_STATUS", true) || it.contains("CAN", true)
}?.run {
req()
}
}
}
private val appStateListener = object : IAppStateListener {
override fun onAppStateChanged(isForeground: Boolean) {
if (isForeground) {
req()
} else {
timer?.cancel()
}
}
}
private val flows: ArrayList<IFlow<out Status>> by lazy {
ArrayList()
}
private fun req() {
timer?.cancel()
model.viewModelScope.launch(Dispatchers.IO) {
CallerAutoPilotManager.sendStatusQueryReq()
while (true) {
delay(60000) //一分钟主动请求一次
CallerAutoPilotManager.sendStatusQueryReq()
}
}.also {
timer = it
}
}
fun init(ctx: Context) {
val owner = ctx as? ViewModelStoreOwner ?: throw IllegalStateException("ctx: $ctx is a instance of ViewModelStoreOwner.")
model = ViewModelProvider(owner).get(StatusModel::class.java)
@@ -55,6 +99,9 @@ object StatusManager {
private fun onCreate(ctx: Context) {
val values = model.status.value?.second ?: throw IllegalStateException("state is not right.")
CallerAutoPilotStatusListenerManager.addListener(TAG, listener)
AppStateManager.registerAppStateListener(appStateListener)
req()
values.map {
when (it) {
is CanStatus -> CanImpl(ctx)
@@ -79,7 +126,6 @@ object StatusManager {
f.onCreate()
}
}
ctx.normalPop(content,
width = 665.PX,
height = WindowManager.LayoutParams.WRAP_CONTENT,
@@ -88,7 +134,12 @@ object StatusManager {
isFocusable = false).also { hidePop = it }
}
private fun onDestroy(ctx: Context) {
CallerAutoPilotStatusListenerManager.removeListener(TAG)
AppStateManager.unRegisterAppStateListener(appStateListener)
timer?.cancel()
flows.forEach {
it.onDestroy()
}

View File

@@ -81,12 +81,16 @@ internal class GpsStatus(val enabled: Boolean = false, val isGranted: Boolean =
/**
* RTK/GNSS定位状态
*/
internal class RTKStatus(var enabled: Boolean = false): Status() {
internal class RTKStatus(var enabled: Boolean = false, var desc: String = "RTK"): Status() {
override fun equals(other: Any?): Boolean {
if (javaClass != other?.javaClass) return false
other as RTKStatus
if (enabled != other.enabled) return false
if (desc != other.desc) {
return false
}
return true
}

View File

@@ -1,7 +1,7 @@
package com.zhjt.mogo_core_function_devatools.status.flow.can
import android.content.*
import android.util.*
import android.util.Log
import chassis.Chassis.GearPosition
import chassis.Chassis.LightSwitch
import com.mogo.eagle.core.function.api.autopilot.*
@@ -11,6 +11,8 @@ import com.zhjt.mogo_core_function_devatools.status.flow.IFlow
import com.zhjt.mogo_core_function_devatools.status.entity.CanStatus
import kotlinx.coroutines.*
import mogo_msg.MogoReportMsg.MogoReportMessage
import system_master.SystemStatusInfo.StatusInfo
import java.util.concurrent.atomic.*
internal class CanImpl(ctx: Context): IFlow<CanStatus>(ctx), IMoGoAutopilotVehicleStateListener, IMoGoAutopilotStatusListener {
@@ -19,6 +21,7 @@ internal class CanImpl(ctx: Context): IFlow<CanStatus>(ctx), IMoGoAutopilotVehic
}
private var job: Job? = null
private val state: AtomicInteger by lazy { AtomicInteger(Int.MIN_VALUE) }
override fun onCreate() {
send(CanStatus(CallerAutoPilotManager.isConnected()))
@@ -33,7 +36,7 @@ internal class CanImpl(ctx: Context): IFlow<CanStatus>(ctx), IMoGoAutopilotVehic
private fun isCanEnabled(): Boolean {
val code = CallerAutoPilotStatusListenerManager.getAutoPilotReportMessageCode()
return CallerAutoPilotManager.isConnected() && code != "EHW_CAN"
return CallerAutoPilotManager.isConnected() && code != "EHW_CAN" && (state.get() == Int.MIN_VALUE || state.get() == 0)
}
@@ -69,12 +72,19 @@ internal class CanImpl(ctx: Context): IFlow<CanStatus>(ctx), IMoGoAutopilotVehic
timeOutCheck()
}
override fun onAutopilotStatusRespByQuery(status: StatusInfo) {
val state = status.healthInfoList?.find { "can_adapter".equals(it.name, true) }?.state?.ordinal
Log.d(TAG, "state: $state")
if (state != null) {
this.state.set(state)
}
}
private fun timeOutCheck() {
job?.safeCancel()
launch(Dispatchers.Default) {
delay(4000)
send(CanStatus(false))
send(CanStatus(isCanEnabled()))
}.also { job = it }
}

View File

@@ -9,6 +9,9 @@ import com.zhjt.mogo_core_function_devatools.status.entity.RTKStatus
import com.zhjt.mogo_core_function_devatools.status.flow.IFlow
import kotlinx.coroutines.*
import mogo.telematics.pad.MessagePad.GnssInfo
import system_master.SystemStatusInfo.HealthInfo
import system_master.SystemStatusInfo.StatusInfo
import java.util.concurrent.atomic.*
internal class RTKImpl(ctx: Context): IFlow<RTKStatus>(ctx), IMoGoAutopilotCarStateListener, IMoGoAutopilotStatusListener {
companion object {
@@ -17,6 +20,10 @@ internal class RTKImpl(ctx: Context): IFlow<RTKStatus>(ctx), IMoGoAutopilotCarSt
private var job: Job? = null
private val healthInfo by lazy {
AtomicReference<HealthInfo>(null)
}
override fun onCreate() {
send(RTKStatus(isRTKEnabled()))
Log.d(TAG, "-- onCreate --")
@@ -27,29 +34,42 @@ internal class RTKImpl(ctx: Context): IFlow<RTKStatus>(ctx), IMoGoAutopilotCarSt
private fun isRTKEnabled(): Boolean {
val code = CallerAutoPilotStatusListenerManager.getAutoPilotReportMessageCode()
val gnssInfo = CallerAutopilotCarStatusListenerManager.getCurrentGnssInfo()
val status = healthInfo.get()
return CallerAutoPilotManager.isConnected() && (
code != "EHW_RTK" &&
code != "EHW_GNSS" &&
code != "ESYS_RTK_STATUS_FAULT" &&
code != "ELCT_RTK_STATUS_FAULT" &&
code != "ELCT_RTK_STATUS_UNKNOWN") && gnssInfo != null
code != "ELCT_RTK_STATUS_UNKNOWN") && gnssInfo != null && (status == null || status.state?.ordinal == 0)
}
override fun onAutopilotCarStateData(gnssInfo: GnssInfo?) {
send(RTKStatus(isRTKEnabled()))
send(RTKStatus(isRTKEnabled(), getDesc()))
timeOutCheck()
}
override fun onAutopilotIpcConnectStatusChanged(status: Int, reason: String?) {
super.onAutopilotIpcConnectStatusChanged(status, reason)
send(RTKStatus(isRTKEnabled()))
send(RTKStatus(isRTKEnabled(), getDesc()))
}
override fun onAutopilotStatusRespByQuery(status: StatusInfo) {
val info = status.healthInfoList?.find { "localization".equals(it.name, true) }
Log.d(TAG, "info: $info")
if (info != null) {
healthInfo.set(info)
}
}
private fun getDesc(): String {
return healthInfo.get()?.desc?.uppercase() ?: "RTK"
}
private fun timeOutCheck() {
job?.safeCancel()
launch(Dispatchers.Default) {
delay(4000)
send(RTKStatus(false))
send(RTKStatus(isRTKEnabled(), getDesc()))
}.also { job = it }
}

View File

@@ -91,7 +91,7 @@ internal class StatusAdapter(val ctx: Context, var data: ArrayList<Status>): Rec
} else {
iv.background = ContextCompat.getDrawable(itemView.context, drawable.icon_dev_status_rtk_disable)
}
tv.text = "RTK"
tv.text = status.desc
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 332 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/icon_ai_select" android:state_focused="true" android:state_pressed="true" />
<item android:drawable="@drawable/icon_ai_select" android:state_focused="false" android:state_pressed="true" />
<item android:drawable="@drawable/icon_ai_select" android:state_selected="true" />
<item android:drawable="@drawable/icon_ai_select" android:state_focused="true" />
<item android:drawable="@drawable/icon_ai_normal" />
</selector>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient
android:startColor="#3A57C5"
android:endColor="#323C6F"
android:angle="0"
/>
<corners android:radius="40px" />
</shape>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient
android:startColor="#3A57C5"
android:endColor="#1E2E87"
android:angle="0"
/>
<corners android:radius="8px" />
</shape>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval"
android:useLevel="false">
<solid android:color="#6D7BAF" />
<stroke android:width="5px" android:color="#FFCCCCCC" />
<size
android:width="105px"
android:height="105px" />
</shape>

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval"
android:useLevel="false">
<gradient
android:startColor="#029DFF"
android:endColor="#0056FF"
android:angle="145"
/>
<stroke android:width="5px" android:color="#FFA7B6F0" />
<size
android:width="105px"
android:height="105px" />
</shape>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/icon_bad_case_select" android:state_focused="true" android:state_pressed="true" />
<item android:drawable="@drawable/icon_bad_case_select" android:state_focused="false" android:state_pressed="true" />
<item android:drawable="@drawable/icon_bad_case_select" android:state_selected="true" />
<item android:drawable="@drawable/icon_bad_case_select" android:state_focused="true" />
<item android:drawable="@drawable/icon_bad_case_normal" />
</selector>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:state_checked="false"
android:drawable="@drawable/icon_ap_badcase_default" />
<item
android:state_checked="true"
android:drawable="@drawable/icon_ap_badcase_check" />
</selector>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#263869" />
<corners android:radius="20px" />
<stroke
android:width="2dp"
android:color="#5EBFFF" />
</shape>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#FF3B4577" />
<corners android:radius="8px" />
</shape>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient
android:startColor="#029DFF"
android:endColor="#0056FF"
android:angle="0"
/>
<corners android:radius="8px" />
</shape>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient
android:startColor="#029DFF"
android:endColor="#0056FF"
android:angle="0"
/>
<corners android:radius="24px" />
</shape>

View File

@@ -0,0 +1,327 @@
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:layout_width="960px"
tools:layout_height="match_parent"
android:background="#F0151D41"
tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout">
<ImageView
android:id="@+id/ivConfigClose"
android:layout_width="107px"
android:layout_height="107px"
android:layout_marginTop="66px"
android:layout_marginEnd="40px"
android:src="@drawable/icon_close_nor"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<View
android:id="@+id/viewConfigTitleLine"
android:layout_width="14px"
android:layout_height="50px"
android:layout_marginStart="80px"
android:layout_marginTop="92px"
android:background="#2966EC"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tvConfigTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Case上报"
android:textColor="#FFFFFFFF"
android:textSize="42px"
app:layout_constraintTop_toTopOf="@id/viewConfigTitleLine"
app:layout_constraintBottom_toBottomOf="@id/viewConfigTitleLine"
app:layout_constraintLeft_toLeftOf="@id/viewConfigTitleLine"
android:layout_marginStart="50px"
/>
<TextView
android:id="@+id/tvConfigSave"
android:layout_width="800px"
android:layout_height="126px"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:text="保存"
android:textColor="#FFFFFFFF"
android:textSize="42px"
android:gravity="center"
android:background="@drawable/save_button_bg"
android:layout_marginBottom="100px"
/>
<ScrollView
android:layout_width="800px"
android:layout_height="0dp"
app:layout_constraintTop_toBottomOf="@id/viewConfigTitleLine"
app:layout_constraintBottom_toTopOf="@id/tvConfigSave"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:layout_marginTop="100px"
android:scrollbars="none"
>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintTop_toBottomOf="@id/viewConfigTitleLine"
app:layout_constraintLeft_toLeftOf="@id/viewConfigTitleLine"
>
<TextView
android:id="@+id/tvIdentityTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="身份"
android:textColor="#FFFFFFFF"
android:textSize="38px"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
/>
<RadioButton
android:id="@+id/rbSafetyOfficer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="安全员"
android:textColor="#FFFFFFFF"
android:textSize="38px"
android:button="@null"
android:checked="true"
android:drawableLeft="@drawable/badcase_radio_button_style"
android:drawablePadding="30px"
android:paddingTop="@dimen/dp_30"
android:paddingBottom="@dimen/dp_30"
android:paddingEnd="@dimen/dp_30"
app:layout_constraintTop_toBottomOf="@id/tvIdentityTitle"
app:layout_constraintLeft_toLeftOf="@id/tvIdentityTitle"
/>
<RadioButton
android:id="@+id/rbDeveloper"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="QA、研发"
android:textColor="#FFFFFFFF"
android:textSize="38px"
android:button="@null"
android:drawableLeft="@drawable/badcase_radio_button_style"
android:drawablePadding="30px"
android:paddingTop="@dimen/dp_30"
android:paddingBottom="@dimen/dp_30"
android:paddingEnd="@dimen/dp_30"
android:layout_marginStart="@dimen/dp_200"
app:layout_constraintTop_toBottomOf="@id/tvIdentityTitle"
app:layout_constraintLeft_toRightOf="@id/rbSafetyOfficer"
/>
<RadioButton
android:id="@+id/rbProduct"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="产品、运营、演示"
android:textColor="#FFFFFFFF"
android:textSize="38px"
android:button="@null"
android:drawableLeft="@drawable/badcase_radio_button_style"
android:drawablePadding="30px"
android:paddingTop="@dimen/dp_30"
android:paddingBottom="@dimen/dp_30"
android:paddingEnd="@dimen/dp_30"
app:layout_constraintTop_toBottomOf="@id/rbSafetyOfficer"
app:layout_constraintLeft_toLeftOf="@id/rbSafetyOfficer"
/>
<TextView
android:id="@+id/tvRecordTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="录制窗口"
android:textColor="#FFFFFFFF"
android:textSize="38px"
app:layout_constraintTop_toBottomOf="@id/rbProduct"
app:layout_constraintLeft_toLeftOf="@id/rbProduct"
android:layout_marginTop="@dimen/dp_80"
/>
<TextView
android:id="@+id/tvInitiativeTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="主动:"
android:textColor="#FFA7B6F0"
android:textSize="38px"
app:layout_constraintTop_toBottomOf="@id/tvRecordTitle"
app:layout_constraintLeft_toLeftOf="@id/tvRecordTitle"
android:layout_marginTop="@dimen/dp_30"
/>
<TextView
android:id="@+id/tvInitiativePre"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="前:"
android:textColor="#FFFFFFFF"
android:textSize="38px"
app:layout_constraintTop_toBottomOf="@id/tvInitiativeTitle"
app:layout_constraintLeft_toLeftOf="@id/tvInitiativeTitle"
android:layout_marginTop="@dimen/dp_50"
/>
<androidx.appcompat.widget.AppCompatEditText
android:id="@+id/etInitiativePreTime"
android:layout_width="228px"
android:layout_height="84px"
app:layout_constraintTop_toTopOf="@id/tvInitiativePre"
app:layout_constraintBottom_toBottomOf="@id/tvInitiativePre"
app:layout_constraintLeft_toRightOf="@id/tvInitiativePre"
android:background="@drawable/badcase_record_edit_bg"
android:textColor="#FFFFFFFF"
android:textSize="38px"
android:hint="12s"
android:textColorHint="#FFFFFFFF"
android:gravity="center"
/>
<TextView
android:id="@+id/tvInitiativeAfter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="后:"
android:textColor="#FFFFFFFF"
android:textSize="38px"
app:layout_constraintTop_toBottomOf="@id/tvInitiativePre"
app:layout_constraintLeft_toLeftOf="@id/tvInitiativePre"
android:layout_marginTop="@dimen/dp_60"
/>
<androidx.appcompat.widget.AppCompatEditText
android:id="@+id/etInitiativeAfterTime"
android:layout_width="228px"
android:layout_height="84px"
app:layout_constraintTop_toTopOf="@id/tvInitiativeAfter"
app:layout_constraintBottom_toBottomOf="@id/tvInitiativeAfter"
app:layout_constraintLeft_toRightOf="@id/tvInitiativeAfter"
android:background="@drawable/badcase_record_edit_bg"
android:textColor="#FFFFFFFF"
android:textSize="38px"
android:hint="8s"
android:textColorHint="#FFFFFFFF"
android:gravity="center"
/>
<TextView
android:id="@+id/tvPassiveTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="被动:"
android:textColor="#FFA7B6F0"
android:textSize="38px"
app:layout_constraintTop_toTopOf="@id/tvInitiativeTitle"
app:layout_constraintBottom_toBottomOf="@id/tvInitiativeTitle"
app:layout_constraintLeft_toRightOf="@id/tvInitiativeTitle"
android:layout_marginStart="@dimen/dp_300"
android:alpha="0.5"
/>
<TextView
android:id="@+id/tvPassivePre"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="前:"
android:textColor="#FFFFFF"
android:textSize="38px"
app:layout_constraintTop_toBottomOf="@id/tvPassiveTitle"
app:layout_constraintLeft_toLeftOf="@id/tvPassiveTitle"
android:layout_marginTop="@dimen/dp_50"
android:alpha="0.5"
/>
<TextView
android:id="@+id/etPassivePreTime"
android:layout_width="228px"
android:layout_height="84px"
app:layout_constraintTop_toTopOf="@id/tvPassivePre"
app:layout_constraintBottom_toBottomOf="@id/tvPassivePre"
app:layout_constraintLeft_toRightOf="@id/tvPassivePre"
android:background="@drawable/badcase_record_edit_bg"
android:alpha="0.5"
android:text="12s"
android:textColor="#FFFFFFFF"
android:textSize="38px"
android:gravity="center"
/>
<TextView
android:id="@+id/tvPassiveAfter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="后:"
android:textColor="#FFFFFFFF"
android:textSize="38px"
app:layout_constraintTop_toBottomOf="@id/tvPassivePre"
app:layout_constraintLeft_toLeftOf="@id/tvPassivePre"
android:layout_marginTop="@dimen/dp_60"
android:alpha="0.5"
/>
<TextView
android:id="@+id/etPassiveAfterTime"
android:layout_width="228px"
android:layout_height="84px"
app:layout_constraintTop_toTopOf="@id/tvPassiveAfter"
app:layout_constraintBottom_toBottomOf="@id/tvPassiveAfter"
app:layout_constraintLeft_toRightOf="@id/tvPassiveAfter"
android:background="@drawable/badcase_record_edit_bg"
android:alpha="0.5"
android:text="8s"
android:textColor="#FFFFFFFF"
android:textSize="38px"
android:gravity="center"
/>
<ImageView
android:id="@+id/ivRecordTemplate"
android:layout_width="19px"
android:layout_height="15px"
android:src="@drawable/icon_expand"
app:layout_constraintLeft_toLeftOf="@id/tvInitiativeAfter"
app:layout_constraintTop_toBottomOf="@id/tvInitiativeAfter"
android:layout_marginTop="@dimen/dp_80"
/>
<TextView
android:id="@+id/tvRecordTemplate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="预置录制模板"
android:textColor="#FFFFFFFF"
android:textSize="38px"
android:gravity="center_vertical"
app:layout_constraintTop_toTopOf="@id/ivRecordTemplate"
app:layout_constraintBottom_toBottomOf="@id/ivRecordTemplate"
app:layout_constraintLeft_toRightOf="@id/ivRecordTemplate"
android:layout_marginStart="@dimen/dp_30"
/>
<RadioGroup
android:id="@+id/rgRecordConfig"
android:layout_width="0dp"
android:layout_height="match_parent"
app:layout_constraintLeft_toLeftOf="@id/tvRecordTemplate"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvRecordTemplate"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginTop="@dimen/dp_20"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>
</merge>

View File

@@ -0,0 +1,183 @@
<?xml version="1.0" encoding="utf-8"?>
<com.mogo.eagle.core.widget.RoundConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="924px"
android:layout_height="668px"
app:roundLayoutRadius="40px"
android:background="@color/module_switch_map_bg">
<View
android:layout_width="match_parent"
android:layout_height="113px"
android:background="@drawable/ai_collect_title_bg"
/>
<TextView
android:id="@+id/tvCollectNum"
android:layout_width="120px"
android:layout_height="113px"
android:background="@drawable/icon_num_bg"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
android:textColor="#FFFFFFFF"
android:textSize="46px"
android:gravity="center"
android:text="1"
/>
<TextView
android:id="@+id/tvCollectTime"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintLeft_toRightOf="@id/tvCollectNum"
app:layout_constraintTop_toTopOf="@id/tvCollectNum"
app:layout_constraintBottom_toBottomOf="@id/tvCollectNum"
android:text="时间14:23:10"
android:textColor="#FFFFFFFF"
android:textSize="38px"
android:layout_marginStart="@dimen/dp_50"
/>
<RadioButton
android:id="@+id/rbLargeCar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="大型车:大货、大巴、特种车辆"
android:textColor="#FFFFFFFF"
android:textSize="34px"
android:button="@null"
android:drawableLeft="@drawable/badcase_radio_button_style"
android:drawablePadding="@dimen/dp_20"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvCollectNum"
android:layout_marginStart="@dimen/dp_50"
android:layout_marginTop="@dimen/dp_50"
android:checked="true"
/>
<RadioButton
android:id="@+id/rbTrafficLight"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="交通灯:水平、箭头、雨天交通灯"
android:textColor="#FFFFFFFF"
android:textSize="34px"
android:button="@null"
android:drawableLeft="@drawable/badcase_radio_button_style"
android:drawablePadding="@dimen/dp_20"
app:layout_constraintTop_toBottomOf="@id/rbLargeCar"
app:layout_constraintLeft_toLeftOf="@id/rbLargeCar"
android:layout_marginTop="@dimen/dp_30"
/>
<RadioButton
android:id="@+id/rbWater"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="积水距离10米内面积大于1平米"
android:textColor="#FFFFFFFF"
android:textSize="34px"
android:button="@null"
android:drawableLeft="@drawable/badcase_radio_button_style"
android:drawablePadding="@dimen/dp_20"
app:layout_constraintTop_toBottomOf="@id/rbTrafficLight"
app:layout_constraintLeft_toLeftOf="@id/rbTrafficLight"
android:layout_marginTop="@dimen/dp_30"
/>
<RadioButton
android:id="@+id/rbConstruction"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="施工:锥桶、路障"
android:textColor="#FFFFFFFF"
android:textSize="34px"
android:button="@null"
android:drawableLeft="@drawable/badcase_radio_button_style"
android:drawablePadding="@dimen/dp_20"
app:layout_constraintTop_toBottomOf="@id/rbWater"
app:layout_constraintLeft_toLeftOf="@id/rbWater"
android:layout_marginTop="@dimen/dp_30"
/>
<RadioButton
android:id="@+id/rbAccident"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="车祸路段:有三角板"
android:textColor="#FFFFFFFF"
android:textSize="34px"
android:button="@null"
android:drawableLeft="@drawable/badcase_radio_button_style"
android:drawablePadding="@dimen/dp_20"
app:layout_constraintTop_toTopOf="@id/rbConstruction"
app:layout_constraintBottom_toBottomOf="@id/rbConstruction"
app:layout_constraintLeft_toRightOf="@id/rbConstruction"
android:layout_marginStart="@dimen/dp_50"
/>
<RadioButton
android:id="@+id/rbRain"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="中雨交通流"
android:textColor="#FFFFFFFF"
android:textSize="34px"
android:button="@null"
android:drawableLeft="@drawable/badcase_radio_button_style"
android:drawablePadding="@dimen/dp_20"
app:layout_constraintTop_toBottomOf="@id/rbConstruction"
app:layout_constraintLeft_toLeftOf="@id/rbConstruction"
android:layout_marginTop="@dimen/dp_30"
/>
<RadioButton
android:id="@+id/rbNightTraffic"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="夜间交通流"
android:textColor="#FFFFFFFF"
android:textSize="34px"
android:button="@null"
android:drawableLeft="@drawable/badcase_radio_button_style"
android:drawablePadding="@dimen/dp_20"
app:layout_constraintTop_toBottomOf="@id/rbAccident"
app:layout_constraintLeft_toLeftOf="@id/rbAccident"
android:layout_marginTop="@dimen/dp_30"
/>
<TextView
android:id="@+id/tvCollectReport"
android:layout_width="270px"
android:layout_height="70px"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toLeftOf="@id/tvCollectCancel"
android:background="@drawable/report_button_bg"
android:text="上报"
android:textColor="#FFFFFF"
android:textSize="30px"
android:gravity="center"
android:layout_marginBottom="@dimen/dp_40"
/>
<TextView
android:id="@+id/tvCollectCancel"
android:layout_width="270px"
android:layout_height="70px"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintLeft_toRightOf="@id/tvCollectReport"
android:text="取消"
android:textColor="#FFFFFF"
android:textSize="30px"
android:gravity="center"
android:background="@drawable/cancel_button_bg"
android:layout_marginBottom="@dimen/dp_40"
/>
</com.mogo.eagle.core.widget.RoundConstraintLayout>

View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto">
<ImageView
android:id="@+id/ivBadCaseTools"
android:layout_width="120px"
android:layout_height="120px"
android:src="@drawable/bad_case_selector"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
/>
<ImageView
android:id="@+id/ivAiCollectTools"
android:layout_width="120px"
android:layout_height="120px"
android:src="@drawable/ai_collect_selector"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toRightOf="@id/ivBadCaseTools"
android:layout_marginStart="50px"
/>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,210 @@
<?xml version="1.0" encoding="utf-8"?>
<com.mogo.eagle.core.widget.RoundConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="924px"
android:layout_height="668px"
app:roundLayoutRadius="40px"
android:background="@color/module_switch_map_bg">
<View
android:layout_width="match_parent"
android:layout_height="113px"
android:background="@drawable/ai_collect_title_bg"
/>
<TextView
android:id="@+id/tvInitiativeNum"
android:layout_width="120px"
android:layout_height="113px"
android:background="@drawable/icon_num_bg"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
android:textColor="#FFFFFFFF"
android:textSize="46px"
android:gravity="center"
android:text="1"
/>
<TextView
android:id="@+id/tvInitiativeTime"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintLeft_toRightOf="@id/tvInitiativeNum"
app:layout_constraintTop_toTopOf="@id/tvInitiativeNum"
app:layout_constraintBottom_toBottomOf="@id/tvInitiativeNum"
android:text="时间14:23:10"
android:textColor="#FFFFFFFF"
android:textSize="38px"
android:layout_marginStart="@dimen/dp_50"
/>
<TextView
android:id="@+id/tvInitiativeIdentity"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="@id/tvInitiativeNum"
app:layout_constraintBottom_toBottomOf="@id/tvInitiativeNum"
app:layout_constraintRight_toRightOf="parent"
android:text="身份QA"
android:textColor="#FFFFFFFF"
android:textSize="38px"
android:layout_marginEnd="@dimen/dp_50"
/>
<RadioButton
android:id="@+id/rbOne"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="严重画龙"
android:textColor="#FFFFFFFF"
android:textSize="34px"
android:button="@null"
android:drawableLeft="@drawable/badcase_radio_button_style"
android:drawablePadding="@dimen/dp_20"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvInitiativeNum"
android:layout_marginStart="@dimen/dp_50"
android:layout_marginTop="@dimen/dp_50"
/>
<RadioButton
android:id="@+id/rbTwo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="点刹、顿挫"
android:textColor="#FFFFFFFF"
android:textSize="34px"
android:button="@null"
android:drawableLeft="@drawable/badcase_radio_button_style"
android:drawablePadding="@dimen/dp_20"
app:layout_constraintTop_toBottomOf="@id/tvInitiativeNum"
app:layout_constraintLeft_toRightOf="@id/rbOne"
android:layout_marginStart="@dimen/dp_50"
android:layout_marginTop="@dimen/dp_50"
/>
<RadioButton
android:id="@+id/rbThree"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="速度过慢"
android:textColor="#FFFFFFFF"
android:textSize="34px"
android:button="@null"
android:drawableLeft="@drawable/badcase_radio_button_style"
android:drawablePadding="@dimen/dp_20"
app:layout_constraintTop_toBottomOf="@id/tvInitiativeNum"
app:layout_constraintLeft_toRightOf="@id/rbTwo"
android:layout_marginStart="@dimen/dp_50"
android:layout_marginTop="@dimen/dp_50"
/>
<RadioButton
android:id="@+id/rbFour"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="速度过快"
android:textColor="#FFFFFFFF"
android:textSize="34px"
android:button="@null"
android:drawableLeft="@drawable/badcase_radio_button_style"
android:drawablePadding="@dimen/dp_20"
app:layout_constraintTop_toBottomOf="@id/rbOne"
app:layout_constraintLeft_toLeftOf="@id/rbOne"
android:layout_marginTop="@dimen/dp_50"
/>
<RadioButton
android:id="@+id/rbFive"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="存在碰撞风险"
android:textColor="#FFFFFFFF"
android:textSize="34px"
android:button="@null"
android:drawableLeft="@drawable/badcase_radio_button_style"
android:drawablePadding="@dimen/dp_20"
app:layout_constraintTop_toBottomOf="@id/rbTwo"
app:layout_constraintLeft_toLeftOf="@id/rbTwo"
android:layout_marginTop="@dimen/dp_50"
/>
<RadioButton
android:id="@+id/rbSix"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="感知、定位、地图等其他"
android:textColor="#FFFFFFFF"
android:textSize="34px"
android:button="@null"
android:drawableLeft="@drawable/badcase_radio_button_style"
android:drawablePadding="@dimen/dp_20"
app:layout_constraintTop_toBottomOf="@id/rbThree"
app:layout_constraintLeft_toLeftOf="@id/rbThree"
android:layout_marginTop="@dimen/dp_50"
/>
<TextView
android:id="@+id/tvInitiativeReport"
android:layout_width="270px"
android:layout_height="70px"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toLeftOf="@id/tvInitiativeCancel"
android:background="@drawable/report_button_bg"
android:text="上报"
android:textColor="#FFFFFF"
android:textSize="30px"
android:gravity="center"
android:layout_marginBottom="@dimen/dp_40"
/>
<TextView
android:id="@+id/tvInitiativeCancel"
android:layout_width="270px"
android:layout_height="70px"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintLeft_toRightOf="@id/tvInitiativeReport"
android:text="取消"
android:textColor="#FFFFFF"
android:textSize="30px"
android:gravity="center"
android:background="@drawable/cancel_button_bg"
android:layout_marginBottom="@dimen/dp_40"
/>
<View
android:id="@+id/viewAudioBg"
android:layout_width="824px"
android:layout_height="174px"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintBottom_toTopOf="@id/tvInitiativeReport"
android:background="@drawable/bad_case_audio_bg"
android:layout_marginBottom="@dimen/dp_40"
/>
<View
android:id="@+id/viewAudioButton"
android:layout_width="105px"
android:layout_height="105px"
app:layout_constraintLeft_toLeftOf="@id/viewAudioBg"
app:layout_constraintRight_toRightOf="@id/viewAudioBg"
app:layout_constraintTop_toTopOf="@id/viewAudioBg"
app:layout_constraintBottom_toBottomOf="@id/viewAudioBg"
android:background="@drawable/bad_case_audio_normal"
/>
<ImageView
android:layout_width="40px"
android:layout_height="55px"
android:src="@drawable/icon_audio"
app:layout_constraintLeft_toLeftOf="@id/viewAudioButton"
app:layout_constraintRight_toRightOf="@id/viewAudioButton"
app:layout_constraintTop_toTopOf="@id/viewAudioButton"
app:layout_constraintBottom_toBottomOf="@id/viewAudioButton"
/>
</com.mogo.eagle.core.widget.RoundConstraintLayout>

View File

@@ -38,10 +38,12 @@ import static com.zhjt.dispatch.model.DispatchServiceModel.DISPATCH_RESULT_AFFIR
import static com.zhjt.dispatch.model.DispatchServiceModel.DISPATCH_RESULT_MANUAL_CANCEL;
import static com.zhjt.dispatch.model.DispatchServiceModel.DISPATCH_RESULT_TIMER_CANCEL;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import mogo.telematics.pad.MessagePad;
import mogo_msg.MogoReportMsg;
import system_master.SystemStatusInfo;
//负责监听自动驾驶状态并进行状态上报,自动驾驶路线上报,接收调度指令展示指令弹窗
public class DispatchAutoPilotManager implements IMogoOnMessageListener<DispatchAdasAutoPilotLocReceiverBean>
@@ -339,4 +341,8 @@ public class DispatchAutoPilotManager implements IMogoOnMessageListener<Dispatch
@Override
public void onAutopilotIpcConnectStatusChanged(int status, @Nullable String reason) {
}
@Override
public void onAutopilotStatusRespByQuery(@NonNull SystemStatusInfo.StatusInfo status) {
}
}

View File

@@ -1,18 +1,26 @@
package com.mogo.eagle.core.function.hmi.ui
import android.animation.Animator
import android.content.*
import android.graphics.*
import android.graphics.drawable.*
import android.os.Build.VERSION
import android.os.Build.VERSION_CODES
import android.os.Bundle
import android.text.TextUtils
import android.transition.*
import android.util.Log
import android.view.Gravity
import android.view.View
import android.view.WindowManager
import android.view.WindowManager.LayoutParams
import android.view.animation.*
import android.widget.*
import androidx.core.view.*
import androidx.lifecycle.lifecycleScope
import com.alibaba.android.arouter.facade.annotation.Route
import com.mogo.commons.mvp.MvpFragment
import com.mogo.commons.voice.AIAssist
import com.mogo.commons.voice.*
import com.mogo.eagle.core.data.bindingcar.AdUpgradeStateHelper
import com.mogo.eagle.core.data.bindingcar.IPCUpgradeStateInfo
import com.mogo.eagle.core.data.camera.CameraEntity
@@ -27,6 +35,7 @@ import com.mogo.eagle.core.data.report.ReportEntity
import com.mogo.eagle.core.function.api.autopilot.IMoGoAutopilotRecordListener
import com.mogo.eagle.core.function.api.autopilot.IMoGoAutopilotStatusListener
import com.mogo.eagle.core.function.api.hmi.IMoGoHmiViewProxy
import com.mogo.eagle.core.function.api.hmi.IMoGoHmiViewProxy.IViewNotificationProvider
import com.mogo.eagle.core.function.api.hmi.view.IViewLimitingVelocity
import com.mogo.eagle.core.function.api.hmi.view.IViewNotification
import com.mogo.eagle.core.function.api.hmi.view.IViewTrafficLight
@@ -55,14 +64,19 @@ import com.mogo.eagle.core.function.hmi.ui.setting.ReportListFloatWindow
import com.mogo.eagle.core.function.hmi.ui.tools.AdUpgradeDialog
import com.mogo.eagle.core.function.hmi.ui.tools.AutoPilotAndCheckView
import com.mogo.eagle.core.function.hmi.ui.widget.V2XNotificationView
import com.mogo.eagle.core.utilcode.kotlin.*
import com.mogo.eagle.core.utilcode.mogo.AppIdentityModeUtils
import com.mogo.eagle.core.utilcode.mogo.logger.CallerLogger
import com.mogo.eagle.core.utilcode.mogo.logger.*
import com.mogo.eagle.core.utilcode.mogo.logger.scene.SceneConstant.Companion.M_HMI
import com.mogo.eagle.core.utilcode.reminder.*
import com.mogo.eagle.core.utilcode.reminder.api.*
import com.mogo.eagle.core.utilcode.reminder.api.IReminder.IStateChangeListener
import com.mogo.eagle.core.utilcode.reminder.api.impl.*
import com.mogo.eagle.core.utilcode.util.SoundUtils
import com.mogo.eagle.core.utilcode.util.ThreadUtils
import com.mogo.eagle.core.utilcode.util.TimeUtils
import com.mogo.eagle.core.utilcode.util.ToastUtils
import com.mogo.module.common.enums.EventTypeEnum
import com.mogo.module.common.enums.*
import kotlinx.android.synthetic.main.fragment_hmi.*
import kotlinx.coroutines.*
import mogo_msg.MogoReportMsg
@@ -82,15 +96,13 @@ import kotlin.collections.ArrayList
IMoGoHmiViewProxy,
MoGoHmiContract.View,
IMoGoAutopilotRecordListener,
IMoGoAutopilotStatusListener {
IMoGoAutopilotStatusListener, IViewNotificationProvider {
private val TAG = "MoGoHmiFragment"
// DebugSettingView
private var mDebugSettingViewFloat: WarningFloat.Builder? = null
private var mDebugSettingView: DebugSettingView? = null
// V2X、OBU、云端推送预警弹窗
private var mWarningFloat: WarningFloat.Builder? = null
private var mNoticeFloat: WarningFloat.Builder? = null
@@ -113,7 +125,7 @@ import kotlin.collections.ArrayList
private var mViewLimitingVelocity: IViewLimitingVelocity? = null
// V2X预警弹窗 View 代理
private var mViewNotification: IViewNotification? = null
private var mViewNotificationProvider: IViewNotificationProvider? = null
//工控机节点上报列表
private var reportList = arrayListOf<ReportEntity>()
@@ -123,6 +135,8 @@ import kotlin.collections.ArrayList
private var adUpgradeDialog: AdUpgradeDialog?=null
private var speakJob: Job? = null
override fun vipIdentification(visible: Boolean) {
ThreadUtils.runOnUiThread {
if (visible) {
@@ -174,7 +188,7 @@ import kotlin.collections.ArrayList
// 首次初始化使用默认视图
setProxyTrafficLightView(viewTrafficLightVr)
setProxyLimitingSpeedView(viewLimitingVelocity)
setProxyNotificationView(V2XNotificationView(view.context))
setViewNotificationProvider(this)
context?.also {
if (!AppIdentityModeUtils.isPassenger(FunctionBuildConfig.appIdentityMode)) {
@@ -183,7 +197,8 @@ import kotlin.collections.ArrayList
}
}
@OptIn(ExperimentalCoroutinesApi::class)
override fun getNotificationView(): IViewNotification? = context?.let { V2XNotificationView(it) }
override fun onAutopilotRecordResult(recordPanel: RecordPanelOuterClass.RecordPanel) {
if (HmiBuildConfig.isShowBadCaseView && recordPanel.type == 1 && recordPanel.stat == 100) {
CallerDevaToolsManager.onReceiveBadCaseRecord(recordPanel)
@@ -256,11 +271,8 @@ import kotlin.collections.ArrayList
}
}
/**
* 设置 V2X 通知 代理View
*/
override fun setProxyNotificationView(view: IViewNotification) {
mViewNotification = view
override fun setViewNotificationProvider(provider: IViewNotificationProvider) {
mViewNotificationProvider = provider
}
/**
@@ -379,7 +391,13 @@ import kotlin.collections.ArrayList
// 控制 BadCase 按钮展示
if (HmiBuildConfig.isShowBadCaseView) {
CallerDevaToolsManager.initBadCase(vsBadCaseToolsView)
ivBadCaseTools.visibility = View.VISIBLE
ivAiCollectTools.visibility = View.VISIBLE
CallerDevaToolsManager.initBadCase(ivBadCaseTools)
CallerDevaToolsManager.initAiCollect(ivAiCollectTools)
}else{
ivBadCaseTools.visibility = View.GONE
ivAiCollectTools.visibility = View.GONE
}
// 控制 红绿灯 展示
@@ -525,88 +543,97 @@ import kotlin.collections.ArrayList
alertContent: CharSequence?,
ttsContent: String?,
tag: String?,
listenerIMoGo: IMoGoWarningStatusListener?,
listener: IMoGoWarningStatusListener?,
playTts: Boolean,
expireTime: Long
) {
val playTTS = playTts && !AppIdentityModeUtils.isPassenger(FunctionBuildConfig.appIdentityMode)
lifecycleScope.launchWhenResumed {
activity?.let {
val floatWindow = mWarningFloat
val showTag = floatWindow?.config?.floatTag
if (floatWindow == null || TextUtils.isEmpty(showTag) || !floatWindow.isShow() || floatWindow.config.floatTag != tag) {
// 代理View初始化了才可以弹窗
mViewNotification?.let { notificationView ->
notificationView.setWarningIcon(EventTypeEnum.getWarningIcon(v2xType))
val warningContent = alertContent
?: EventTypeEnum.getWarningContent(v2xType)
if (warningContent.isEmpty()) {
CallerLogger.e("$M_HMI$TAG", "Show warningContent is null or empty!")
return@launchWhenResumed
} else {
notificationView.setWarningContent(warningContent)
}
if (floatWindow != null && floatWindow.isShow()) {
showWarning(WarningDirectionEnum.ALERT_WARNING_NON)
WarningFloat.dismiss(floatWindow.config.floatTag, true)
}
mWarningFloat = WarningFloat.with(it)
.setTag(tag)
.setLayout(notificationView)
.setSidePattern(notificationView.sidePattern)
.setCountDownTime(expireTime)
.setGravity(notificationView.layoutGravity, offsetX = notificationView.offsetX, offsetY = notificationView.offsetY)
.setImmersionStatusBar(true)
.isEnqueue(true)
.addWarningStatusListener(listenerIMoGo)
.addWarningStatusListener(object : IMoGoWarningStatusListener {
override fun onShow() {
// 创建弹窗成功才进行TTS播报
CallerLogger.d(
"$M_HMI$TAG",
"mWarningFloat = $mWarningFloat---ttsContent = $ttsContent"
)
if (mWarningFloat != null && !TextUtils.isEmpty(ttsContent) && playTTS) {
CallerLogger.d("$M_HMI$TAG", "---> ttsContent = $ttsContent")
AIAssist.getInstance(activity)
.speakTTSVoice(ttsContent)
}
}
override fun onDismiss() {
showWarning(WarningDirectionEnum.ALERT_WARNING_NON)
}
})
.setAnimator(object : DefaultAnimator() {
override fun enterAnim(
view: View,
params: LayoutParams,
windowManager: WindowManager,
sidePattern: SidePattern
): Animator? =
super.enterAnim(view, params, windowManager, sidePattern)?.apply {
interpolator = OvershootInterpolator()
}
override fun exitAnim(
view: View,
params: LayoutParams,
windowManager: WindowManager,
sidePattern: SidePattern
): Animator? =
super.exitAnim(view, params, windowManager, sidePattern)
?.setDuration(200)
})
.show()
}
} else {
val notification = floatWindow.config.layoutView as? V2XNotificationView
if (alertContent?.isNotEmpty() == true) {
notification?.setWarningContent(alertContent)
floatWindow.resetExpireTime(expireTime)
activity?.let {
val warningContent = alertContent
?: EventTypeEnum.getWarningContent(v2xType)
if (warningContent.isEmpty()) {
CallerLogger.e("$M_HMI$TAG", "Show warningContent is null or empty!")
return
}
speakJob?.safeCancel()
val content = mViewNotificationProvider?.getNotificationView() ?: return
content.setWarningIcon(EventTypeEnum.getWarningIcon(v2xType))
content.setWarningContent(warningContent)
var reminder: IReminder? = null
Reminder.enqueue(this@MoGoHmiFragment, object : PopupWindowReminder(PopupWindow(content, LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT).also { itx ->
itx.isTouchable = false
itx.isFocusable = false
itx.isClippingEnabled = false
itx.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
val transition = Slide(Gravity.TOP).also { t ->
t.interpolator = AccelerateDecelerateInterpolator()
t.duration = 200
}
if (VERSION.SDK_INT >= VERSION_CODES.M) {
itx.enterTransition = transition
itx.exitTransition = transition
}
}) {
override fun show() {
val parent = it.window.decorView
parent.doOnAttach {
popupWindow.showAtLocation(parent, Gravity.TOP, 0, 0)
}
}
override fun isOverride(): Boolean {
return true
}
}.also { itx -> reminder = itx }, object : IStateChangeListener {
override fun onShow(reminder: IReminder) {
listener?.onShow()
if (ttsContent != null && !TextUtils.isEmpty(ttsContent) && playTTS) {
CallerLogger.d("$M_HMI$TAG", "---> ttsContent = $ttsContent")
lifecycleScope.launch {
speak(it, ttsContent)
}.also {
speakJob = it
}
}
}
override fun onHide(reminder: IReminder) {
listener?.onDismiss()
showWarning(WarningDirectionEnum.ALERT_WARNING_NON)
}
})
if (reminder == null) {
return
}
lifecycleScope.launch {
delay(expireTime)
reminder?.hide()
}
}
}
private suspend fun speak(ctx: Context, text: String) = suspendCancellableCoroutine<Unit> {
try {
val voiceCallback = object : IMogoVoiceCmdCallBack {
override fun onSpeakEnd(speakText: String?) {
super.onSpeakEnd(speakText)
it.resumeWith(Result.success(Unit))
}
override fun onSpeakError(speakText: String?, errorMsg: String?) {
super.onSpeakError(speakText, errorMsg)
it.resumeWith(Result.success(Unit))
}
}
it.invokeOnCancellation {
AIAssist.getInstance(ctx).stopSpeakTts(text)
}
AIAssist.getInstance(ctx).speakTTSVoice(text, voiceCallback)
} catch (t: Throwable) {
it.resumeWith(Result.success(Unit))
Logger.e(TAG, t.message)
}
}
@@ -861,6 +888,14 @@ import kotlin.collections.ArrayList
dismissToolsFloatView()
}
override fun showSmallFragment() {
// TODO:("展示全览模式地图")
}
override fun hideSmallFragment() {
// TODO:("隐藏全览模式地图")
}
private fun showCameraList(cameraList: List<CameraEntity>?) {
context?.let {
if (cameraViewFloat == null) {

View File

@@ -11,9 +11,12 @@ import android.text.Html
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
import android.widget.*
import androidx.annotation.RequiresApi
import androidx.appcompat.widget.PopupMenu
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.content.ContextCompat
import androidx.core.view.*
import androidx.recyclerview.widget.LinearLayoutManager
import chassis.Chassis
import com.mogo.cloud.passport.MoGoAiCloudClient
@@ -52,6 +55,7 @@ import com.mogo.eagle.core.function.hmi.R
import com.mogo.eagle.core.function.hmi.ui.logcatch.ILogViewListener
import com.mogo.eagle.core.function.hmi.ui.logcatch.LogInfoView
import com.mogo.eagle.core.function.hmi.ui.upgrade.UpgradeListAdapter
import com.mogo.eagle.core.network.*
import com.mogo.eagle.core.utilcode.kotlin.onClick
import com.mogo.eagle.core.utilcode.mogo.AppIdentityModeUtils
import com.mogo.eagle.core.utilcode.mogo.logger.LogLevel
@@ -66,6 +70,7 @@ import com.mogo.map.uicontroller.VisualAngleMode.*
import com.mogo.module.service.routeoverlay.*
import com.zhidao.easysocket.utils.L
import com.zhidao.support.adas.high.other.permission.BackgrounderPermission
import com.zhjt.mogo_core_function_devatools.env.*
import kotlinx.android.synthetic.main.view_debug_setting.view.*
import mogo.telematics.pad.MessagePad
import mogo_msg.MogoReportMsg
@@ -536,6 +541,10 @@ class DebugSettingView @JvmOverloads constructor(
// 演示模式
tbIsDemoMode.setOnCheckedChangeListener { _, isChecked ->
CallerAutoPilotManager.setDemoMode(isChecked)
if(!isChecked){
//关闭美化模式时,通知工控机
CallerAutoPilotManager.setIPCDemoMode(isChecked)
}
FunctionBuildConfig.isDemoMode = isChecked
tbIsDrawAutopilotTrajectoryData.isEnabled = !isChecked
FunctionBuildConfig.isIgnoreConditionsDrawAutopilotTrajectoryData = isChecked
@@ -765,7 +774,7 @@ class DebugSettingView @JvmOverloads constructor(
/**
* 设置鹰眼本地参数配置监听
*/
private fun setEagleEyeConfigListener() {
@SuppressLint("SetTextI18n") private fun setEagleEyeConfigListener() {
//初始化刹车加速度阈值信息
val brakeThreshold = SharedPrefsMgr.getInstance(context)
.getFloat(MoGoConfig.BRAKE_ACCELERATION_THRESHOLD, -2.5F)
@@ -810,6 +819,41 @@ class DebugSettingView @JvmOverloads constructor(
tbReportWarning.visibility = GONE
}
//切换环境
tvCurEnv.text = "当前环境:${EnvChangeManager.getCityName()}${EnvChangeManager.getNetMode()}"
btChangeEnv.onClick {
PopupMenu(context, btChangeEnv).also { p ->
p.menuInflater.inflate(R.menu.menu_env_pop, p.menu)
MenuCompat.setGroupDividerEnabled(p.menu, true)
p.setOnMenuItemClickListener { item ->
when(item.itemId) {
R.id.group_hy -> {
return@setOnMenuItemClickListener false
}
R.id.group_bj -> {
return@setOnMenuItemClickListener false
}
R.id.env_reset ->
EnvChangeManager.reset()
R.id.hy_product ->
EnvChangeManager.changeTo("0734", DebugConfig.NET_MODE_RELEASE)
R.id.hy_qa ->
EnvChangeManager.changeTo("0734", DebugConfig.NET_MODE_QA)
R.id.hy_demo ->
EnvChangeManager.changeTo("0734", DebugConfig.NET_MODE_DEMO)
R.id.bj_product ->
EnvChangeManager.changeTo("010", DebugConfig.NET_MODE_RELEASE)
R.id.bj_qa ->
EnvChangeManager.changeTo("010", DebugConfig.NET_MODE_QA)
R.id.bj_demo ->
EnvChangeManager.changeTo("010", DebugConfig.NET_MODE_DEMO)
else ->
throw AssertionError("invalid item: $item")
}
return@setOnMenuItemClickListener true
}
}.show()
}
}
/**

View File

@@ -30,6 +30,7 @@ import org.jetbrains.annotations.NotNull;
import chassis.Chassis;
import mogo.telematics.pad.MessagePad;
import mogo_msg.MogoReportMsg;
import system_master.SystemStatusInfo;
import static com.mogo.eagle.core.utilcode.mogo.logger.scene.SceneConstant.M_BUS_P;
@@ -142,13 +143,17 @@ public class SteeringWheelView extends ConstraintLayout {
}
}
});
}
@Override
public void onAutopilotSNRequest() {
}
@Override
public void onAutopilotStatusRespByQuery(@NonNull SystemStatusInfo.StatusInfo status) {
}
};
private final IMoGoAutopilotVehicleStateListener mIMoGoAutopilotVehicleStateListener = new IMoGoAutopilotVehicleStateListener() {

View File

@@ -32,8 +32,7 @@ class V2XNotificationView @JvmOverloads constructor(
sidePattern = SidePattern.RESULT_TOP
layoutGravity = Gravity.CENTER_HORIZONTAL
// 设置View的停留位置
offsetX = 0
offsetY = 110
setPadding(0, 110, 0, 0)
}
override fun setWarningIcon(@DrawableRes warningIcon: Int) {

View File

@@ -63,6 +63,7 @@ import java.util.Map;
import mogo.telematics.pad.MessagePad;
import mogo_msg.MogoReportMsg;
import system_master.SystemStatusInfo;
/**
* @author congtaowang
@@ -396,6 +397,11 @@ public class MainActivity extends MvpActivity<MainView, MainPresenter> implement
public void onAutopilotSNRequest() {
}
@Override
public void onAutopilotStatusRespByQuery(@NonNull SystemStatusInfo.StatusInfo status) {
}
private void updateConnectInfoView(@NonNull AutopilotStatusInfo autoPilotStatusInfo) {
if (!isFloatingLayerHidden) {// 遮罩层显示的时候
mConnAdapter.updateData(autoPilotStatusInfo);

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

View File

@@ -109,16 +109,26 @@
<!--问题反馈-->
<ImageView
android:id="@+id/vsBadCaseToolsView"
android:layout_width="@dimen/module_hmi_check_size"
android:layout_height="@dimen/module_hmi_check_size"
android:layout_marginStart="25px"
android:layout_marginBottom="40px"
android:background="@drawable/icon_car_ap_badcase_entrance"
android:visibility="gone"
android:id="@+id/ivBadCaseTools"
android:layout_width="120px"
android:layout_height="120px"
android:src="@drawable/bad_case_selector"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@+id/ivToolsIcon"
app:layout_goneMarginStart="50px" />
app:layout_constraintStart_toEndOf="@id/ivToolsIcon"
android:layout_marginStart="50px"
android:layout_marginBottom="40px"
/>
<ImageView
android:id="@+id/ivAiCollectTools"
android:layout_width="120px"
android:layout_height="120px"
android:src="@drawable/ai_collect_selector"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@id/ivBadCaseTools"
android:layout_marginStart="50px"
android:layout_marginBottom="40px"
/>
<View
android:id="@+id/viewUpgradeTips"

View File

@@ -1036,6 +1036,40 @@
android:visibility="gone"
tools:visibility="visible">
<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/llChangeEnv"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="@dimen/dp_10"
android:orientation="horizontal"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:background="@drawable/debug_setting_edit_bg"
android:gravity="center_vertical">
<TextView
android:id="@+id/tvCurEnv"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:textColor="#000"
android:gravity="start"
android:layout_marginStart="@dimen/dp_10"
android:textSize="@dimen/dp_24"
android:layout_weight="1"
tools:text="当前环境:"/>
<Button
android:id="@+id/btChangeEnv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="@dimen/dp_8"
android:layout_marginEnd="@dimen/dp_10"
android:text="切换环境"
android:textSize="@dimen/dp_24"
android:textColor="#1A1A1A"/>
</androidx.appcompat.widget.LinearLayoutCompat>
<Button
android:id="@+id/btnBrakeThreshold"
android:layout_width="wrap_content"
@@ -1046,8 +1080,8 @@
android:paddingEnd="@dimen/dp_20"
android:text="设置刹车加速度阈值"
android:textSize="@dimen/dp_24"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />
app:layout_constraintTop_toBottomOf="@id/llChangeEnv"
app:layout_constraintLeft_toLeftOf="parent" />
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="0dp"
@@ -1132,12 +1166,9 @@
android:textOff="开启异常上报提示"
android:textOn="关闭异常上报提示"
android:textSize="@dimen/dp_24"
app:layout_constraintTop_toBottomOf="@id/btnConnectServerIp"
/>
app:layout_constraintTop_toBottomOf="@id/btnConnectServerIp" />
</androidx.constraintlayout.widget.ConstraintLayout>
<ToggleButton
android:id="@+id/tbHmiController"
android:layout_width="match_parent"

View File

@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2016 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:id="@+id/group_hy"
android:title="衡阳">
<menu>
<item android:id="@+id/hy_product" android:title="生产环境"/>
<item android:id="@+id/hy_qa" android:title="测试环境"/>
<item android:id="@+id/hy_demo" android:title="演示环境"/>
</menu>
</item>
<item android:id="@+id/group_bj"
android:title="北京">
<menu>
<item android:id="@+id/bj_product" android:title="生产环境"/>
<item android:id="@+id/bj_qa" android:title="测试环境"/>
<item android:id="@+id/bj_demo" android:title="演示环境"/>
</menu>
</item>
<item android:id="@+id/env_reset" android:title="重置" />
</menu>

View File

@@ -21,7 +21,7 @@
<string name="check_vehicle_detection">车辆检测</string>
<string name="debug_panel">调试面板</string>
<string name="debug_panel_fb">反馈</string>
<string name="debug_panel_fb">录包设置</string>
<string name="check_vehicle_speed_setting">车速设置</string>
<string name="check_system_operation">系统运行</string>
<string name="check_system_shut_down">关机</string>

View File

@@ -3,9 +3,13 @@ package com.mogo.eagle.core.function.map
import com.mogo.eagle.core.data.config.FunctionBuildConfig
import com.mogo.eagle.core.function.api.autopilot.IMoGoAutopilotPointCloudListener
import com.mogo.eagle.core.function.api.base.IMoGoSubscriber
import com.mogo.eagle.core.function.call.autopilot.CallerAutoPilotStatusListenerManager
import com.mogo.eagle.core.function.call.autopilot.CallerAutopilotPointCloudListenerManager
import com.mogo.eagle.core.utilcode.mogo.logger.Logger
import com.zhidaoauto.map.sdk.open.business.PointCloudHelper
import mogo.telematics.pad.MessagePad
import rule_segement.MogoPointCloudOuterClass
import java.math.BigDecimal
/**
* 订阅点云数据
@@ -56,9 +60,9 @@ class MapPointCloudSubscriber private constructor() : IMoGoSubscriber, IMoGoAuto
* @param isStrong 是否加粗显示
* @return 是否执行
*/
Logger.d(TAG, "====开始传入地图点云数据====")
//Logger.d(TAG, "====开始传入地图点云数据====")
val result = PointCloudHelper.updatePointCloudDataByPb(pointCloud, false, true, true)
Logger.d(TAG, "====结束传入地图点云数据=====$result")
//Logger.d(TAG, "====结束传入地图点云数据=====$result")
} else {
if (isDrawPointCloud) {
Logger.d(TAG, "====停止点云绘制====")
@@ -67,4 +71,16 @@ class MapPointCloudSubscriber private constructor() : IMoGoSubscriber, IMoGoAuto
}
}
}
override fun onAutopilotPointCloudDataUpdate(header: MessagePad.Header?, pointCloud: MogoPointCloudOuterClass.MogoPointCloud?) {
// Logger.d(TAG, "数据对比:"
// + "\n自车定位自车的 satelliteTime==${getPrettyNumber(CallerAutoPilotStatusListenerManager.getAutoPilotStatusInfo().satelliteTime.toString())} 经纬度:${CallerAutoPilotStatusListenerManager.getAutoPilotStatusInfo().locationLat},${CallerAutoPilotStatusListenerManager.getAutoPilotStatusInfo().locationLon}"
// + "\n点云数据点云的 header?.timestamp==${getPrettyNumber(header?.timestamp.toString())} 经纬度:${pointCloud?.selfLatitude},${pointCloud?.selfLongitude} 点云数量addDataCount==${pointCloud?.addDataCount} delDataCount===${pointCloud?.delDataCount} "
// )
}
fun getPrettyNumber(number: String): String {
return BigDecimal.valueOf(number.toDouble())
.stripTrailingZeros().toPlainString()
}
}

View File

@@ -4,6 +4,7 @@ import android.content.Context;
import android.os.Bundle;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.alibaba.android.arouter.facade.annotation.Route;
@@ -25,6 +26,7 @@ import java.util.List;
import mogo.telematics.pad.MessagePad;
import mogo_msg.MogoReportMsg;
import system_master.SystemStatusInfo;
/**
* @author donghongyu
@@ -176,4 +178,9 @@ public class SmallMapFragment extends BaseFragment
clearPolyline();
}
}
@Override
public void onAutopilotStatusRespByQuery(@NonNull SystemStatusInfo.StatusInfo status) {
}
}

View File

@@ -63,6 +63,16 @@ public class TestPanelBroadcastReceiver extends BroadcastReceiver {
intent.putExtra(V2XConst.BROADCAST_SCENE_EXTRA_KEY, v2XMessageEntity);
LocalBroadcastManager.getInstance(mContext).sendBroadcast(intent);
// 存储本地,出行动态作展示
saveLocalStory(V2XMessageEntity.V2XTypeEnum.ALERT_ROAD_WARNING,
v2XMessageEntity.getContent().getNoveltyInfo());
} if (sceneType == 2) {// 触发AI道路施工事件
V2XMessageEntity<V2XRoadEventEntity> v2XMessageEntity =
TestOnLineCarUtils.getV2XScenarioAIRoadEventData();
Intent intent = new Intent(V2XConst.BROADCAST_SCENE_HANDLER_ACTION);
intent.putExtra(V2XConst.BROADCAST_SCENE_EXTRA_KEY, v2XMessageEntity);
LocalBroadcastManager.getInstance(mContext).sendBroadcast(intent);
// 存储本地,出行动态作展示
saveLocalStory(V2XMessageEntity.V2XTypeEnum.ALERT_ROAD_WARNING,
v2XMessageEntity.getContent().getNoveltyInfo());

View File

@@ -1,5 +1,7 @@
package com.mogo.eagle.core.function.v2x.events.utils;
import static com.mogo.module.common.entity.V2XMessageEntity.V2XTypeEnum.ALERT_ROAD_WARNING;
import com.mogo.eagle.core.data.map.MogoLatLng;
import com.mogo.eagle.core.function.v2x.R;
import com.mogo.eagle.core.function.v2x.events.entity.net.V2XOptimalRouteDataRes;
@@ -7,10 +9,12 @@ import com.mogo.eagle.core.function.v2x.events.entity.net.V2XSpecialCarRes;
import com.mogo.eagle.core.network.utils.GsonUtil;
import com.mogo.eagle.core.utilcode.util.Utils;
import com.mogo.module.common.entity.MarkerExploreWay;
import com.mogo.module.common.entity.MarkerLocation;
import com.mogo.module.common.entity.MarkerResponse;
import com.mogo.module.common.entity.V2XMessageEntity;
import com.mogo.module.common.entity.V2XRoadEventEntity;
import com.mogo.module.common.entity.V2XWarningEntity;
import com.mogo.v2x.event.V2XEvent;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
@@ -80,7 +84,7 @@ public class TestOnLineCarUtils {
V2XMessageEntity<V2XRoadEventEntity> v2xMessageEntity = new V2XMessageEntity<>();
// 控制类型
v2xMessageEntity.setType(V2XMessageEntity.V2XTypeEnum.ALERT_ROAD_WARNING);
v2xMessageEntity.setType(ALERT_ROAD_WARNING);
// 设置数据
v2xMessageEntity.setContent(v2xRoadEventEntity);
// 控制展示状态
@@ -92,7 +96,22 @@ public class TestOnLineCarUtils {
return null;
}
/**
* 模拟道路事件测试数据
*/
public static V2XMessageEntity<V2XRoadEventEntity> getV2XScenarioAIRoadEventData() {
V2XRoadEventEntity entity = new V2XRoadEventEntity();
entity.setLocation(new MarkerLocation());
entity.setShowEventButton(false);
entity.setPoiType("100061");
entity.setExpireTime(20000);
V2XMessageEntity<V2XRoadEventEntity> body = new V2XMessageEntity<>();
body.setOnlyShow(false);
body.setShowState(true);
body.setContent(entity);
body.setType(ALERT_ROAD_WARNING);
return body;
}
/**
* 模拟道路事件UGC测试数据
*/