This commit is contained in:
zhongchao
2022-03-11 19:41:19 +08:00
parent 9275ed5ff2
commit 180bdfd50a
35 changed files with 1260 additions and 834 deletions

View File

@@ -3,6 +3,7 @@ package com.mogo.och.bus.fragment;
import android.animation.ObjectAnimator;
import android.content.Intent;
import android.graphics.Color;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
@@ -13,6 +14,8 @@ import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.constraintlayout.widget.Group;
import com.mogo.commons.AbsMogoApplication;
@@ -21,8 +24,15 @@ import com.mogo.commons.mvp.IView;
import com.mogo.commons.mvp.MvpFragment;
import com.mogo.commons.mvp.Presenter;
import com.mogo.commons.voice.AIAssist;
import com.mogo.eagle.core.data.autopilot.AutoPilotRecordResult;
import com.mogo.eagle.core.data.autopilot.AutopilotWarnMessage;
import com.mogo.eagle.core.data.config.HmiBuildConfig;
import com.mogo.eagle.core.data.traffic.TrafficData;
import com.mogo.eagle.core.function.api.autopilot.IMoGoAutopilotIdentifyListener;
import com.mogo.eagle.core.function.api.autopilot.IMoGoAutopilotStatusListener;
import com.mogo.eagle.core.function.call.autopilot.CallerAutoPilotStatusListenerManager;
import com.mogo.eagle.core.function.call.autopilot.CallerAutopilotIdentifyListenerManager;
import com.mogo.eagle.core.function.call.devatools.CallerDevaToolsManager;
import com.mogo.eagle.core.function.call.hmi.CallerHmiManager;
import com.mogo.eagle.core.utilcode.mogo.logger.Logger;
import com.mogo.eagle.core.utilcode.util.UiThreadHandler;
@@ -35,6 +45,8 @@ import com.mogo.och.bus.R;
import com.mogo.och.bus.view.BusArcView;
import com.mogo.och.bus.view.SlidePanelView;
import java.util.ArrayList;
/**
* 网约车基础Fragment主要负责布局通用界面处理站点面板和通话面板互斥情况
* <p>
@@ -42,7 +54,7 @@ import com.mogo.och.bus.view.SlidePanelView;
*
* @author tongchenfei
*/
public abstract class BaseOchBusTabFragment<V extends IView, P extends Presenter<V>> extends MvpFragment<V, P> implements IMogoMapListener {
public abstract class BaseOchBusTabFragment<V extends IView, P extends Presenter<V>> extends MvpFragment<V, P> implements IMogoMapListener, IMoGoAutopilotIdentifyListener {
private static final String TAG = "BaseOchFragment";
@@ -200,17 +212,45 @@ public abstract class BaseOchBusTabFragment<V extends IView, P extends Presenter
// mBadcaseBtn的visible显示逻辑在showBadcaseEntrance内处理
mBadcaseBtn = findViewById(R.id.module_mogo_och_badcase_iv);
CallerHmiManager.INSTANCE.registerBadCaseCallback(
() -> { // onShow()
return mBadcaseBtn; },
() -> { // onHide()
return null; });
// CallerHmiManager.INSTANCE.registerBadCaseCallback(
// () -> { // onShow()
// return mBadcaseBtn; },
// () -> { // onHide()
// return null; });
if (mBadcaseBtn != null) {
CallerDevaToolsManager.INSTANCE.initBadCase(mBadcaseBtn, null, null);
if (!HmiBuildConfig.isShowBadCaseView) {
CallerAutopilotIdentifyListenerManager.INSTANCE.addListener(TAG, this);
}
}
//设置升级小红点提示 默认隐藏
mUpgradeTipIv = findViewById(R.id.module_och_bus_upgrade_red_tip);
CallerHmiManager.INSTANCE.registerUpgradeTipsCallback(() -> mUpgradeTipIv);
}
@Override
public void onDestroyView() {
super.onDestroyView();
if (!HmiBuildConfig.isShowBadCaseView) {
CallerAutopilotIdentifyListenerManager.INSTANCE.removeListener(TAG);
}
}
@Override
public void onAutopilotRecordResult(@Nullable AutoPilotRecordResult record) {
if (!HmiBuildConfig.isShowBadCaseView && record != null && record.getType() == 1 && record.getStat() == 100) {
CallerDevaToolsManager.INSTANCE.onReceiveBadCaseRecord(record);
}
}
@Override
public void onAutopilotIdentifyDataUpdate(@Nullable ArrayList<TrafficData> trafficData) { }
@Override
public void onAutopilotWarnMessage(@Nullable AutopilotWarnMessage autopilotWarnMessage) { }
/**
* 测试到站
*/
@@ -367,11 +407,6 @@ public abstract class BaseOchBusTabFragment<V extends IView, P extends Presenter
return null;
}
@Override
public void onDestroyView() {
super.onDestroyView();
}
/**
* 获取站点面板view在{@link #initViews()}时候添加到container中
*

View File

@@ -3,6 +3,7 @@ package com.mogo.och.taxi.ui;
import android.animation.ObjectAnimator;
import android.content.Intent;
import android.graphics.drawable.AnimationDrawable;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
@@ -15,6 +16,8 @@ import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.constraintlayout.widget.Group;
import androidx.fragment.app.FragmentTransaction;
@@ -24,10 +27,17 @@ import com.mogo.commons.mvp.IView;
import com.mogo.commons.mvp.MvpFragment;
import com.mogo.commons.mvp.Presenter;
import com.mogo.commons.voice.AIAssist;
import com.mogo.eagle.core.data.autopilot.AutoPilotRecordResult;
import com.mogo.eagle.core.data.autopilot.AutopilotControlParameters;
import com.mogo.eagle.core.data.autopilot.AutopilotWarnMessage;
import com.mogo.eagle.core.data.config.HmiBuildConfig;
import com.mogo.eagle.core.data.traffic.TrafficData;
import com.mogo.eagle.core.function.api.autopilot.IMoGoAutopilotIdentifyListener;
import com.mogo.eagle.core.function.api.autopilot.IMoGoAutopilotStatusListener;
import com.mogo.eagle.core.function.call.autopilot.CallerAutoPilotManager;
import com.mogo.eagle.core.function.call.autopilot.CallerAutoPilotStatusListenerManager;
import com.mogo.eagle.core.function.call.autopilot.CallerAutopilotIdentifyListenerManager;
import com.mogo.eagle.core.function.call.devatools.CallerDevaToolsManager;
import com.mogo.eagle.core.function.call.hmi.CallerHmiManager;
import com.mogo.eagle.core.function.call.map.CallerSmpManager;
import com.mogo.eagle.core.utilcode.mogo.logger.Logger;
@@ -38,6 +48,8 @@ import com.mogo.module.common.constants.DataTypes;
import com.mogo.module.common.view.OnPreventFastClickListener;
import com.mogo.och.taxi.R;
import java.util.ArrayList;
/**
* 网约车基础Fragment主要负责布局通用界面处理站点面板和通话面板互斥情况
@@ -46,7 +58,7 @@ import com.mogo.och.taxi.R;
*
* @author tongchenfei
*/
public abstract class BaseOchTaxiTabFragment<V extends IView, P extends Presenter<V>> extends MvpFragment<V, P> implements IMogoMapListener {
public abstract class BaseOchTaxiTabFragment<V extends IView, P extends Presenter<V>> extends MvpFragment<V, P> implements IMogoMapListener, IMoGoAutopilotIdentifyListener {
private static final String TAG = "BaseOchFragment";
private LinearLayout ctvAutopilotStatus;
@@ -133,12 +145,18 @@ public abstract class BaseOchTaxiTabFragment<V extends IView, P extends Presente
// mBadcaseBtn的visible显示逻辑在showBadcaseEntrance内处理
mBadcaseBtn = findViewById(R.id.module_och_taxi_badcase_iv);
CallerHmiManager.INSTANCE.registerBadCaseCallback(
() -> { // onShow()
return mBadcaseBtn; },
() -> { // onHide()
return null; });
// CallerHmiManager.INSTANCE.registerBadCaseCallback(
// () -> { // onShow()
// return mBadcaseBtn; },
// () -> { // onHide()
// return null; });
if (mBadcaseBtn != null) {
CallerDevaToolsManager.INSTANCE.initBadCase(mBadcaseBtn, null, null);
if (!HmiBuildConfig.isShowBadCaseView) {
CallerAutopilotIdentifyListenerManager.INSTANCE.addListener(TAG, this);
}
}
//设置升级小红点提示 默认隐藏
mUpgradeTipIv = findViewById(R.id.module_och_taxi_upgrade_red_tip);
CallerHmiManager.INSTANCE.registerUpgradeTipsCallback(() -> mUpgradeTipIv);
@@ -240,6 +258,29 @@ public abstract class BaseOchTaxiTabFragment<V extends IView, P extends Presente
protected void onGoToTaxiOrders(){
}
@Override
public void onDestroyView() {
super.onDestroyView();
if (!HmiBuildConfig.isShowBadCaseView) {
CallerAutopilotIdentifyListenerManager.INSTANCE.removeListener(TAG);
}
}
@Override
public void onAutopilotIdentifyDataUpdate(@Nullable ArrayList<TrafficData> trafficData) { }
@Override
public void onAutopilotRecordResult(@Nullable AutoPilotRecordResult record) {
if (!HmiBuildConfig.isShowBadCaseView && record != null && record.getType() == 1 && record.getStat() == 100) {
CallerDevaToolsManager.INSTANCE.onReceiveBadCaseRecord(record);
}
}
@Override
public void onAutopilotWarnMessage(@Nullable AutopilotWarnMessage autopilotWarnMessage) { }
public void showNotice(String notice) {
getActivity().runOnUiThread(() -> {
AIAssist.getInstance(getContext()).speakTTSVoice(notice);
@@ -409,11 +450,6 @@ public abstract class BaseOchTaxiTabFragment<V extends IView, P extends Presente
return panelView;
}
@Override
public void onDestroyView() {
super.onDestroyView();
}
/**
* 获取站点面板view在{@link #initViews()}时候添加到container中
*

View File

@@ -234,16 +234,23 @@ dependencies {
compileOnly rootProject.ext.dependencies.adasapi
compileOnly rootProject.ext.dependencies.adasconfigapi
debugImplementation rootProject.ext.dependencies.debugleakcanary
releaseImplementation rootProject.ext.dependencies.releaseleakcanary
if (Boolean.valueOf(USE_MAVEN_PACKAGE)) {
implementation rootProject.ext.dependencies.mogo_core_function_main
implementation rootProject.ext.dependencies.ttspad
androidTestImplementation rootProject.ext.dependencies.mogo_core_function_call
androidTestImplementation rootProject.ext.dependencies.mogo_core_res
androidTestImplementation rootProject.ext.dependencies.mogo_core_function_hmi
androidTestImplementation rootProject.ext.dependencies.mogo_core_function_notice
} else {
implementation project(':core:function-impl:mogo-core-function-main')
implementation project(':tts:tts-pad')
androidTestImplementation project(':core:mogo-core-function-call')
androidTestImplementation project(':core:mogo-core-res')
androidTestImplementation project(':core:function-impl:mogo-core-function-hmi')
androidTestImplementation project(':core:function-impl:mogo-core-function-notice')
}
apply from: "./functions/och.gradle"

View File

@@ -1,11 +1,10 @@
package com.mogo.functions.test
import androidx.lifecycle.lifecycleScope
import androidx.test.core.app.ActivityScenario
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import com.mogo.eagle.core.data.autopilot.AutoPilotRecordResult
import com.mogo.eagle.core.function.hmi.ui.MoGoHmiFragment
import com.mogo.eagle.core.function.call.devatools.CallerDevaToolsManager
import com.mogo.eagle.core.function.main.MainLauncherActivity
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
@@ -14,7 +13,6 @@ import org.junit.Test
import org.junit.runner.RunWith
import java.text.SimpleDateFormat
import java.util.*
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
import kotlin.random.Random
@@ -31,14 +29,14 @@ class AutoPilotBadCaseTest {
@ExperimentalCoroutinesApi @Test
fun showBadCaseEntrance1(): Unit = runBlocking(Dispatchers.Main) {
val f = ensureMoGoHmiFragmentShow()
delay(5000)
var index = 0
(1 until 50)
.map { it }
.asFlow()
.onEach {
delay(TimeUnit.SECONDS.toMillis(5))
f.onAutopilotRecordResult(AutoPilotRecordResult().also {
CallerDevaToolsManager.onReceiveBadCaseRecord(AutoPilotRecordResult().also {
it.diskFree = (100 + index).toLong()
it.duration = 60.0
it.fileName = "/user/general/record_$index.log"
@@ -58,7 +56,7 @@ class AutoPilotBadCaseTest {
@ExperimentalCoroutinesApi @Test
fun showBadCaseEntrance2():Unit = runBlocking(Dispatchers.Main) {
val f = ensureMoGoHmiFragmentShow()
delay(5000)
var index = 0
(1 until 50)
.map { it }
@@ -69,7 +67,7 @@ class AutoPilotBadCaseTest {
} else {
delay(Random(20).nextLong())
}
f.onAutopilotRecordResult(AutoPilotRecordResult().also {
CallerDevaToolsManager.onReceiveBadCaseRecord(AutoPilotRecordResult().also {
it.diskFree = (100 + index).toLong()
it.duration = 60.0
it.fileName = "/user/general/record_$index.log"
@@ -80,11 +78,6 @@ class AutoPilotBadCaseTest {
it.timestamp = SimpleDateFormat("yyyyMMddHHmmss").format(Date())
index++
})
if (index == 5) {
f.lifecycleScope.cancel()
}
}
.flowOn(Dispatchers.Default)
.collect()
@@ -93,14 +86,13 @@ class AutoPilotBadCaseTest {
@ExperimentalCoroutinesApi @Test
fun showBadCaseEntrance3(): Unit = runBlocking(Dispatchers.Main) {
val f = ensureMoGoHmiFragmentShow()
var index = 0
(1 until 50)
.map { it }
.asFlow()
.onEach {
delay(TimeUnit.SECONDS.toMillis(20))
f.onAutopilotRecordResult(AutoPilotRecordResult().also {
CallerDevaToolsManager.onReceiveBadCaseRecord(AutoPilotRecordResult().also {
it.diskFree = (100 + index).toLong()
it.duration = 60.0
it.fileName = "/user/general/record_$index.log"
@@ -116,30 +108,4 @@ class AutoPilotBadCaseTest {
.collect()
delay(TimeUnit.HOURS.toMillis(2))
}
private suspend fun ensureMoGoHmiFragmentShow(): MoGoHmiFragment = suspendCancellableCoroutine {
launch.onActivity { itx ->
val executor = Executors.newSingleThreadScheduledExecutor()
executor.scheduleAtFixedRate({
var find =
itx.supportFragmentManager.fragments.find { it is MoGoHmiFragment } as? MoGoHmiFragment
while (find == null) {
find =
itx.supportFragmentManager.fragments.find { it is MoGoHmiFragment } as? MoGoHmiFragment
}
while (!find.isResumed) {
Thread.sleep(500)
}
it.resumeWith(Result.success(find))
try {
Thread.sleep(500)
executor.shutdownNow()
} catch (e: Throwable) {
e.printStackTrace()
}
}, 50, 500, TimeUnit.MILLISECONDS)
}
}
}

View File

@@ -4,6 +4,7 @@ plugins {
id 'kotlin-android-extensions'
id 'kotlin-kapt'
id 'com.alibaba.arouter'
id 'com.google.protobuf'
}
android {
@@ -39,21 +40,45 @@ android {
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
freeCompilerArgs += [
"-Xopt-in=kotlin.RequiresOptIn"
]
}
protobuf {
protoc {
artifact = rootProject.ext.dependencies.google_protoc
}
generateProtoTasks {
all().each { task ->
task.builtins {
java {}
}
}
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation rootProject.ext.dependencies.kotlinstdlibjdk7
implementation rootProject.ext.dependencies.coroutinescore
implementation rootProject.ext.dependencies.arouter
kapt rootProject.ext.dependencies.aroutercompiler
implementation rootProject.ext.dependencies.mogologlib
implementation rootProject.ext.dependencies.mogochainbase
kapt rootProject.ext.dependencies.androidxroomcompiler
implementation rootProject.ext.dependencies.androidxroomruntime
implementation rootProject.ext.dependencies.androidxroomktx
implementation rootProject.ext.dependencies.androidx_datastore
implementation rootProject.ext.dependencies.google_proto_java
implementation rootProject.ext.dependencies.androidxappcompat
implementation rootProject.ext.dependencies.androidxconstraintlayout
implementation rootProject.ext.dependencies.androidxrecyclerview
if (Boolean.valueOf(USE_MAVEN_PACKAGE)) {
implementation rootProject.ext.dependencies.mogoserviceapi
implementation rootProject.ext.dependencies.modulecommon
implementation rootProject.ext.dependencies.mogo_core_utils
implementation rootProject.ext.dependencies.mogo_core_function_api
implementation rootProject.ext.dependencies.mogo_core_function_call

View File

@@ -2,8 +2,10 @@ package com.zhjt.mogo_core_function_devatools
import android.annotation.SuppressLint
import android.content.Context
import android.view.View
import com.alibaba.android.arouter.facade.annotation.Route
import com.mogo.cloud.passport.MoGoAiCloudClientConfig
import com.mogo.eagle.core.data.autopilot.AutoPilotRecordResult
import com.mogo.eagle.core.data.chain.ChainConstant.Companion.CHAIN_LINK_LOG_ADAS_INIT
import com.mogo.eagle.core.data.chain.ChainConstant.Companion.CHAIN_LINK_LOG_ADAS_MSG
import com.mogo.eagle.core.data.chain.ChainConstant.Companion.CHAIN_LINK_LOG_CONNECT_STATUS
@@ -18,6 +20,7 @@ import com.mogo.eagle.core.utilcode.util.DeviceUtils
import com.mogo.eagle.core.utilcode.util.Utils
import com.zhidao.loglib.fw.FileWriteManager
import com.zhidao.loglib.fw.FwBuild
import com.zhjt.mogo_core_function_devatools.badcase.BadCaseManager
import com.zhjt.mogo_core_function_devatools.logcatch.MogoLogCatchManager
import com.zhjt.service.chain.core.ChainTraceStarter
@@ -100,6 +103,14 @@ class DevaToolsProvider : IDevaToolsProvider {
FileWriteManager.getInstance().operateChainMap(fwBuildMap)
}
override fun initBadCase(view: View, onShow: (() -> Unit)?, onHide: (() -> Unit)?) {
BadCaseManager.init(view, onShow, onHide)
}
override fun onReceiveBadCaseRecord(record: AutoPilotRecordResult) {
BadCaseManager.onReceiveBadCaseRecord(record)
}
override fun onDestroy() {
MogoLogCatchManager.onDestroy()
}

View File

@@ -0,0 +1,281 @@
package com.zhjt.mogo_core_function_devatools.badcase
import android.transition.AutoTransition
import android.transition.TransitionManager
import android.util.Log
import android.view.View
import android.view.ViewGroup
import androidx.lifecycle.Lifecycle.Event
import androidx.lifecycle.Lifecycle.Event.ON_DESTROY
import androidx.lifecycle.LifecycleCoroutineScope
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import com.mogo.cloud.passport.MoGoAiCloudClientConfig
import com.mogo.eagle.core.data.autopilot.AutoPilotRecordResult
import com.mogo.eagle.core.function.call.hmi.CallerHmiManager
import com.mogo.eagle.core.utilcode.kotlin.lifecycleOwner
import com.mogo.eagle.core.utilcode.kotlin.onClick
import com.mogo.eagle.core.utilcode.util.ToastUtils
import com.mogo.eagle.core.utilcode.util.Utils
import com.zhjt.mogo_core_function_devatools.badcase.api.entity.BadCaseResponse.Reason
import com.zhjt.mogo_core_function_devatools.badcase.mvp.BadCasePresenter
import com.zhjt.mogo_core_function_devatools.badcase.mvp.BadCaseView
import com.zhjt.mogo_core_function_devatools.badcase.repository.db.entity.AutoPilotRecord
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.Channel
import java.lang.ref.WeakReference
import java.util.concurrent.TimeUnit
internal object BadCaseManager : LifecycleEventObserver {
const val TAG = "BadCase"
/**
* 超过此时间case入口自动消失
*/
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
private var record: AutoPilotRecord? = null
@Volatile
private var viewHolder : WeakReference<View>? = null
@Volatile
private var dismissJob: Job? = null
@OptIn(ExperimentalCoroutinesApi::class)
private var channel: Channel<AutoPilotRecord> = Channel(Channel.RENDEZVOUS)
get() = if (field.isClosedForReceive || field.isClosedForSend) {
field = Channel(Channel.RENDEZVOUS)
field
} else {
field
}
private val presenter by lazy {
BadCasePresenter()
}
@Volatile
private var scope: LifecycleCoroutineScope? = null
get() = if (field == null) {
field = viewHolder?.get()?.lifecycleOwner?.lifecycleScope
field
} else {
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()
}
@OptIn(ExperimentalCoroutinesApi::class)
private fun register() {
scope?.launch(Dispatchers.Default) {
while (true) {
Log.d(TAG, "---- 开始监听BadCase事件 ----")
val old = record
if (old == null || old.consumed) {
Log.d(TAG, "---- 当前事件已消费 -- value: $old")
var receive = channel.receive()
var oldT = record?.toLongTime() ?: 0L
var newT = receive.toLongTime()
if (isValid(oldT, newT)) {
record = receive
Log.d(TAG, "---- 时间有效,开始展示入口 ---")
withContext(Dispatchers.Main) {
showBadCaseInternal(receive)
}
continue
}
Log.d(TAG, "---- 时间无效,移除管道中无用数据 ---")
presenter.deleteRecord(receive)
while (oldT != 0L && newT != 0L && (newT - oldT) >= CASE_EXPIRE_DURATION) {
oldT = newT
receive = channel.receive()
newT = receive.toLongTime()
presenter.deleteRecord(receive)
}
receive.takeIf { it.key != old?.key }?.also {
Log.d(TAG, "record: [$record] is displaying for rest ...")
record = receive
withContext(Dispatchers.Main) {
showBadCaseInternal(it)
}
}
} else {
Log.d(TAG, "record: [$old] hasn't been consumed~~~~")
withContext(Dispatchers.Main) {
showEntry()
}
delay(1000)
}
}
}
}
private fun recoverBadCase() {
scope?.launchWhenCreated {
val lastModified = presenter.getLastModified()
val list = withContext(Dispatchers.IO) {
try {
Log.d(TAG, " --- 1 ----")
Log.d(TAG, "恢复持久化的数据 - 最后修改时间:$lastModified")
presenter.getUnConsumedRecords().fold(mutableListOf<AutoPilotRecord>()) {
acc, record ->
if (isValid(lastModified, record.toLongTime())) {
acc.add(record)
} else {
presenter.deleteRecord(record)
}
acc
}
} catch (t: Throwable) {
emptyList()
}
}
if (list.isEmpty()) {
Log.d(TAG, "没有要恢复的数据")
} else {
list.forEach {
Log.d(TAG, "恢复的接管数据:$it")
channel.send(it)
}
}
}
}
private fun isValid(oldT: Long, newT: Long): Boolean {
return oldT == 0L || newT == 0L || (newT - oldT >= 0 && (newT - oldT) < CASE_EXPIRE_DURATION)
}
fun onReceiveBadCaseRecord(record: AutoPilotRecordResult) {
scope?.launch {
val newRecord = record.toRecord()
withContext(Dispatchers.IO) {
presenter.insertRecord(newRecord)
channel.send(newRecord)
}
}
}
private fun CoroutineScope.showBadCaseInternal(record: AutoPilotRecord) = launch {
viewHolder?.get()?.also {
presenter.updateLastModified(record.toLongTime())
showEntry()
it.onClick {
showBadCaseFloat(
onDismiss = {
hideFloat?.invoke()
hideFloat = null
},
onSelect = { reason ->
val uploadResult = presenter.upload(mutableMapOf<String, String>().also { itx ->
itx["carLicense"] = MoGoAiCloudClientConfig.getInstance().sn
itx["filename"] = record.fileName ?: ""
itx["filesize"] = record.total.toString()
itx["key"] = record.key ?: ""
itx["reason"] = reason.reason ?: ""
itx["duration"] = record.duration.toInt().toString()
itx["timestamp"] = record.timestamp
})
if (uploadResult == null || uploadResult.code != 200) {
ToastUtils.showShort("接管反馈失败")
} else {
ToastUtils.showShort("接管反馈成功")
record.consumed = true
withContext(Dispatchers.IO) {
presenter.deleteRecord(record)
}
hideEntry()
hideFloat?.invoke()
hideFloat = null
}
})
}
dismissAfterDelay()?.also { dismissJob = it }
}
}
private fun dismissAfterDelay(): Job? {
dismissJob?.takeIf { it.isActive }?.cancel()
return scope?.launch {
delay(CASE_EXPIRE_DURATION)
hideEntry()
record?.also {
it.consumed = true
withContext(Dispatchers.IO) {
presenter.deleteRecord(it)
}
}
}
}
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 context = viewHolder?.get()?.context ?: Utils.getApp()
BadCaseView(context).also {
it.register(record, onDismiss, onSelect)
hideFloat = CallerHmiManager.showBadCaseFloat(floatView = it)
}
}
override fun onStateChanged(source: LifecycleOwner, event: Event) {
if (event == ON_DESTROY) {
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 AutoPilotRecordResult.toRecord(): AutoPilotRecord = AutoPilotRecord().also {
it.id = this.id
it.stat = this.stat
it.key = this.key
it.note = this.note
it.type = this.type
it.total = this.total
it.fileName = this.fileName
it.duration = this.duration
it.diskFree = this.diskFree
it.consumed = false
}

View File

@@ -0,0 +1,19 @@
package com.zhjt.mogo_core_function_devatools.badcase.api
import com.zhjt.mogo_core_function_devatools.badcase.api.entity.BadCaseResponse
import com.zhjt.mogo_core_function_devatools.badcase.api.entity.UploadResult
import retrofit2.Response
import retrofit2.http.FieldMap
import retrofit2.http.FormUrlEncoded
import retrofit2.http.GET
import retrofit2.http.POST
internal interface BadCaseApi {
@FormUrlEncoded
@POST("/yycp-vehicle-management-service/tool/badcase/add")
suspend fun post(@FieldMap map: Map<String, String>): Response<UploadResult>
@GET("/yycp-vehicle-management-service/tool/badcase/reasons")
suspend fun get(): Response<BadCaseResponse>
}

View File

@@ -0,0 +1,28 @@
package com.zhjt.mogo_core_function_devatools.badcase.api.entity
import androidx.annotation.Keep
import com.google.gson.annotations.Expose
@Keep
internal class BadCaseResponse {
var code: Int = -1
var data: List<Reason>? = null
var msg: String? = null
var success: Boolean = false
var total: Int = -1
@Expose(serialize = false, deserialize = false)
var isBuildIn: Boolean = false
@Keep
class Reason {
var id: String? = null
var reason: String? = null
/**
* 业务字段,不参与序列化和反序列化
*/
@Expose(deserialize = false, serialize = false)
var isChecked: Boolean = false
}
}

View File

@@ -0,0 +1,15 @@
package com.zhjt.mogo_core_function_devatools.badcase.api.entity
import androidx.annotation.Keep
@Keep
internal class UploadResult {
var code: Int = -1
var msg: String? = null
var data: Array<String>? = null
var success: Boolean = false
override fun toString(): String {
return "UploadResult(code=$code, msg=$msg, data=${data?.contentToString()}, success=$success)"
}
}

View File

@@ -0,0 +1,8 @@
package com.zhjt.mogo_core_function_devatools.badcase.consts
import com.mogo.commons.debug.DebugConfig
internal object BadCaseHost {
fun getHost(): String = if (DebugConfig.getNetMode() == DebugConfig.NET_MODE_RELEASE) "http://dzt.zhidaozhixing.com" else "http://front.zdjs-private-test.myghost.zhidaoauto.com"
}

View File

@@ -0,0 +1,54 @@
package com.zhjt.mogo_core_function_devatools.badcase.mvp
import android.util.Log
import com.zhjt.mogo_core_function_devatools.badcase.BadCaseManager
import com.zhjt.mogo_core_function_devatools.badcase.api.entity.UploadResult
import com.zhjt.mogo_core_function_devatools.badcase.mvp.biz.IBadCasePresenter
import com.zhjt.mogo_core_function_devatools.badcase.repository.Repository
import com.zhjt.mogo_core_function_devatools.badcase.repository.db.entity.AutoPilotRecord
import kotlinx.coroutines.flow.Flow
internal class BadCasePresenter: IBadCasePresenter {
private val repository by lazy {
Repository()
}
override suspend fun loadBadCases() = repository.loadBadCases()
override suspend fun insertRecord(record: AutoPilotRecord) {
try {
repository.insert(record)
} catch (t: Throwable) {
Log.d(BadCaseManager.TAG, "-- 插入数据失败 -- msg: $t")
}
}
override suspend fun getUnConsumedRecords(): List<AutoPilotRecord> {
return try {
repository.getAllUnConsumedRecord() ?: emptyList()
} catch (t: Throwable) {
Log.d(BadCaseManager.TAG, "-- 获取所有未消费的数据失败 -- msg: $t")
emptyList()
}
}
override suspend fun deleteRecord(record: AutoPilotRecord) {
try {
repository.deleteRecord(record)
} catch (t: Throwable) {
Log.d(BadCaseManager.TAG, "-- 删除某条记录失败 -- msg: $t")
}
}
override suspend fun upload(map: Map<String, String>): UploadResult? = repository.upload(map)
override suspend fun updateLastModified(timestamp: Long) {
repository.uploadLastModified(timestamp)
}
override suspend fun getLastModified(): Long {
Log.d(BadCaseManager.TAG, " --- 2 ----")
return repository.getLastModified()
}
}

View File

@@ -0,0 +1,176 @@
package com.zhjt.mogo_core_function_devatools.badcase.mvp
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.GradientDrawable
import android.graphics.drawable.StateListDrawable
import android.util.AttributeSet
import android.util.StateSet
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.content.ContextCompat
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.mogo.eagle.core.data.autopilot.AutoPilotRecordResult
import com.mogo.eagle.core.utilcode.kotlin.*
import com.zhjt.mogo_core_function_devatools.R
import com.zhjt.mogo_core_function_devatools.badcase.api.entity.BadCaseResponse.Reason
import com.zhjt.mogo_core_function_devatools.badcase.repository.db.entity.AutoPilotRecord
import kotlinx.android.synthetic.main.layout_badcase_collect.view.*
import kotlinx.coroutines.launch
import java.text.SimpleDateFormat
import java.util.*
internal class BadCaseView: ConstraintLayout {
@Volatile
private var selectCase: Reason? = null
@Volatile
private var cases: List<Reason>? = null
private val presenter by lazy {
BadCasePresenter()
}
private var onDismiss: (() -> Unit)? = null
private var onSelect:(suspend (reason: Reason) -> Unit)? = null
private val scope by lazy {
lifecycleOwner.lifecycleScope
}
private var record: AutoPilotRecord? = null
constructor(context: Context) : this(context, null)
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
@SuppressLint("SetTextI18n") constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
LayoutInflater.from(context).inflate(R.layout.layout_badcase_collect, this, true)
background = ColorDrawable(Color.parseColor("#F0151D41"))
isClickable = true
layoutParams = ViewGroup.LayoutParams(960.toPixels().toInt(), 1528.toPixels().toInt())
close?.onClick {
onDismiss?.invoke()
}
cancel?.also {
it.background = shape(solid = Color.parseColor("#3B4577"), radius = 16)
it.onClick {
onDismiss?.invoke()
}
}
ok?.also {
val enabled = gradient(radius = 16.toPixels().toInt(), orientation = GradientDrawable.Orientation.LEFT_RIGHT, centerX = 0.06f, startColor = Color.rgb(35, 146, 252), endColor = Color.rgb(28, 75, 252))
val disabled = gradient(radius = 16.toPixels().toInt(), orientation = GradientDrawable.Orientation.LEFT_RIGHT, centerX = 0.06f, startColor = Color.rgb(24, 71, 129), endColor = Color.rgb(21, 46, 129))
it.background = object : StateListDrawable() {}.also { itx ->
itx.addState(intArrayOf(android.R.attr.state_enabled), enabled)
itx.addState(StateSet.WILD_CARD, disabled)
}
it.onClick {
selectCase?.run {
scope.launch {
onSelect?.invoke(this@run)
}
}
}
}
scope.launchWhenCreated {
time_of_take_over?.text = "接管时间:${SimpleDateFormat("yyyy.MM.dd HH:mm", Locale.getDefault()).format(record?.toLongTime() ?: System.currentTimeMillis())}"
showLoading()
presenter.loadBadCases().also {
cases = it
refresh(it)
}
hideLoading()
}
}
private fun refresh(causes: List<Reason>) {
cases = causes
rv_take_over?.let {
it.layoutManager = LinearLayoutManager(it.context, LinearLayoutManager.VERTICAL, false)
it.adapter = object : RecyclerView.Adapter<BadCaseViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BadCaseViewHolder = BadCaseViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.layout_badcase_item, parent, false))
override fun onBindViewHolder(holder: BadCaseViewHolder, position: Int) {
val cases = cases
if (cases == null || cases.isEmpty()) {
return
}
if (position >= cases.size) {
return
}
val case = cases[position]
holder.bindData(case)
}
override fun getItemCount(): Int = cases?.size ?: 0
}
}
}
private fun showLoading() {
pb?.let {
it.visibility = View.VISIBLE
}
}
private fun hideLoading() {
pb?.let {
it.visibility = View.INVISIBLE
}
}
private inner class BadCaseViewHolder(item: View) : RecyclerView.ViewHolder(item) {
private val check: ImageView = item.findViewById(R.id.check)
private val reason: TextView = item.findViewById(R.id.reason)
init {
check.background = StateListDrawable().also {
it.addState(intArrayOf(android.R.attr.state_selected), ContextCompat.getDrawable(itemView.context, R.drawable.icon_ap_badcase_check))
it.addState(StateSet.WILD_CARD, ContextCompat.getDrawable(itemView.context, R.drawable.icon_ap_badcase_default))
}
}
@SuppressLint("NotifyDataSetChanged")
fun bindData(case: Reason) {
check.isSelected = case.isChecked
reason.text = case.reason ?: ""
if (case.isChecked) {
ok?.isSelected = true
}
itemView.onClick {
case.isChecked = !case.isChecked
selectCase = case
cancelOtherChecked(case)
ok?.isEnabled = hasCheckedItem()
rv_take_over?.adapter?.notifyDataSetChanged()
}
}
private fun hasCheckedItem(): Boolean = cases?.find { it.isChecked } != null
private fun cancelOtherChecked(case: Reason) {
val cases = cases
if (cases == null || cases.isEmpty()) {
return
}
cases.filterNot { it == case }.forEach {
it.isChecked = false
}
}
}
fun register(record: AutoPilotRecord?, onDismiss: () -> Unit, onSelect:suspend (reason: Reason) -> Unit) {
this.record = record
this.onDismiss = onDismiss
this.onSelect = onSelect
}
}

View File

@@ -0,0 +1,24 @@
package com.zhjt.mogo_core_function_devatools.badcase.mvp.biz
import com.zhjt.mogo_core_function_devatools.badcase.api.entity.BadCaseResponse.Reason
import com.zhjt.mogo_core_function_devatools.badcase.api.entity.UploadResult
import com.zhjt.mogo_core_function_devatools.badcase.repository.db.entity.AutoPilotRecord
import kotlinx.coroutines.flow.Flow
internal interface IBadCasePresenter {
suspend fun loadBadCases(): List<Reason>
suspend fun updateLastModified(timestamp: Long)
suspend fun getLastModified(): Long
suspend fun upload(map: Map<String, String>): UploadResult?
suspend fun insertRecord(record: AutoPilotRecord)
suspend fun getUnConsumedRecords(): List<AutoPilotRecord>
suspend fun deleteRecord(record: AutoPilotRecord)
}

View File

@@ -0,0 +1,93 @@
package com.zhjt.mogo_core_function_devatools.badcase.repository
import android.util.Log
import com.zhjt.mogo_core_function_devatools.badcase.BadCaseManager
import com.zhjt.mogo_core_function_devatools.badcase.api.entity.BadCaseResponse.Reason
import com.zhjt.mogo_core_function_devatools.badcase.api.entity.UploadResult
import com.zhjt.mogo_core_function_devatools.badcase.repository.db.BadCaseDbModel
import com.zhjt.mogo_core_function_devatools.badcase.repository.db.entity.AutoPilotRecord
import com.zhjt.mogo_core_function_devatools.badcase.repository.net.BadCaseNetModel
import com.zhjt.mogo_core_function_devatools.badcase.repository.store.BadCaseStore
import kotlinx.coroutines.flow.Flow
internal class Repository {
private val net by lazy {
BadCaseNetModel()
}
private val db by lazy {
BadCaseDbModel()
}
private val store by lazy {
BadCaseStore
}
suspend fun loadBadCases(): List<Reason> {
return net.get()?.data?.takeIf { it.isNotEmpty() }?.also { store.updateRecords(it) } ?: store.records().takeIf { it.isNotEmpty() } ?: getBuildIn()
}
suspend fun uploadLastModified(timestamp: Long) {
store.updateLastModified(timestamp)
}
suspend fun getLastModified(): Long {
Log.d(BadCaseManager.TAG, " --- 3 ----")
return store.getLastModified()
}
private fun getBuildIn(): List<Reason> {
Log.d(BadCaseManager.TAG, "-- load cases from buildin -- 1 --")
val data = mutableListOf<Reason>()
data += Reason().also {
it.id = "1"
it.reason = "变道有干扰"
}
data += Reason().also {
it.id = "2"
it.reason = "遇红绿灯未停车"
}
data += Reason().also {
it.id = "3"
it.reason = "遇障碍物未停车"
}
data += Reason().also {
it.id = "4"
it.reason = "无法绕行"
}
data += Reason().also {
it.id = "5"
it.reason = "画龙"
}
data += Reason().also {
it.id = "6"
it.reason = "转弯过于靠近路侧"
}
data += Reason().also {
it.id = "7"
it.reason = "无故退出自动驾驶"
}
data += Reason().also {
it.id = "8"
it.reason = "其它"
}
return data
}
suspend fun upload(map: Map<String, String>): UploadResult? {
return net.upload(map)
}
suspend fun insert(record: AutoPilotRecord) {
db.dao().insertRecord(record)
}
suspend fun deleteRecord(record: AutoPilotRecord) {
db.dao().deleteRecord(record)
}
suspend fun getAllUnConsumedRecord(): List<AutoPilotRecord>? {
return db.dao().getAllUnConsumedRecords()
}
}

View File

@@ -0,0 +1,16 @@
package com.zhjt.mogo_core_function_devatools.badcase.repository.db
import androidx.room.Database
import androidx.room.RoomDatabase
import com.zhjt.mogo_core_function_devatools.badcase.repository.db.dao.IBadCaseRecordDao
import com.zhjt.mogo_core_function_devatools.badcase.repository.db.entity.AutoPilotRecord
@Database(entities = [
AutoPilotRecord::class
],
version = 1,
exportSchema = false)
internal abstract class BadCaseDb : RoomDatabase() {
abstract fun dao(): IBadCaseRecordDao
}

View File

@@ -0,0 +1,12 @@
package com.zhjt.mogo_core_function_devatools.badcase.repository.db
import androidx.room.Room
import com.mogo.eagle.core.utilcode.util.Utils
import com.zhjt.mogo_core_function_devatools.badcase.repository.db.dao.IBadCaseRecordDao
internal class BadCaseDbModel {
fun dao(): IBadCaseRecordDao {
return Room.databaseBuilder(Utils.getApp(), BadCaseDb::class.java, "bad-cases").build().dao()
}
}

View File

@@ -0,0 +1,19 @@
package com.zhjt.mogo_core_function_devatools.badcase.repository.db.dao
import androidx.room.*
import com.zhjt.mogo_core_function_devatools.badcase.repository.db.entity.AutoPilotRecord
@Dao
internal interface IBadCaseRecordDao {
@Transaction
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertRecord(record: AutoPilotRecord): Long
@Transaction
@Delete
suspend fun deleteRecord(record: AutoPilotRecord): Int
@Query("SELECT * FROM record ORDER BY timestamp ASC")
suspend fun getAllUnConsumedRecords(): List<AutoPilotRecord>?
}

View File

@@ -0,0 +1,89 @@
package com.zhjt.mogo_core_function_devatools.badcase.repository.db.entity
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import java.text.SimpleDateFormat
import java.util.*
@Entity(tableName = "record")
internal class AutoPilotRecord {
/**
* 磁盘可用空间(M)
*/
@ColumnInfo(name = "disk_free")
var diskFree: Long = 0
/**
* 采集时长
*/
var duration: Double = 0.0
/**
* 保存的文件名
*/
@ColumnInfo(name = "file_name")
var fileName: String? = ""
/**
* 其他信息,包含错误信息等
*/
var note: String? = ""
/**
* 域控制器定义的bag key
*/
var key: String? = ""
/**
* 采集状态:
* 100 - 采集成功,自动结束
* 101 - 采集成功,收到结束指令
* 200 - 采集失败
* 201 - 采集中
* 300 - 开始采集
*/
var stat: Int = 0
/**
* 任务类型1-badcase采集任务,2-地图数据采集任务
*/
var type: Int = 0
/**
* 任务ID
*/
var id: Int = 0
/**
* 时间戳格式YYYY-MM-DD-hh-mm-ss
*/
@PrimaryKey
var timestamp: String = SimpleDateFormat("yyyyMMddHHmmss", Locale.getDefault()).format(Date())
/**
* 此次采集数据总大小M
*/
var total: Long? = 0
/**
* 记录此条数据是否已消费
*/
@Volatile
var consumed: Boolean = false
override fun toString(): String {
return "AutoPilotRecord(diskFree=$diskFree, duration=$duration, fileName=$fileName, note=$note, key=$key, stat=$stat, type=$type, id=$id, timestamp='$timestamp', total=$total, consumed=$consumed)"
}
fun toLongTime(): Long = try {
SimpleDateFormat("yyyyMMddHHmmss", Locale.getDefault()).parse(this.timestamp)?.time ?: 0L
} catch (t: Throwable) {
0L
}
}

View File

@@ -0,0 +1,44 @@
package com.zhjt.mogo_core_function_devatools.badcase.repository.net
import android.util.Log
import com.mogo.eagle.core.network.MoGoRetrofitFactory
import com.zhjt.mogo_core_function_devatools.badcase.BadCaseManager
import com.zhjt.mogo_core_function_devatools.badcase.api.BadCaseApi
import com.zhjt.mogo_core_function_devatools.badcase.api.entity.BadCaseResponse
import com.zhjt.mogo_core_function_devatools.badcase.api.entity.UploadResult
import com.zhjt.mogo_core_function_devatools.badcase.consts.BadCaseHost
internal class BadCaseNetModel {
suspend fun get(): BadCaseResponse? = try {
Log.d(BadCaseManager.TAG, "-- load cases from net -- 1 --")
MoGoRetrofitFactory
.getInstance(BadCaseHost.getHost())
.create(BadCaseApi::class.java)
.get()
.takeIf {
val body = it.body()
it.isSuccessful && body != null && (body.code == 0 || body.code == 200)
}
?.body()?.also {
Log.d(BadCaseManager.TAG, "-- load cases from net -- 2 --")
}
} catch (t: Throwable) {
Log.d(BadCaseManager.TAG, "-- load cases from net -- 3 --")
null
}
suspend fun upload(map: Map<String, String>): UploadResult? = try {
MoGoRetrofitFactory
.getInstance(BadCaseHost.getHost())
.create(BadCaseApi::class.java)
.post(map)
.takeIf {
val body = it.body()
return@takeIf it.isSuccessful && (body != null) && (body.code == 0 || body.code == 200)
}
?.body()
} catch (t: Throwable) {
null
}
}

View File

@@ -0,0 +1,109 @@
package com.zhjt.mogo_core_function_devatools.badcase.repository.store
import android.util.Log
import androidx.datastore.core.DataStore
import androidx.datastore.core.DataStoreFactory
import androidx.datastore.core.Serializer
import com.mogo.eagle.core.utilcode.util.Utils
import com.zhjt.mogo_core_function_devatools.badcase.BadCaseManager
import com.zhjt.mogo_core_function_devatools.badcase.api.entity.BadCaseResponse
import com.zhjt.mogo_core_function_devatools.badcase.api.entity.BadCaseResponse.Reason
import com.zhjt.mogo_core_function_devatools.badcase.generated.BadCauses
import com.zhjt.mogo_core_function_devatools.badcase.generated.Cause
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
import java.io.File
import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
import kotlin.math.log
internal object BadCaseStore {
private val serializer = object : Serializer<BadCauses> {
override val defaultValue: BadCauses
get() = BadCauses.getDefaultInstance()
override suspend fun readFrom(input: InputStream): BadCauses = suspendCancellableCoroutine {
Log.d(BadCaseManager.TAG, "--- readFrom ---")
it.invokeOnCancellation {
Thread.currentThread().interrupt()
}
try {
it.resumeWith(Result.success(BadCauses.parseFrom(input)))
} catch (t: Throwable) {
it.resumeWith(Result.failure(t))
}
}
override suspend fun writeTo(t: BadCauses, output: OutputStream) = suspendCancellableCoroutine<Unit> {
it.invokeOnCancellation {
Thread.currentThread().interrupt()
}
try {
t.writeTo(output)
it.resumeWith(Result.success(Unit))
} catch (t: Throwable) {
it.resumeWith(Result.failure(t))
}
}
}
private val store: DataStore<BadCauses> by lazy {
DataStoreFactory.create(serializer = serializer) { File(Utils.getApp().filesDir, "bad_cases.pb") }
}
suspend fun updateRecords(reasons: List<Reason>): BadCauses {
Log.d(BadCaseManager.TAG, "--- updateRecords ---")
val data = mutableListOf<Cause>()
reasons.forEach { itx ->
data += Cause.newBuilder().let {
it.id = itx.id
it.reason = itx.reason
it.build()
}
}
return store.updateData { itx ->
itx.toBuilder().clearData().addAllData(data).build()
}
}
suspend fun updateLastModified(timestamp: Long): BadCauses {
Log.d(BadCaseManager.TAG, "--- updateLastModified ---")
return store.updateData { itx ->
itx.toBuilder().setLastModified(timestamp).build()
}
}
suspend fun getLastModified(): Long {
Log.d(BadCaseManager.TAG, " --- 4 ----")
return store
.data
.catch {
if (it is IOException) {
emit(BadCauses.getDefaultInstance())
}
}
.map {
it.lastModified
}.firstOrNull() ?: 0L
}
@OptIn(FlowPreview::class)
suspend fun records(): List<Reason> {
Log.d(BadCaseManager.TAG, "-- load cases from pb -- 1 -- ")
val causes = store.data.firstOrNull()
return causes?.dataList?.map {
Reason().also { itx ->
itx.id = it.id
itx.reason = it.reason
}
}?.fold(mutableListOf()) { acc, reason ->
acc += reason
acc
} ?: emptyList()
}
}

View File

@@ -0,0 +1,17 @@
syntax = "proto3";
option java_multiple_files = true;
option java_package = "com.zhjt.mogo_core_function_devatools.badcase.generated";
option java_outer_classname = "BadCausesProto";
message BadCauses {
int64 lastModified = 1;
repeated Cause data = 2 ;
}
message Cause {
string id = 1;
string reason = 2;
}

View File

@@ -38,6 +38,10 @@ android {
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
}
dependencies {

View File

@@ -2,21 +2,14 @@ package com.mogo.eagle.core.function.hmi.ui
import android.animation.Animator
import android.os.Bundle
import android.os.Handler
import android.text.TextUtils
import android.util.Log
import android.view.Gravity
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import android.view.animation.OvershootInterpolator
import androidx.lifecycle.Lifecycle.Event.ON_DESTROY
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.lifecycleScope
import androidx.transition.AutoTransition
import androidx.transition.TransitionManager
import com.alibaba.android.arouter.facade.annotation.Route
import com.mogo.cloud.passport.MoGoAiCloudClientConfig
import com.mogo.commons.mvp.MvpFragment
import com.mogo.commons.voice.AIAssist
import com.mogo.eagle.core.data.autopilot.AdUpgradeStateHelper
@@ -30,9 +23,9 @@ import com.mogo.eagle.core.data.notice.NoticeTrafficStylePushData
import com.mogo.eagle.core.function.api.autopilot.IMoGoAutopilotIdentifyListener
import com.mogo.eagle.core.function.api.hmi.warning.IMoGoWaringProvider
import com.mogo.eagle.core.function.api.hmi.warning.IMoGoWarningStatusListener
import com.mogo.eagle.core.function.call.autopilot.CallerAutoPilotManager
import com.mogo.eagle.core.function.call.autopilot.CallerAutopilotIdentifyListenerManager
import com.mogo.eagle.core.function.call.check.CallerCheckManager
import com.mogo.eagle.core.function.call.devatools.CallerDevaToolsManager
import com.mogo.eagle.core.function.call.map.CallerMapDataCollectorManager
import com.mogo.eagle.core.function.call.monitor.CallerMonitorManager
import com.mogo.eagle.core.function.hmi.R
@@ -45,23 +38,15 @@ import com.mogo.eagle.core.function.hmi.ui.notice.NoticeBannerView
import com.mogo.eagle.core.function.hmi.ui.notice.NoticeNormalBannerView
import com.mogo.eagle.core.function.hmi.ui.setting.DebugSettingView
import com.mogo.eagle.core.function.hmi.ui.tools.AutoPilotAndCheckView
import com.mogo.eagle.core.function.hmi.ui.tools.AutoPilotBadCaseView
import com.mogo.eagle.core.function.hmi.ui.tools.Repository
import com.mogo.eagle.core.function.hmi.ui.tools.post
import com.mogo.eagle.core.function.hmi.ui.widget.V2XNotificationView
import com.mogo.eagle.core.utilcode.kotlin.lifecycleOwner
import com.mogo.eagle.core.utilcode.kotlin.onClick
import com.mogo.eagle.core.utilcode.mogo.logger.Logger
import com.mogo.eagle.core.utilcode.util.ThreadUtils
import com.mogo.eagle.core.utilcode.util.ToastUtils
import com.mogo.eagle.core.utilcode.util.Utils
import com.mogo.module.common.enums.EventTypeEnum
import kotlinx.android.synthetic.main.fragment_hmi.*
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.Channel.Factory.UNLIMITED
import java.text.SimpleDateFormat
import java.util.*
import java.util.concurrent.TimeUnit
/**
* @author xiaoyuzhou
@@ -94,132 +79,8 @@ class MoGoHmiFragment : MvpFragment<MoGoWarningContract.View?, WaringPresenter?>
// 检测、自动驾驶速度设置
private var toolsViewFloat: WarningFloat.Builder? = null
@Volatile
private var autoPilotBadCaseEntrance: View? = null
private var autoPilotBadCaseView: AutoPilotBadCaseView? = null
private var onBadCaseShow: (() -> View)? = null
private var onBadCaseHide: (() -> Unit)? = null
private var upgradeTipsView: (() -> View)? = null
companion object {
private const val MSG_WHAT_DISMISS_BAD_CASE_ENTRY = 0x1010
private val CASE_EXPIRE_DURATION = TimeUnit.HOURS.toMillis(4)
}
@ExperimentalCoroutinesApi
private val channel by lazy {
Channel<AutoPilotRecordResult?>(UNLIMITED).also {
lifecycleScope.launchWhenResumed {
withContext(Dispatchers.Default) {
while (!it.isClosedForReceive) {
try {
val entrance = autoPilotBadCaseEntrance
val old = entrance?.getTag(R.id.autopilot_badcase_record) as? AutoPilotRecordResult
if (entrance == null || old == null || old.consumed) {
Log.d("QQQ", "-- step -- 1 --")
var oldT = try {
old?.timestamp?.takeIf { it.isNotBlank() }?.let {
SimpleDateFormat("yyyyMMddHHmmss", Locale.getDefault()).parse(it)?.time
?: 0L
} ?: 0L
} catch (t: Throwable) {
t.printStackTrace()
0L
}
var record: AutoPilotRecordResult? = null
var newT = try {
it.receive()?.also { record = it }?.timestamp?.takeIf { it.isNotBlank() }?.let {
SimpleDateFormat("yyyyMMddHHmmss", Locale.getDefault()).parse(it)?.time
?: 0L
} ?: 0L
} catch (t: Throwable) {
t.printStackTrace()
0L
}
if (oldT == 0L || (newT > 0L && (newT - oldT > 0L) && (newT - oldT) < CASE_EXPIRE_DURATION)) {
Log.d("QQQ", "-- step -- 2 --")
record?.takeIf { it.key != old?.key && it.timestamp != old?.timestamp }?.also {
Log.d("QQQ", "record: [$record] is displaying and consuming ~~~")
showBadCaseEntrance(it)
}
continue
}
while (oldT != 0L && newT != 0L && (newT - oldT) >= CASE_EXPIRE_DURATION) {
Log.d("QQQ", "record: [$record] has been discarded, because it has been timeout.")
oldT = newT
newT = try {
it.receive()?.also {
record = it
}?.timestamp?.takeIf { it.isNotBlank() }?.let {
SimpleDateFormat("yyyyMMddHHmmss", Locale.getDefault()).parse(it)?.time
?: 0L
} ?: 0L
} catch (t: Throwable) {
t.printStackTrace()
0L
}
}
record?.takeIf { it.key != old?.key && it.timestamp != old?.timestamp }?.also {
Log.d("QQQ", "record: [$record] is displaying for rest ...")
showBadCaseEntrance(it)
}
} else {
withContext(Dispatchers.Main) {
entrance.takeIf { it.visibility != View.VISIBLE }?.also {
(entrance.parent as? ViewGroup)?.let { g ->
TransitionManager.beginDelayedTransition(g, AutoTransition())
}
it.visibility = View.VISIBLE
}
}
Log.d("QQQ", "record: [$old] hasn't been consumed~~~~")
}
} finally {
delay(1000)
}
}
}
}
}
}
private val handler by lazy {
Handler(Handler.Callback { it ->
if (it.what == MSG_WHAT_DISMISS_BAD_CASE_ENTRY) {
val entrance = autoPilotBadCaseEntrance
if (entrance != null && entrance.visibility == View.VISIBLE) {
val record = entrance.getTag(R.id.autopilot_badcase_record) as? AutoPilotRecordResult
record?.consumed = true
record?.let { itx ->
lifecycleScope.launch(Dispatchers.IO) {
try {
val i = Repository.dao().deleteRecord(itx)
Log.d("QQQ", "delete result: $i")
} catch (t: Throwable) {
Log.d("QQQ", "---- delete error: ${t.message}")
}
}
}
dismissBadCaseFloatView()
if (entrance.visibility != View.GONE) {
(entrance.parent as? ViewGroup)?.let { g ->
TransitionManager.beginDelayedTransition(g, AutoTransition())
}
entrance.visibility = View.GONE
onBadCaseHide?.invoke()
}
}
return@Callback true
}
return@Callback false
})
}
override fun vipIdentification(visible: Boolean) {
ThreadUtils.runOnUiThread {
Logger.d(TAG, "vipIdentification")
@@ -260,18 +121,6 @@ class MoGoHmiFragment : MvpFragment<MoGoWarningContract.View?, WaringPresenter?>
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
CallerAutopilotIdentifyListenerManager.addListener(TAG, this)
lifecycleScope.launchWhenResumed {
withContext(Dispatchers.IO) {
val dao = Repository.dao()
try {
dao.getAllUnConsumedRecords()?.forEach {
channel.send(it)
}
} catch (t: Throwable) {
t.printStackTrace()
}
}
}
/*// TODO 这里后面需要改成独立进程通讯后台获取YUV
view.postDelayed({
@@ -282,21 +131,8 @@ class MoGoHmiFragment : MvpFragment<MoGoWarningContract.View?, WaringPresenter?>
@OptIn(ExperimentalCoroutinesApi::class)
override fun onAutopilotRecordResult(record: AutoPilotRecordResult?) {
record ?: return
Log.d("QQQ", "onAutopilotRecordResult:$record")
if (record.type == 1 && record.stat == 100) {
lifecycleScope.launchWhenResumed {
withContext(Dispatchers.IO) {
val dao = Repository.dao()
try {
dao.insertRecord(record)
} catch (t: Throwable) {
t.printStackTrace()
}
record.also {
channel.send(it)
}
}
}
if (HmiBuildConfig.isShowBadCaseView && record.type == 1 && record.stat == 100) {
CallerDevaToolsManager.onReceiveBadCaseRecord(record)
}
if (record.type == 2 && (record.stat == 101 || record.stat == 100)) {
CallerMapDataCollectorManager.finish(record.id, record.stat, "", record.fileName ?: "", record.note ?: "")
@@ -308,61 +144,6 @@ class MoGoHmiFragment : MvpFragment<MoGoWarningContract.View?, WaringPresenter?>
CallerAutopilotIdentifyListenerManager.removeListener(TAG)
}
private fun showBadCaseEntrance(record: AutoPilotRecordResult) {
Log.d("QQQ", "showBadCaseEntrance:$record")
lifecycleScope.launch {
if (HmiBuildConfig.isShowBadCaseView) {
if (vsBadCaseToolsView?.parent != null) {
val inflateView = vsBadCaseToolsView.inflate()
autoPilotBadCaseEntrance = inflateView
}
}
val entrance = autoPilotBadCaseEntrance
Log.d("QQQ", "show --- 1 ----")
if (entrance != null) {
if (entrance.visibility != View.VISIBLE) {
(entrance.parent as? ViewGroup)?.let { g ->
TransitionManager.beginDelayedTransition(g, AutoTransition())
}
entrance.visibility = View.VISIBLE
}
entrance.setTag(R.id.autopilot_badcase_record, record)
entrance.onClick {
showBadCasesFloat {
if (it.visibility != View.GONE) {
(entrance.parent as? ViewGroup)?.let { g ->
TransitionManager.beginDelayedTransition(g, AutoTransition())
}
it.visibility = View.GONE
onBadCaseHide?.invoke()
}
}
}
dismissBadCaseEntryAfterSomeTime()
} else {
val view = onBadCaseShow?.invoke()
if (view != null) {
view.lifecycleOwner.lifecycle.addObserver(badCaseEntranceObserver)
view.visibility = View.GONE
autoPilotBadCaseEntrance = view
showBadCaseEntrance(record)
}
}
}
}
private val badCaseEntranceObserver = LifecycleEventObserver { _, event ->
if (event == ON_DESTROY) {
onBadCaseShow = null
onBadCaseHide = null
autoPilotBadCaseEntrance = null
}
}
override fun registerBadCaseCallback(onShow: () -> View, onHide: (() -> Unit)?) {
onBadCaseShow = onShow
onBadCaseHide = onHide
}
/**
*注册工控机升级提示圆点View的回调
@@ -391,116 +172,6 @@ class MoGoHmiFragment : MvpFragment<MoGoWarningContract.View?, WaringPresenter?>
}
}
private fun showBadCasesFloat(dismiss: (() -> Unit)?) {
Log.d("QQQ", "showBadCaseToolsFloat")
context?.let { it ->
if (autoPilotToolsFloat == null) {
if (autoPilotBadCaseView == null) {
autoPilotBadCaseView = AutoPilotBadCaseView(it).also { itx ->
val record =
autoPilotBadCaseEntrance?.getTag(R.id.autopilot_badcase_record) as? AutoPilotRecordResult
itx.tag = record
itx.onDismiss {
dismissBadCaseFloatView()
}
itx.onSelect {
lifecycleScope.launch {
try {
val params = mutableMapOf<String, String>()
autoPilotBadCaseEntrance?.apply {
params["carLicense"] =
MoGoAiCloudClientConfig.getInstance().sn
params["filename"] = record?.fileName ?: ""
params["filesize"] = record?.total.toString()
params["key"] = record?.key ?: ""
params["reason"] = it.reason ?: ""
params["duration"] = record?.duration?.toInt()?.toString()
?: ""
params["timestamp"] = record?.timestamp ?: ""
}
val response = post(params)
if (response.isSuccessful) {
val body = response.body()
if (body == null) {
Log.e("QQQ", "返回的body是空的~~~")
return@launch
}
if (body.code == 200) {
Logger.i(TAG, "ok:${body}")
dismissBadCaseFloatView()
dismiss?.invoke()
CallerAutoPilotManager.recordCause(
record?.key,
record?.fileName,
it.id, it.reason)
ToastUtils.showShort("接管反馈成功~")
record?.also {
it.consumed = true
withContext(Dispatchers.IO) {
try {
Repository.dao().deleteRecord(record)
} catch (t: Throwable) {
Log.d("QQQ", "---- delete error 2: ${t.message}")
}
}
}
return@launch
}
Log.e("QQQ", "fail:${body}")
}
} catch (t: Throwable) {
t.printStackTrace()
ToastUtils.showShort("网络请求失败,请尝试联网~")
Log.e("QQQ", "exception:${t.message}")
}
}
}
}
}
autoPilotToolsFloat = WarningFloat.with(it)
.setTag("BadCaseCollectFloat")
.setLayout(autoPilotBadCaseView!!)
.setSidePattern(SidePattern.LEFT)
.setGravity(Gravity.LEFT, offsetY = 72)
.setImmersionStatusBar(true)
.setAnimator(object : DefaultAnimator() {
override fun enterAnim(
view: View,
params: WindowManager.LayoutParams,
windowManager: WindowManager,
sidePattern: SidePattern
): Animator? =
super.enterAnim(view, params, windowManager, sidePattern)
?.apply {
interpolator = OvershootInterpolator()
}
override fun exitAnim(
view: View,
params: WindowManager.LayoutParams,
windowManager: WindowManager,
sidePattern: SidePattern
): Animator? =
super.exitAnim(view, params, windowManager, sidePattern)
?.setDuration(200)
})
.addWarningStatusListener(object : IMoGoWarningStatusListener {
override fun onDismiss() {
autoPilotToolsFloat = null
autoPilotBadCaseView = null
}
})
.show()
} else {
autoPilotToolsFloat?.show()
}
}
}
private fun dismissBadCaseEntryAfterSomeTime() {
handler.removeMessages(MSG_WHAT_DISMISS_BAD_CASE_ENTRY)
handler.sendEmptyMessageDelayed(MSG_WHAT_DISMISS_BAD_CASE_ENTRY, CASE_EXPIRE_DURATION)
}
private fun showToolsFloat() {
Logger.d(TAG, "showToolsFloat")
@@ -597,6 +268,10 @@ class MoGoHmiFragment : MvpFragment<MoGoWarningContract.View?, WaringPresenter?>
} else {
setToolsViewVisibility(View.GONE)
}
if (HmiBuildConfig.isShowBadCaseView) {
CallerDevaToolsManager.initBadCase(vsBadCaseToolsView)
}
}
override fun getLayoutId(): Int {
@@ -1125,18 +800,54 @@ class MoGoHmiFragment : MvpFragment<MoGoWarningContract.View?, WaringPresenter?>
}
private fun dismissBadCaseFloatView() {
autoPilotToolsFloat?.let {
WarningFloat.dismiss(it.config.floatTag, false)
autoPilotToolsFloat = null
autoPilotBadCaseView = null
override fun showBadCaseFloat(tag: String, floatView: View): () -> Unit {
WarningFloat.with(context ?: Utils.getApp())
.setTag(tag)
.setLayout(floatView)
.setSidePattern(SidePattern.LEFT)
.setGravity(Gravity.START, offsetY = 72)
.setImmersionStatusBar(true)
.setAnimator(object : DefaultAnimator() {
override fun enterAnim(
view: View,
params: WindowManager.LayoutParams,
windowManager: WindowManager,
sidePattern: SidePattern
): Animator? =
super.enterAnim(view, params, windowManager, sidePattern)
?.apply {
interpolator = OvershootInterpolator()
}
override fun exitAnim(
view: View,
params: WindowManager.LayoutParams,
windowManager: WindowManager,
sidePattern: SidePattern
): Animator? =
super.exitAnim(view, params, windowManager, sidePattern)
?.setDuration(200)
})
.addWarningStatusListener(object : IMoGoWarningStatusListener {
override fun onDismiss() {
autoPilotToolsFloat = null
}
})
.also {
autoPilotToolsFloat = it
}
.show()
return {
autoPilotToolsFloat?.let {
WarningFloat.dismiss(it.config.floatTag, false)
autoPilotToolsFloat = null
}
}
}
override fun onDestroy() {
super.onDestroy()
Log.d(TAG, "onDestroy")
handler.removeCallbacksAndMessages(null)
}

View File

@@ -1,404 +0,0 @@
package com.mogo.eagle.core.function.hmi.ui.tools
import android.annotation.SuppressLint
import android.content.Context
import android.content.Context.MODE_PRIVATE
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.GradientDrawable
import android.graphics.drawable.StateListDrawable
import android.os.Handler
import android.util.AttributeSet
import android.util.StateSet
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.annotation.Keep
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.room.*
import com.google.gson.annotations.Expose
import com.mogo.commons.debug.DebugConfig
import com.mogo.commons.debug.DebugConfig.getNetMode
import com.mogo.eagle.core.data.autopilot.AutoPilotRecordResult
import com.mogo.eagle.core.function.hmi.R
import com.mogo.eagle.core.function.hmi.ui.tools.BadCaseEntity.Reason
import com.mogo.eagle.core.network.MoGoRetrofitFactory
import com.mogo.eagle.core.network.utils.GsonUtil
import com.mogo.eagle.core.utilcode.kotlin.*
import com.mogo.eagle.core.utilcode.util.ThreadUtils
import com.mogo.eagle.core.utilcode.util.Utils
import kotlinx.android.synthetic.main.layout_badcase_collect.view.*
import kotlinx.coroutines.*
import kotlinx.coroutines.android.asCoroutineDispatcher
import retrofit2.Response
import retrofit2.http.FieldMap
import retrofit2.http.FormUrlEncoded
import retrofit2.http.GET
import retrofit2.http.POST
import java.text.SimpleDateFormat
import java.util.*
import kotlin.Result.Companion.failure
import kotlin.Result.Companion.success
private typealias OnDismissCallback = () -> Unit
private typealias OnSelectCallback = (Reason) -> Unit
object Repository {
fun dao(): Dao {
return Room.databaseBuilder(Utils.getApp(), RecordDb::class.java, "bad-cases").build().dao()
}
@Database(entities = [
AutoPilotRecordResult::class
],
version = 1,
exportSchema = false)
abstract class RecordDb : RoomDatabase() {
abstract fun dao(): Dao
}
@androidx.room.Dao
interface Dao {
@Transaction
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertRecord(record: AutoPilotRecordResult): Long
@Transaction
@Delete
suspend fun deleteRecord(record: AutoPilotRecordResult): Int
@Query("SELECT * FROM record ORDER BY timestamp ASC")
suspend fun getAllUnConsumedRecords(): List<AutoPilotRecordResult>?
}
}
interface BadCaseApi {
@FormUrlEncoded
@POST("/yycp-vehicle-management-service/tool/badcase/add")
suspend fun post(@FieldMap map: Map<String, String>): Response<PostResult>
@GET("/yycp-vehicle-management-service/tool/badcase/reasons")
suspend fun get(): Response<BadCaseEntity>
}
@Keep
class BadCaseEntity {
var code: Int = -1
var data: List<Reason>? = null
var msg: String? = null
var success: Boolean = false
var total: Int = -1
@Expose(serialize = false, deserialize = false)
var isBuildIn: Boolean = false
@Keep
class Reason {
var id: String? = null
var reason: String? = null
/**
* 业务字段,不参与序列化和反序列化
*/
@Expose(deserialize = false, serialize = false)
var isChecked: Boolean = false
}
}
@Keep
class PostResult {
var code: Int = -1
var msg: String? = null
var data: Array<String>? = null
var success: Boolean = false
override fun toString(): String {
return "Result(code=$code, msg=$msg, data=${data?.contentToString()}, success=$success)"
}
}
private fun getHost(): String = if (getNetMode() == DebugConfig.NET_MODE_RELEASE) "http://dzt.zhidaozhixing.com" else "http://front.zdjs-private-test.myghost.zhidaoauto.com"
internal suspend fun post(map: Map<String, String>): Response<PostResult> {
return MoGoRetrofitFactory.getInstance(getHost()).create(BadCaseApi::class.java).post(map)
}
private suspend fun get(): Response<BadCaseEntity>? {
return try { MoGoRetrofitFactory.getInstance(getHost()).create(BadCaseApi::class.java).get() } catch (t: Throwable) { t.printStackTrace(); null}
}
private suspend fun updateCache(entity: BadCaseEntity) = suspendCancellableCoroutine<Unit> {
try {
val future = ThreadUtils.getIoPool().submit {
try {
val gson = GsonUtil.jsonFromObject(entity)
Sp.saveBody(gson)
it.resumeWith(success(Unit))
} catch (t: Throwable) {
it.resumeWith(failure(t))
}
}
it.invokeOnCancellation {
future.cancel(true)
}
} catch (e: Throwable) {
it.resumeWith(failure(e))
}
}
private suspend fun getCache(): BadCaseEntity? = suspendCancellableCoroutine {
try {
val body = Sp.getBody()
if (body != null && body.isNotEmpty()) {
val future = ThreadUtils.getIoPool().submit {
try {
val result = GsonUtil.objectFromJson(body, BadCaseEntity::class.java)
it.resumeWith(success(result))
} catch (t: Throwable) {
it.resumeWith(success(null))
}
}
it.invokeOnCancellation {
future.cancel(true)
}
}
} catch (t: Throwable) {
it.resumeWith(success(null))
}
}
private fun getBuildIn(): BadCaseEntity = BadCaseEntity().also { itx ->
val data = mutableListOf<Reason>()
data += Reason().also {
it.id = "1"
it.reason = "变道有干扰"
}
data += Reason().also {
it.id = "2"
it.reason = "遇红绿灯未停车"
}
data += Reason().also {
it.id = "3"
it.reason = "遇障碍物未停车"
}
data += Reason().also {
it.id = "4"
it.reason = "无法绕行"
}
data += Reason().also {
it.id = "5"
it.reason = "画龙"
}
data += Reason().also {
it.id = "6"
it.reason = "转弯过于靠近路侧"
}
data += Reason().also {
it.id = "7"
it.reason = "无故退出自动驾驶"
}
data += Reason().also {
it.id = "8"
it.reason = "其它"
}
itx.data = data
itx.isBuildIn = true
}
internal object Sp {
private val sp by lazy {
Utils.getApp().getSharedPreferences("bad_case_prefs", MODE_PRIVATE)
}
@SuppressLint("ApplySharedPref")
fun saveBody(body: String) {
sp.edit().putString("prefs", body).commit()
}
fun getBody(): String? {
return sp.getString("prefs", null)
}
}
class AutoPilotBadCaseView: ConstraintLayout {
private var dismiss: OnDismissCallback? = null
private var select: OnSelectCallback? = null
private var cases: List<Reason>? = null
private var selectCase: Reason? = null
private val scope = CoroutineScope(Handler().asCoroutineDispatcher() + SupervisorJob())
constructor(context: Context) : this(context, null)
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
LayoutInflater.from(context).inflate(R.layout.layout_badcase_collect, this, true)
background = ColorDrawable(Color.parseColor("#F0151D41"))
isClickable = true
layoutParams = ViewGroup.LayoutParams(960.toPixels().toInt(), 1528.toPixels().toInt())
close?.onClick {
dismiss?.invoke()
}
cancel?.also {
it.background = shape(solid = Color.parseColor("#3B4577"), radius = 16)
it.onClick {
dismiss?.invoke()
}
}
ok?.also {
val enabled = gradient(radius = 16.toPixels().toInt(), orientation = GradientDrawable.Orientation.LEFT_RIGHT, centerX = 0.06f, startColor = Color.rgb(35, 146, 252), endColor = Color.rgb(28, 75, 252))
val disabled = gradient(radius = 16.toPixels().toInt(), orientation = GradientDrawable.Orientation.LEFT_RIGHT, centerX = 0.06f, startColor = Color.rgb(24, 71, 129), endColor = Color.rgb(21, 46, 129))
it.background = object : StateListDrawable() {}.also { itx ->
itx.addState(intArrayOf(android.R.attr.state_enabled), enabled)
itx.addState(StateSet.WILD_CARD, disabled)
}
it.onClick {
val case = selectCase
if (case != null) {
select?.invoke(case)
}
}
}
}
override fun onAttachedToWindow() {
super.onAttachedToWindow()
val adapter = rv_take_over?.adapter
if (adapter != null && adapter.itemCount > 0) {
return
}
time_of_take_over?.text = "接管时间: ${(tag as? AutoPilotRecordResult)?.timestamp?.let {
try {
SimpleDateFormat("yyyyMMddHHmmss", Locale.getDefault()).parse(it)?.let { itx ->
SimpleDateFormat("yyyy.MM.dd HH:mm", Locale.getDefault()).format(itx)
}
} catch (e: Throwable) {
null
} ?: SimpleDateFormat("yyyy.MM.dd HH:mm", Locale.getDefault()).format(Date())}}"
scope.launch {
showLoading()
try {
get()?.takeIf { it.isSuccessful && it.body() != null && it.body()?.code == 200 }?.let {
val entity = it.body()!!
try {
updateCache(entity)
} catch (t: Throwable) {
t.printStackTrace()
} finally {
updateBadCaseList(entity)
}
}
?:
getCache()?.also {
updateBadCaseList(it)
}
?:
updateBadCaseList(getBuildIn())
} finally {
hideLoading()
}
}
}
private fun updateBadCaseList(body: BadCaseEntity) {
cases = body.data
rv_take_over?.let {
it.layoutManager = LinearLayoutManager(it.context, LinearLayoutManager.VERTICAL, false)
it.adapter = object : RecyclerView.Adapter<BadCaseViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BadCaseViewHolder = BadCaseViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.layout_badcase_item, parent, false))
override fun onBindViewHolder(holder: BadCaseViewHolder, position: Int) {
val cases = cases
if (cases == null || cases.isEmpty()) {
return
}
if (position >= cases.size) {
return
}
val case = cases[position]
holder.bindData(case)
}
override fun getItemCount(): Int = cases?.size ?: 0
}
}
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
scope.cancel("Cancel all for AutoPilotBadCaseView#onDetachedFromWindow")
}
private fun showLoading() {
pb?.let {
it.visibility = View.VISIBLE
}
}
private fun hideLoading() {
pb?.let {
it.visibility = View.INVISIBLE
}
}
private inner class BadCaseViewHolder(item: View) : RecyclerView.ViewHolder(item) {
private val check: ImageView = item.findViewById(R.id.check)
private val reason: TextView = item.findViewById(R.id.reason)
init {
check.background = StateListDrawable().also {
it.addState(intArrayOf(android.R.attr.state_selected), ContextCompat.getDrawable(itemView.context, R.drawable.icon_ap_badcase_check))
it.addState(StateSet.WILD_CARD, ContextCompat.getDrawable(itemView.context, R.drawable.icon_ap_badcase_default))
}
}
@SuppressLint("NotifyDataSetChanged")
fun bindData(case: Reason) {
check.isSelected = case.isChecked
reason.text = case.reason ?: ""
if (case.isChecked) {
ok?.isSelected = true
}
itemView.onClick {
case.isChecked = !case.isChecked
selectCase = case
cancelOtherChecked(case)
ok?.isEnabled = hasCheckedItem()
rv_take_over?.adapter?.notifyDataSetChanged()
}
}
private fun hasCheckedItem(): Boolean = cases?.find { it.isChecked } != null
private fun cancelOtherChecked(case: Reason) {
val cases = cases
if (cases == null || cases.isEmpty()) {
return
}
cases.filterNot { it == case }.forEach {
it.isChecked = false
}
}
}
fun onDismiss(dismiss:() -> Unit) {
this.dismiss = dismiss
}
fun onSelect(cb:(Reason) -> Unit) {
this.select = cb
}
}

View File

@@ -98,14 +98,14 @@
app:layout_constraintStart_toEndOf="@+id/viewPerspectiveSwitch"
app:layout_goneMarginStart="50px" />
<ViewStub
<ImageView
android:id="@+id/vsBadCaseToolsView"
android:inflatedId="@+id/badCaseToolsView"
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:layout="@layout/view_ap_badcase_entrance"
android:visibility="gone"
android:background="@drawable/icon_car_ap_badcase_entrance"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@+id/ivToolsIcon"
app:layout_goneMarginStart="50px"/>

View File

@@ -44,13 +44,16 @@ dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation rootProject.ext.dependencies.kotlinstdlibjdk7
implementation rootProject.ext.dependencies.arouter
implementation project(path: ':libraries:mogo-adas')
kapt rootProject.ext.dependencies.aroutercompiler
implementation rootProject.ext.dependencies.coroutinescore
implementation rootProject.ext.dependencies.coroutinesandroid
if (Boolean.valueOf(USE_MAVEN_PACKAGE)) {
implementation rootProject.ext.dependencies.mogo_core_data
compileOnly rootProject.ext.dependencies.adasHigh
} else {
implementation project(':core:mogo-core-data')
compileOnly project(':libraries:mogo-adas')
}
}

View File

@@ -1,5 +1,7 @@
package com.mogo.eagle.core.function.api.devatools
import android.view.View
import com.mogo.eagle.core.data.autopilot.AutoPilotRecordResult
import com.mogo.eagle.core.data.chain.ChainLogParam
import com.mogo.eagle.core.function.api.base.IMoGoFunctionServerProvider
@@ -18,4 +20,17 @@ interface IDevaToolsProvider : IMoGoFunctionServerProvider {
fun refreshTraceInfo(map: HashMap<Int, ChainLogParam>)
/**
* 初始化BadCase入口
* @param view: 展示入口
* @param onShow: BadCase入口展示时回调
* @param onHide: BadCase入口隐藏时回调
* 注: 此方法必须在[recoverBadCase]和[onReceiveBadCaseRecord]之前调用
*/
fun initBadCase(view: View, onShow: (() -> Unit)? = null, onHide: (() -> Unit)? = null)
/**
* 当工控机回调时调用
*/
fun onReceiveBadCaseRecord(record: AutoPilotRecordResult)
}

View File

@@ -186,13 +186,6 @@ interface IMoGoWaringProvider {
fun showAdUpgradeStatus(upgradeMode : Int,downloadStatus : Int,currentProgress : Int,totalProgress : Int
,downloadVersion : String,upgradeStatus : Int)
/**
* 注册badcase入口展示和隐藏的回调
* 当[onShow]被调用时,调用[showBadCaseEntrance]
* [onHide]回调不用关心,可以不注册
*/
fun registerBadCaseCallback(onShow:() -> View, onHide: (() -> Unit)?)
/**
*注册工控机升级提示圆点View的回调
* @param 提示圆点View
@@ -205,4 +198,11 @@ interface IMoGoWaringProvider {
* @param msg
*/
fun showDockerRebootResult(code: Int,msg: String)
/**
* @param floatView: 要展示的View
* @param tag: 唯一标识
* @return 触发消失时回调
*/
fun showBadCaseFloat(tag: String = "BadCaseFloat", floatView: View): () -> Unit
}

View File

@@ -1,5 +1,7 @@
package com.mogo.eagle.core.function.call.devatools
import android.view.View
import com.mogo.eagle.core.data.autopilot.AutoPilotRecordResult
import com.mogo.eagle.core.data.chain.ChainLogParam
import com.mogo.eagle.core.data.constants.MogoServicePaths
import com.mogo.eagle.core.function.api.devatools.IDevaToolsProvider
@@ -7,7 +9,7 @@ import com.mogo.eagle.core.function.call.base.CallerBase
object CallerDevaToolsManager {
private val devaToolsProviderApi: IDevaToolsProvider
private val devaToolsProviderApi: IDevaToolsProvider?
get() = CallerBase.getApiInstance(
IDevaToolsProvider::class.java,
MogoServicePaths.PATH_DEVA_TOOLS
@@ -17,7 +19,7 @@ object CallerDevaToolsManager {
* 开始抓取全量日志
*/
fun startCatchLog() {
devaToolsProviderApi.startLogCatch()
devaToolsProviderApi?.startLogCatch()
}
/**
@@ -25,27 +27,41 @@ object CallerDevaToolsManager {
* duration 分钟数
*/
fun startCatchLog(duration: Int) {
devaToolsProviderApi.startLogCatch(duration)
devaToolsProviderApi?.startLogCatch(duration)
}
/**
* 停止抓取全量日志
*/
fun stopCatchLog() {
devaToolsProviderApi.stopLogCatch()
devaToolsProviderApi?.stopLogCatch()
}
/**
* 更新链路节点信息,是否写入
*/
fun refreshTraceInfo(map: HashMap<Int, ChainLogParam>) {
devaToolsProviderApi.refreshTraceInfo(map)
devaToolsProviderApi?.refreshTraceInfo(map)
}
/**
* 获取链路节点信息
*/
fun getTraceInfo(): HashMap<Int, ChainLogParam> {
return devaToolsProviderApi.getTraceInfo()
return devaToolsProviderApi?.getTraceInfo() ?: HashMap()
}
/**
* 初始化BadCase相关配置
*/
fun initBadCase(view: View, onShow: (() -> Unit)? = null, onHide: (() -> Unit)? = null) {
devaToolsProviderApi?.initBadCase(view, onShow, onHide)
}
/**
* 收到工控机回调时触发
*/
fun onReceiveBadCaseRecord(record: AutoPilotRecordResult) {
devaToolsProviderApi?.onReceiveBadCaseRecord(record)
}
}

View File

@@ -264,15 +264,6 @@ object CallerHmiManager : CallerBase() {
waringProviderApi?.showAdUpgradeStatus(upgradeMode,downloadStatus, currentProgress, totalProgress, downloadVersion, upgradeStatus)
}
/**
* 注册badcase入口展示和隐藏的回调
* 当[onShow]被调用时, 表示达到展示条件,返回展示的入口控件
* [onHide]回调不用关心,可以不注册
*/
fun registerBadCaseCallback(onShow:() -> View, onHide: (() -> Unit)?) {
waringProviderApi?.registerBadCaseCallback(onShow, onHide)
}
/**
*注册工控机升级提示圆点View的回调
* @param 提示圆点View
@@ -290,4 +281,10 @@ object CallerHmiManager : CallerBase() {
waringProviderApi?.showDockerRebootResult(code, msg)
}
/**
* 展示BadCase浮层
*/
fun showBadCaseFloat(tag: String = "BadCaseFloat", floatView: View): (() -> Unit)? {
return waringProviderApi?.showBadCaseFloat(tag, floatView)
}
}