diff --git a/app/build.gradle b/app/build.gradle index 7aa3af751c..9607c94d7c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -4,75 +4,83 @@ apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' apply plugin: 'android-aspectjx' apply plugin: 'bugly' -apply plugin: 'apm-plugin' + +if (!isAndroidTestBuild()) { + apply plugin: 'apm-plugin' +} //apply ByteX宿主 -apply plugin: 'bytex' -ByteX { - enable true - enableInDebug true - logLevel "DEBUG" + +if (!isAndroidTestBuild()) { + apply plugin: 'bytex' + ByteX { + enable true + enableInDebug true + logLevel "DEBUG" + } } -apply plugin: 'bytex.threadOpt' +if (!isAndroidTestBuild()) { + apply plugin: 'bytex.threadOpt' + thread_opt { + enable true + enableInDebug true + rxJavaIoReplacer 'com/mogo/eagle/core/utilcode/util/ThreadUtils@@getIoPool@@()Ljava/util/concurrent/ExecutorService;' + rxJavaComputationReplacer 'com/mogo/eagle/core/utilcode/util/ThreadUtils@@getCpuPool@@()Ljava/util/concurrent/ExecutorService;' + coroutineIoReplacer 'com/mogo/eagle/core/utilcode/util/ThreadUtils@@getIoPool@@()Ljava/util/concurrent/ExecutorService;' + coroutineDefaultReplacer 'com/mogo/eagle/core/utilcode/util/ThreadUtils@@getCpuPool@@()Ljava/util/concurrent/ExecutorService;' + } -thread_opt { - enable true - enableInDebug true - rxJavaIoReplacer 'com/mogo/eagle/core/utilcode/util/ThreadUtils@@getIoPool@@()Ljava/util/concurrent/ExecutorService;' - rxJavaComputationReplacer 'com/mogo/eagle/core/utilcode/util/ThreadUtils@@getCpuPool@@()Ljava/util/concurrent/ExecutorService;' - coroutineIoReplacer 'com/mogo/eagle/core/utilcode/util/ThreadUtils@@getIoPool@@()Ljava/util/concurrent/ExecutorService;' - coroutineDefaultReplacer 'com/mogo/eagle/core/utilcode/util/ThreadUtils@@getCpuPool@@()Ljava/util/concurrent/ExecutorService;' + /** + * 方便使用systrace工具,在工程侧打点,便于分析工程侧性能问题 + */ + apply plugin: 'bytex.systrace' + systrace { + /** + * 交付时要关闭,会有性能损耗 + */ + enable false + enableInDebug false + /** + * - 是否使用[Trace.beginAsyncSection(String, int)/Trace.endAsyncSection(String, int)]进行打点 + * - 默认使用[Trace.beginSection(String)/Trace.endSection()]进行打点 + */ + isTraceAsync false + /** + * - 是否在运行时只针对主线程打点,其它线程不打 + */ + isOnlyMainThread false + + /** + * - 是否忽略对类的静态构造方法打点 + * - 默认不忽略 + */ + isIgnoreClinitMethod false + + /** + * - 是否忽略对类中的简单方法打点 + * 简单方法定义: + * - 空方法 + * - get/set 方法 + * - 单独的方法,方法体内没有调用其它方法 + * - 默认不忽略 + */ + isIgnoreSampleMethod false + + /** + * - 针对特定类集合,配置打点白名单,在此集合中的类中的所有方法不打点 + * - 支持正则表达式 + */ + whiteListForClass = [] + + /** + * - 针对特定包名集合,配置打点白名单,所有类以此包名为前缀的类不打点 + * - 支持正则表达式 + */ + whiteListForPackage = [] + } } -/** - * 方便使用systrace工具,在工程侧打点,便于分析工程侧性能问题 - */ -apply plugin: 'bytex.systrace' -systrace { - /** - * 交付时要关闭,会有性能损耗 - */ - enable false - enableInDebug false - /** - * - 是否使用[Trace.beginAsyncSection(String, int)/Trace.endAsyncSection(String, int)]进行打点 - * - 默认使用[Trace.beginSection(String)/Trace.endSection()]进行打点 - */ - isTraceAsync false - /** - * - 是否在运行时只针对主线程打点,其它线程不打 - */ - isOnlyMainThread false - - /** - * - 是否忽略对类的静态构造方法打点 - * - 默认不忽略 - */ - isIgnoreClinitMethod false - - /** - * - 是否忽略对类中的简单方法打点 - * 简单方法定义: - * - 空方法 - * - get/set 方法 - * - 单独的方法,方法体内没有调用其它方法 - * - 默认不忽略 - */ - isIgnoreSampleMethod false - - /** - * - 针对特定类集合,配置打点白名单,在此集合中的类中的所有方法不打点 - * - 支持正则表达式 - */ - whiteListForClass = [] - - /** - * - 针对特定包名集合,配置打点白名单,所有类以此包名为前缀的类不打点 - * - 支持正则表达式 - */ - whiteListForPackage = [] -} /*apply plugin: 'chain.log.hook' hooklog{ @@ -146,6 +154,10 @@ android { targetCompatibility 1.8 } + kotlinOptions { + jvmTarget = '1.8' + } + sourceSets { main { manifest.srcFile 'src/main/AndroidManifest.xml' @@ -199,6 +211,10 @@ android { packagingOptions { exclude 'META-INF/io.netty.versions.properties' } + + useLibrary 'android.test.runner' + useLibrary 'android.test.base' + useLibrary 'android.test.mock' } repositories { @@ -236,31 +252,38 @@ dependencies { apply from: "./functions/tts.gradle" apply from: "./functions/och.gradle" - + androidTestImplementation rootProject.ext.dependencies.androidx_test_core + androidTestImplementation rootProject.ext.dependencies.androidx_test_core_ktx + androidTestImplementation rootProject.ext.dependencies.androidx_unit_ext + androidTestImplementation rootProject.ext.dependencies.androidx_unit_ext_ktx + androidTestImplementation rootProject.ext.dependencies.androidx_runner + androidTestImplementation rootProject.ext.dependencies.androidx_espresso_core } -ApmPlugin { - // 是否进行插桩 - enable true - // 是否在Debug包插桩,默认不插桩 - enableInDebug true - // DEBUG("DEBUG"), INFO("INFO"), WARN("WARN"), ERROR("ERROR"); - // INFO 级别Log会汇总所有被插桩处理的类供查看,路径 app/build/ByteX/ApmPlugin - logLevel "DEBUG" - // 启动分析开关:监控App启动耗时,需要同时开启pageLoadSwitch - startSwitch = true - // 页面响应开关:监控Activity的生命周期耗时 - pageLoadSwitch = true - // 网络监控开关:监控okhttp3的网络请求 - okHttp3Switch = true - // 白名单下的包进行插桩,需要填写要插装类所在的包名,支持前缀配置 - whiteList = [ - "com.mogo" - ] - // 黑名单包下类不进行插桩,可以配置包名和类名,没有可以填空 - blackList = [ +if (!isAndroidTestBuild()) { + ApmPlugin { + // 是否进行插桩 + enable true + // 是否在Debug包插桩,默认不插桩 + enableInDebug true + // DEBUG("DEBUG"), INFO("INFO"), WARN("WARN"), ERROR("ERROR"); + // INFO 级别Log会汇总所有被插桩处理的类供查看,路径 app/build/ByteX/ApmPlugin + logLevel "DEBUG" + // 启动分析开关:监控App启动耗时,需要同时开启pageLoadSwitch + startSwitch = true + // 页面响应开关:监控Activity的生命周期耗时 + pageLoadSwitch = true + // 网络监控开关:监控okhttp3的网络请求 + okHttp3Switch = true + // 白名单下的包进行插桩,需要填写要插装类所在的包名,支持前缀配置 + whiteList = [ + "com.mogo" + ] + // 黑名单包下类不进行插桩,可以配置包名和类名,没有可以填空 + blackList = [ - ] + ] + } } android.applicationVariants.all { variant -> @@ -305,3 +328,13 @@ def getWorkingBranchHash() { println "Working branch hash: " + workingBranchHash return workingBranchHash } + + +boolean isAndroidTestBuild() { + for (String s : gradle.startParameter.taskNames) { + if (s.contains("AndroidTest")) { + return true + } + } + return false +} diff --git a/app/src/androidTest/java/com/mogo/functions/test/AutoPilotBadCaseTest.kt b/app/src/androidTest/java/com/mogo/functions/test/AutoPilotBadCaseTest.kt new file mode 100644 index 0000000000..8657b3107e --- /dev/null +++ b/app/src/androidTest/java/com/mogo/functions/test/AutoPilotBadCaseTest.kt @@ -0,0 +1,145 @@ +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.main.MainLauncherActivity +import kotlinx.coroutines.* +import kotlinx.coroutines.flow.* +import org.junit.Before +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 + +@RunWith(AndroidJUnit4::class) +@LargeTest +class AutoPilotBadCaseTest { + + lateinit var launch: ActivityScenario + + @Before + fun launch() { + launch = ActivityScenario.launch(MainLauncherActivity::class.java) + } + + @ExperimentalCoroutinesApi @Test + fun showBadCaseEntrance1(): Unit = runBlocking(Dispatchers.Main) { + val f = ensureMoGoHmiFragmentShow() + var index = 0 + (1 until 50) + .map { it } + .asFlow() + .onEach { + delay(TimeUnit.SECONDS.toMillis(5)) + f.onAutopilotRecordResult(AutoPilotRecordResult().also { + it.diskFree = 100 + index + it.duration = 60.0 + it.fileName = "/user/general/record_$index.log" + it.id = 10 + index + it.key = "yyy_$index" + it.stat = 100 + it.type = 1 + it.timestamp = SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(Date()) + index++ + }) + } + .flowOn(Dispatchers.Default) + .collect() + delay(TimeUnit.HOURS.toMillis(2)) + } + + + @ExperimentalCoroutinesApi @Test + fun showBadCaseEntrance2():Unit = runBlocking(Dispatchers.Main) { + val f = ensureMoGoHmiFragmentShow() + var index = 0 + (1 until 50) + .map { it } + .asFlow() + .onEach { + if (index in 1..4) { + delay(TimeUnit.SECONDS.toMillis(15)) + } else { + delay(Random(20).nextLong()) + } + f.onAutopilotRecordResult(AutoPilotRecordResult().also { + it.diskFree = 100 + index + it.duration = 60.0 + it.fileName = "/user/general/record_$index.log" + it.id = 10 + index + it.key = "yyy_$index" + it.stat = 100 + it.type = 1 + it.timestamp = SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(Date()) + index++ + }) + + if (index == 5) { + f.lifecycleScope.cancel() + } + + } + .flowOn(Dispatchers.Default) + .collect() + delay(TimeUnit.HOURS.toMillis(2)) + } + + @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 { + it.diskFree = 100 + index + it.duration = 60.0 + it.fileName = "/user/general/record_$index.log" + it.id = 10 + index + it.key = "yyy_$index" + it.stat = 100 + it.type = 1 + it.timestamp = SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(Date()) + index++ + }) + } + .flowOn(Dispatchers.Default) + .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) + } + } +} \ No newline at end of file diff --git a/config.gradle b/config.gradle index 38732a1ecf..983be9703a 100644 --- a/config.gradle +++ b/config.gradle @@ -246,7 +246,16 @@ ext { life_cycle_scope : "androidx.lifecycle:lifecycle-runtime-ktx:2.2.0", view_model_scope : "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0", - live_data_scope : "androidx.lifecycle:lifecycle-livedata-ktx:2.2.0" + live_data_scope : "androidx.lifecycle:lifecycle-livedata-ktx:2.2.0", + + + //========================== Unit Test ====================== + androidx_test_core : "androidx.test:core:1.2.1", + androidx_test_core_ktx : "androidx.test:core-ktx:1.2.0", + androidx_unit_ext : "androidx.test.ext:junit:1.1.2", + androidx_unit_ext_ktx : "androidx.test.ext:junit-ktx:1.1.2", + androidx_runner : "androidx.test:runner:1.3.0", + androidx_espresso_core : "androidx.test.espresso:espresso-core:3.3.0", ] } \ No newline at end of file diff --git a/core/function-impl/mogo-core-function-autopilot/src/main/java/com/mogo/eagle/core/function/autopilot/MoGoAutopilotProvider.kt b/core/function-impl/mogo-core-function-autopilot/src/main/java/com/mogo/eagle/core/function/autopilot/MoGoAutopilotProvider.kt index 7d06609d60..5f722bd33e 100644 --- a/core/function-impl/mogo-core-function-autopilot/src/main/java/com/mogo/eagle/core/function/autopilot/MoGoAutopilotProvider.kt +++ b/core/function-impl/mogo-core-function-autopilot/src/main/java/com/mogo/eagle/core/function/autopilot/MoGoAutopilotProvider.kt @@ -109,4 +109,8 @@ class MoGoAutopilotProvider : override fun setIPCReboot() { AdasManager.getInstance().rebootIPC() } + + override fun recordCause(key: String?, name: String?, reason: String?) { + AdasManager.getInstance().recordCause(key, name, reason) + } } \ No newline at end of file diff --git a/core/function-impl/mogo-core-function-hmi/src/main/java/com/mogo/eagle/core/function/hmi/notification/WarningFloat.kt b/core/function-impl/mogo-core-function-hmi/src/main/java/com/mogo/eagle/core/function/hmi/notification/WarningFloat.kt index 158e2c132d..dd0750ac92 100644 --- a/core/function-impl/mogo-core-function-hmi/src/main/java/com/mogo/eagle/core/function/hmi/notification/WarningFloat.kt +++ b/core/function-impl/mogo-core-function-hmi/src/main/java/com/mogo/eagle/core/function/hmi/notification/WarningFloat.kt @@ -147,6 +147,14 @@ class WarningFloat { } } + fun setWindowWidth(width: Int) = apply { + this.config.width = width + } + + fun setWindowHeight(height: Int) = apply { + this.config.height = height + } + /** * 创建浮窗,包括Activity浮窗和系统浮窗,如若系统浮窗无权限,先进行权限申请 */ diff --git a/core/function-impl/mogo-core-function-hmi/src/main/java/com/mogo/eagle/core/function/hmi/notification/WarningFloatWindowHelper.kt b/core/function-impl/mogo-core-function-hmi/src/main/java/com/mogo/eagle/core/function/hmi/notification/WarningFloatWindowHelper.kt index 20c9b90796..639df0470b 100644 --- a/core/function-impl/mogo-core-function-hmi/src/main/java/com/mogo/eagle/core/function/hmi/notification/WarningFloatWindowHelper.kt +++ b/core/function-impl/mogo-core-function-hmi/src/main/java/com/mogo/eagle/core/function/hmi/notification/WarningFloatWindowHelper.kt @@ -65,8 +65,8 @@ internal class WarningFloatWindowHelper( // 没有边界限制,允许窗口扩展到屏幕外 WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS else WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL - width = WindowManager.LayoutParams.WRAP_CONTENT - height = WindowManager.LayoutParams.WRAP_CONTENT + width = config.width + height = config.height // 如若设置了固定坐标,直接定位 if (config.locationPair != Pair(0, 0)) { diff --git a/core/function-impl/mogo-core-function-hmi/src/main/java/com/mogo/eagle/core/function/hmi/notification/WarningNotificationConfig.kt b/core/function-impl/mogo-core-function-hmi/src/main/java/com/mogo/eagle/core/function/hmi/notification/WarningNotificationConfig.kt index edb0f3e1d1..dcf1151adb 100644 --- a/core/function-impl/mogo-core-function-hmi/src/main/java/com/mogo/eagle/core/function/hmi/notification/WarningNotificationConfig.kt +++ b/core/function-impl/mogo-core-function-hmi/src/main/java/com/mogo/eagle/core/function/hmi/notification/WarningNotificationConfig.kt @@ -1,6 +1,7 @@ package com.mogo.eagle.core.function.hmi.notification import android.view.View +import android.view.WindowManager import com.mogo.eagle.core.function.hmi.notification.anim.DefaultAnimator import com.mogo.eagle.core.function.hmi.notification.enums.ShowPattern import com.mogo.eagle.core.function.hmi.notification.enums.SidePattern @@ -59,4 +60,9 @@ data class WarningNotificationConfig( // Callbacks var callbacks: OnFloatCallbacks? = null, + // 窗口宽度 + var width: Int = WindowManager.LayoutParams.WRAP_CONTENT, + + // 窗口高度 + var height: Int = WindowManager.LayoutParams.WRAP_CONTENT ) \ No newline at end of file diff --git a/core/function-impl/mogo-core-function-hmi/src/main/java/com/mogo/eagle/core/function/hmi/ui/MoGoHmiFragment.kt b/core/function-impl/mogo-core-function-hmi/src/main/java/com/mogo/eagle/core/function/hmi/ui/MoGoHmiFragment.kt index 658e7f23a9..adb070d169 100644 --- a/core/function-impl/mogo-core-function-hmi/src/main/java/com/mogo/eagle/core/function/hmi/ui/MoGoHmiFragment.kt +++ b/core/function-impl/mogo-core-function-hmi/src/main/java/com/mogo/eagle/core/function/hmi/ui/MoGoHmiFragment.kt @@ -1,23 +1,30 @@ 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.WindowManager +import android.view.* import android.view.animation.OvershootInterpolator +import androidx.annotation.VisibleForTesting +import androidx.lifecycle.lifecycleScope 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.AutoPilotRecordResult import com.mogo.eagle.core.data.camera.CameraEntity import com.mogo.eagle.core.data.config.HmiBuildConfig import com.mogo.eagle.core.data.constants.MoGoFragmentPaths import com.mogo.eagle.core.data.enums.WarningDirectionEnum import com.mogo.eagle.core.data.notice.NoticeNormalData 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.monitor.CallerMonitorManager import com.mogo.eagle.core.function.hmi.R @@ -30,12 +37,21 @@ 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.post import com.mogo.eagle.core.function.hmi.ui.widget.V2XNotificationView +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.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 @@ -45,7 +61,7 @@ import kotlinx.android.synthetic.main.fragment_hmi.* @Route(path = MoGoFragmentPaths.PATH_FRAGMENT_HMI) class MoGoHmiFragment : MvpFragment(), IMoGoWaringProvider, - MoGoWarningContract.View { + MoGoWarningContract.View, IMoGoAutopilotIdentifyListener { private val TAG = "MoGoHmiFragment" // DebugSettingView @@ -64,9 +80,106 @@ class MoGoHmiFragment : MvpFragment private var toolsView: AutoPilotAndCheckView? = null + private var autoPilotToolsFloat: WarningFloat.Builder? = null + // 检测、自动驾驶速度设置 private var toolsViewFloat: WarningFloat.Builder? = null + @Volatile + private var autoPilotBadCaseEntrance: View? = null + + private var autoPilotBadCaseView: AutoPilotBadCaseView? = null + + companion object { + private const val MSG_WHAT_DISMISS_BAD_CASE_ENTRY = 0x1010 + private val DURATION_FOR_DISMISS = TimeUnit.SECONDS.toMillis(10) + } + + @ExperimentalCoroutinesApi + private val channel by lazy { + Channel(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) { + Logger.d(TAG, "-- step -- 1 --") + var oldT = try { + old?.timestamp?.takeIf { it.isNotBlank() }?.let { + SimpleDateFormat("yyyy-MM-dd-HH-mm-ss", 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("yyyy-MM-dd-HH-mm-ss", Locale.getDefault()).parse(it)?.time + ?: 0L + } ?: 0L + } catch (t: Throwable) { + t.printStackTrace() + 0L + } + + if (oldT == 0L || (newT > 0L && (newT - oldT > 0L) && (newT - oldT) < DURATION_FOR_DISMISS)) { + Logger.d(TAG, "-- step -- 2 --") + record?.takeIf { it.key != old?.key && it.timestamp != old?.timestamp }?.also { + Logger.d(TAG, "record: [$record] is displaying and consuming ~~~" ) + showBadCaseEntrance(it) + } + continue + } + + while (oldT != 0L && newT != 0L && (newT - oldT) >= DURATION_FOR_DISMISS) { + Logger.d(TAG, "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("yyyy-MM-dd-HH-mm-ss", Locale.getDefault()).parse(it)?.time ?: 0L + } ?: 0L + } catch (t: Throwable) { + t.printStackTrace() + 0L + } + } + record?.takeIf { it.key != old?.key && it.timestamp != old?.timestamp }?.also { + Logger.d(TAG, "record: [$record] is displaying for rest ..." ) + showBadCaseEntrance(it) + } + } else { + Logger.d(TAG, "record: [$old] hasn't been consumed~~~~" ) + } + } finally { + delay(1000) + } + } + } + } + } + } + + private val handler by lazy { + Handler(Handler.Callback { + if (it.what == MSG_WHAT_DISMISS_BAD_CASE_ENTRY) { + val entrance = autoPilotBadCaseEntrance + if (entrance != null && entrance.visibility == View.VISIBLE) { + Logger.d(TAG, "${TimeUnit.MILLISECONDS.toMinutes(DURATION_FOR_DISMISS)}分种后BadCase入口消失") + (entrance.getTag(R.id.autopilot_badcase_record) as? AutoPilotRecordResult)?.let { itx -> + itx.consumed = true + } + entrance.visibility = View.GONE + } + return@Callback true + } + return@Callback false + }) + } override fun vipIdentification(visible: Boolean) { ThreadUtils.runOnUiThread { @@ -138,6 +251,155 @@ class MoGoHmiFragment : MvpFragment } } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + CallerAutopilotIdentifyListenerManager.addListener(TAG, this) + } + + @ExperimentalCoroutinesApi + override fun onAutopilotRecordResult(record: AutoPilotRecordResult?) { + record ?: return + Logger.d(TAG, "onAutopilotRecordResult:$record") + if (record.type == 1 && record.stat == 100) { + lifecycleScope.launchWhenResumed { + channel.send(record) + } + } + } + + override fun onDestroyView() { + super.onDestroyView() + CallerAutopilotIdentifyListenerManager.removeListener(TAG) + } + + @VisibleForTesting + fun showBadCaseEntrance(record: AutoPilotRecordResult) { + Logger.d(TAG, "showBadCaseEntrance:$record") + lifecycleScope.launch { + if (vs_bad_case_entrance?.parent != null) { + val inflateView = vs_bad_case_entrance.inflate() + autoPilotBadCaseEntrance = inflateView + } + val entrance = autoPilotBadCaseEntrance + if (entrance != null) { + if (entrance.visibility != View.VISIBLE) { + entrance.visibility = View.VISIBLE + } + entrance.setTag(R.id.autopilot_badcase_record, record) + entrance.onClick { + showBadCasesFloat { + it.visibility = View.GONE + } + } + dismissBadCaseEntryAfterSomeTime() + } + } + } + + private fun showBadCasesFloat(dismiss: (() -> Unit)?) { + Logger.d(TAG, "showBadCaseToolsFloat") + context?.let { it -> + if (autoPilotToolsFloat == null) { + if (autoPilotBadCaseView == null) { + autoPilotBadCaseView = AutoPilotBadCaseView(it).also { itx -> + itx.onDismiss { + val record = + autoPilotBadCaseEntrance?.getTag(R.id.autopilot_badcase_record) as? AutoPilotRecordResult + CallerAutoPilotManager.recordCause(record?.key, record?.fileName, null) + dismissBadCaseFloatView() + } + itx.onSelect { + lifecycleScope.launch { + val record = + autoPilotBadCaseEntrance?.getTag(R.id.autopilot_badcase_record) as? AutoPilotRecordResult + try { + val params = mutableMapOf() + 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["timestamp"] = System.currentTimeMillis().toString() + } + val response = post(params) + if (response.isSuccessful) { + val body = response.body() + if (body == null) { + Logger.e(TAG, "返回的body是空的~~~") + return@launch + } + if (body.code == 200) { + Logger.i(TAG, "ok:${body}") + dismissBadCaseFloatView() + dismiss?.invoke() + ToastUtils.showShort("接管反馈成功~") + return@launch + } + Logger.e(TAG, "fail:${body}") + } + } catch (t: Throwable) { + t.printStackTrace() + ToastUtils.showShort("网络请求失败,请尝试联网~") + Logger.e(TAG, "exception:${t.message}") + } finally { + record?.consumed = true + CallerAutoPilotManager.recordCause( + record?.key, + record?.fileName, + it.id + ) + } + } + } + } + } + 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, DURATION_FOR_DISMISS) + } + private fun showToolsFloat() { Logger.d(TAG, "showToolsFloat") context?.let { @@ -696,9 +958,18 @@ class MoGoHmiFragment : MvpFragment // toolsView?.setStatus(true) } + private fun dismissBadCaseFloatView() { + autoPilotToolsFloat?.let { + WarningFloat.dismiss(it.config.floatTag, false) + autoPilotToolsFloat = null + autoPilotBadCaseView = null + } + } + override fun onDestroy() { super.onDestroy() Log.d(TAG, "onDestroy") + handler.removeCallbacksAndMessages(null) } diff --git a/core/function-impl/mogo-core-function-hmi/src/main/java/com/mogo/eagle/core/function/hmi/ui/tools/AutoPilotBadCase.kt b/core/function-impl/mogo-core-function-hmi/src/main/java/com/mogo/eagle/core/function/hmi/ui/tools/AutoPilotBadCase.kt new file mode 100644 index 0000000000..765c0210d3 --- /dev/null +++ b/core/function-impl/mogo-core-function-hmi/src/main/java/com/mogo/eagle/core/function/hmi/ui/tools/AutoPilotBadCase.kt @@ -0,0 +1,338 @@ +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 com.google.gson.annotations.Expose +import com.mogo.commons.debug.DebugConfig +import com.mogo.commons.debug.DebugConfig.getNetMode +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.RetrofitFactory +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 kotlin.Result.Companion.failure +import kotlin.Result.Companion.success + + +private typealias OnDismissCallback = () -> Unit +private typealias OnSelectCallback = (Reason) -> Unit + +interface BadCaseApi { + + @FormUrlEncoded + @POST("/yycp-vehicle-management-service/tool/badcase/add") + suspend fun post(@FieldMap map: Map): Response + + @GET("/yycp-vehicle-management-service/tool/badcase/reasons") + suspend fun get(): Response +} + +@Keep +class BadCaseEntity { + var code: Int = -1 + var data: List? = 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? = 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" */"http://front.zdjs-private-test.myghost.zhidaoauto.com" else "http://front.zdjs-private-test.myghost.zhidaoauto.com" + +internal suspend fun post(map: Map): Response { + return RetrofitFactory.getInstance(getHost()).create(BadCaseApi::class.java).post(map) +} + +private suspend fun get(): Response? { + return try { RetrofitFactory.getInstance(getHost()).create(BadCaseApi::class.java).get() } catch (t: Throwable) { t.printStackTrace(); null} +} + +private suspend fun updateCache(entity: BadCaseEntity) = suspendCancellableCoroutine { + 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() + data += Reason().also { + it.reason = "变道有干扰" + } + data += Reason().also { + it.reason = "遇红绿灯未停车" + } + data += Reason().also { + 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? = 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 + } + 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() { + 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 + } +} \ No newline at end of file diff --git a/core/function-impl/mogo-core-function-hmi/src/main/res/drawable-xxhdpi/icon_ap_badcase_check.png b/core/function-impl/mogo-core-function-hmi/src/main/res/drawable-xxhdpi/icon_ap_badcase_check.png new file mode 100644 index 0000000000..3f05565483 Binary files /dev/null and b/core/function-impl/mogo-core-function-hmi/src/main/res/drawable-xxhdpi/icon_ap_badcase_check.png differ diff --git a/core/function-impl/mogo-core-function-hmi/src/main/res/drawable-xxhdpi/icon_ap_badcase_default.png b/core/function-impl/mogo-core-function-hmi/src/main/res/drawable-xxhdpi/icon_ap_badcase_default.png new file mode 100644 index 0000000000..0114bb4f2b Binary files /dev/null and b/core/function-impl/mogo-core-function-hmi/src/main/res/drawable-xxhdpi/icon_ap_badcase_default.png differ diff --git a/core/function-impl/mogo-core-function-hmi/src/main/res/drawable-xxhdpi/icon_car_ap_badcase_entrance.png b/core/function-impl/mogo-core-function-hmi/src/main/res/drawable-xxhdpi/icon_car_ap_badcase_entrance.png new file mode 100644 index 0000000000..89a6eaa5dc Binary files /dev/null and b/core/function-impl/mogo-core-function-hmi/src/main/res/drawable-xxhdpi/icon_car_ap_badcase_entrance.png differ diff --git a/core/function-impl/mogo-core-function-hmi/src/main/res/layout/fragment_hmi.xml b/core/function-impl/mogo-core-function-hmi/src/main/res/layout/fragment_hmi.xml index b4e0c7aabf..1e207d28c8 100644 --- a/core/function-impl/mogo-core-function-hmi/src/main/res/layout/fragment_hmi.xml +++ b/core/function-impl/mogo-core-function-hmi/src/main/res/layout/fragment_hmi.xml @@ -97,6 +97,18 @@ app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toEndOf="@+id/viewPerspectiveSwitch" app:layout_goneMarginStart="50px" /> + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/core/function-impl/mogo-core-function-hmi/src/main/res/layout/layout_badcase_item.xml b/core/function-impl/mogo-core-function-hmi/src/main/res/layout/layout_badcase_item.xml new file mode 100644 index 0000000000..d8bbe22a3b --- /dev/null +++ b/core/function-impl/mogo-core-function-hmi/src/main/res/layout/layout_badcase_item.xml @@ -0,0 +1,28 @@ + + + + + + + \ No newline at end of file diff --git a/core/function-impl/mogo-core-function-hmi/src/main/res/layout/view_ap_badcase_entrance.xml b/core/function-impl/mogo-core-function-hmi/src/main/res/layout/view_ap_badcase_entrance.xml new file mode 100644 index 0000000000..d77bcfe7a3 --- /dev/null +++ b/core/function-impl/mogo-core-function-hmi/src/main/res/layout/view_ap_badcase_entrance.xml @@ -0,0 +1,11 @@ + + \ No newline at end of file diff --git a/core/function-impl/mogo-core-function-hmi/src/main/res/values/ids.xml b/core/function-impl/mogo-core-function-hmi/src/main/res/values/ids.xml new file mode 100644 index 0000000000..df10b924c5 --- /dev/null +++ b/core/function-impl/mogo-core-function-hmi/src/main/res/values/ids.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/core/mogo-core-data/src/main/java/com/mogo/eagle/core/data/autopilot/AutoPilotRecord.kt b/core/mogo-core-data/src/main/java/com/mogo/eagle/core/data/autopilot/AutoPilotRecord.kt new file mode 100644 index 0000000000..1ebe30b93e --- /dev/null +++ b/core/mogo-core-data/src/main/java/com/mogo/eagle/core/data/autopilot/AutoPilotRecord.kt @@ -0,0 +1,80 @@ +package com.mogo.eagle.core.data.autopilot + + + + +class AutoPilotRecordResult { + + /** + * 磁盘可用空间(M) + */ + var diskFree: Int = 0 + + /** + * 采集时长 + */ + var duration: Double = 0.0 + + + /** + * 保存的文件名 + */ + 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 + */ + var timestamp: String? = "" + + /** + * 此次采集数据总大小(M) + */ + var total: Int? = 0 + + + /** + * 记录此条数据是否已消费 + */ + + @Volatile + var consumed: Boolean = false + + + override fun toString(): String { + return "AutoPilotRecordResult(diskFree=$diskFree, duration=$duration, fileName=$fileName, note=$note, key=$key, stat=$stat, type=$type, id=$id, timestamp=$timestamp, total=$total)" + } +} \ No newline at end of file diff --git a/core/mogo-core-function-api/src/main/java/com/mogo/eagle/core/function/api/autopilot/IMoGoAutopilotIdentifyListener.kt b/core/mogo-core-function-api/src/main/java/com/mogo/eagle/core/function/api/autopilot/IMoGoAutopilotIdentifyListener.kt index 2b9c19a019..4a76aa4c9e 100644 --- a/core/mogo-core-function-api/src/main/java/com/mogo/eagle/core/function/api/autopilot/IMoGoAutopilotIdentifyListener.kt +++ b/core/mogo-core-function-api/src/main/java/com/mogo/eagle/core/function/api/autopilot/IMoGoAutopilotIdentifyListener.kt @@ -1,5 +1,6 @@ package com.mogo.eagle.core.function.api.autopilot +import com.mogo.eagle.core.data.autopilot.AutoPilotRecordResult import com.mogo.eagle.core.data.autopilot.AutopilotWarnMessage import com.mogo.eagle.core.data.traffic.TrafficData import java.util.* @@ -16,13 +17,19 @@ interface IMoGoAutopilotIdentifyListener { * * @param trafficData 交通元素信息列表 */ - fun onAutopilotIdentifyDataUpdate(trafficData: ArrayList?) + fun onAutopilotIdentifyDataUpdate(trafficData: ArrayList?) {} /** * 报警信息 * * @param autopilotWarnMessage 预警信息 */ - fun onAutopilotWarnMessage(autopilotWarnMessage: AutopilotWarnMessage?) + fun onAutopilotWarnMessage(autopilotWarnMessage: AutopilotWarnMessage?) {} + + + /** + * 采集结果回调 + */ + fun onAutopilotRecordResult(record: AutoPilotRecordResult?) {} } \ No newline at end of file diff --git a/core/mogo-core-function-api/src/main/java/com/mogo/eagle/core/function/api/autopilot/IMoGoAutopilotProvider.java b/core/mogo-core-function-api/src/main/java/com/mogo/eagle/core/function/api/autopilot/IMoGoAutopilotProvider.java index d0e3859817..959f35bc3a 100644 --- a/core/mogo-core-function-api/src/main/java/com/mogo/eagle/core/function/api/autopilot/IMoGoAutopilotProvider.java +++ b/core/mogo-core-function-api/src/main/java/com/mogo/eagle/core/function/api/autopilot/IMoGoAutopilotProvider.java @@ -60,6 +60,14 @@ public interface IMoGoAutopilotProvider extends IMoGoFunctionServerProvider { Boolean setAutoPilotSpeed(int speed); + /** + * 记录各种失败 + * @param key 采集任务的标识 + * @param name 文件名字 + * @param reason 原因 + */ + void recordCause(String key, String name, String reason); + /** * 关机 */ diff --git a/core/mogo-core-function-call/src/main/java/com/mogo/eagle/core/function/call/autopilot/CallerAutoPilotManager.kt b/core/mogo-core-function-call/src/main/java/com/mogo/eagle/core/function/call/autopilot/CallerAutoPilotManager.kt index 6be446c1e9..d5287ebc56 100644 --- a/core/mogo-core-function-call/src/main/java/com/mogo/eagle/core/function/call/autopilot/CallerAutoPilotManager.kt +++ b/core/mogo-core-function-call/src/main/java/com/mogo/eagle/core/function/call/autopilot/CallerAutoPilotManager.kt @@ -13,7 +13,7 @@ import com.mogo.eagle.core.function.call.base.CallerBase object CallerAutoPilotManager { private val TAG = "CallerAutoPilotManager" - private val providerApi: IMoGoAutopilotProvider + private val providerApi: IMoGoAutopilotProvider? get() = CallerBase.getApiInstance( IMoGoAutopilotProvider::class.java, MogoServicePaths.PATH_AUTO_PILOT @@ -25,7 +25,7 @@ object CallerAutoPilotManager { * @param autoPilotIp 指定与控制器IP */ fun resetIpAddress(autoPilotIp: String) { - providerApi.resetIpAddress(autoPilotIp) + providerApi?.resetIpAddress(autoPilotIp) } /** @@ -38,7 +38,7 @@ object CallerAutoPilotManager { //LogUtils.eTag(TAG, "自动驾驶控制参数异常,请检查参数信息") return } - providerApi.startAutoPilot(controlParameters) + providerApi?.startAutoPilot(controlParameters) } /** @@ -46,40 +46,44 @@ object CallerAutoPilotManager { * 具体的json格式需要与@宋克难 进行沟通 */ fun sendDataToAutopilot(jsonString: String) { - providerApi.sendMessageToAutopilot(jsonString) + providerApi?.sendMessageToAutopilot(jsonString) } /** * 结束自动驾驶 */ fun cancelAutoPilot() { - providerApi.cancelAutoPilot() + providerApi?.cancelAutoPilot() } /** * 开启域控制器录制bag包 */ fun recordPackage() { - providerApi.recordPackage() + providerApi?.recordPackage() } fun setEnableLog(isEnableLog: Boolean) { - providerApi.setEnableLog(isEnableLog) + providerApi?.setEnableLog(isEnableLog) } fun setIsWriteLog(isWriteLog: Boolean) { - providerApi.setIsWriteLog(isWriteLog) + providerApi?.setIsWriteLog(isWriteLog) } fun setAutoPilotSpeed(speed: Int): Boolean { - return providerApi.setAutoPilotSpeed(speed) + return providerApi?.setAutoPilotSpeed(speed) ?: false + } + + fun recordCause(key: String?, name: String?, reason: String?) { + providerApi?.recordCause(key, name, reason) } fun setIPCShutDown() { - providerApi.setIPCShutDown() + providerApi?.setIPCShutDown() } fun setIPCReboot() { - providerApi.setIPCReboot() + providerApi?.setIPCReboot() } } \ No newline at end of file diff --git a/core/mogo-core-function-call/src/main/java/com/mogo/eagle/core/function/call/autopilot/CallerAutoPilotStatusListenerManager.kt b/core/mogo-core-function-call/src/main/java/com/mogo/eagle/core/function/call/autopilot/CallerAutoPilotStatusListenerManager.kt index 33aac6f3e7..b76a5ee25f 100644 --- a/core/mogo-core-function-call/src/main/java/com/mogo/eagle/core/function/call/autopilot/CallerAutoPilotStatusListenerManager.kt +++ b/core/mogo-core-function-call/src/main/java/com/mogo/eagle/core/function/call/autopilot/CallerAutoPilotStatusListenerManager.kt @@ -149,4 +149,5 @@ object CallerAutoPilotStatusListenerManager : CallerBase() { } } + } \ No newline at end of file diff --git a/core/mogo-core-function-call/src/main/java/com/mogo/eagle/core/function/call/autopilot/CallerAutopilotIdentifyListenerManager.kt b/core/mogo-core-function-call/src/main/java/com/mogo/eagle/core/function/call/autopilot/CallerAutopilotIdentifyListenerManager.kt index f474e8bb81..d0c0fa9514 100644 --- a/core/mogo-core-function-call/src/main/java/com/mogo/eagle/core/function/call/autopilot/CallerAutopilotIdentifyListenerManager.kt +++ b/core/mogo-core-function-call/src/main/java/com/mogo/eagle/core/function/call/autopilot/CallerAutopilotIdentifyListenerManager.kt @@ -1,6 +1,7 @@ package com.mogo.eagle.core.function.call.autopilot import androidx.annotation.Nullable +import com.mogo.eagle.core.data.autopilot.AutoPilotRecordResult import com.mogo.eagle.core.data.autopilot.AutopilotWarnMessage import com.mogo.eagle.core.data.traffic.TrafficData import com.mogo.eagle.core.function.api.autopilot.IMoGoAutopilotIdentifyListener @@ -89,4 +90,17 @@ object CallerAutopilotIdentifyListenerManager : CallerBase() { } } + /** + * 采集任务记录回调 + */ + fun invokeAutopilotRecordResult(result: AutoPilotRecordResult) { + M_AUTOPILOT_IDENTIFY_LISTENERS.forEach { + val tag = it.key + val listener = it.value + LogUtils.dTag(TAG, "tag:$tag listener:$listener") + listener.onAutopilotRecordResult(result) + } + } + + } \ No newline at end of file diff --git a/core/mogo-core-utils/src/main/java/com/mogo/eagle/core/utilcode/kotlin/ExtensionKt.kt b/core/mogo-core-utils/src/main/java/com/mogo/eagle/core/utilcode/kotlin/ExtensionKt.kt new file mode 100644 index 0000000000..8d6e851291 --- /dev/null +++ b/core/mogo-core-utils/src/main/java/com/mogo/eagle/core/utilcode/kotlin/ExtensionKt.kt @@ -0,0 +1,48 @@ +package com.mogo.eagle.core.utilcode.kotlin + +import android.content.Context +import android.graphics.Color +import android.graphics.drawable.Drawable +import android.graphics.drawable.GradientDrawable +import android.util.TypedValue +import android.view.View +import androidx.annotation.ColorInt +import androidx.annotation.IntRange +import com.mogo.eagle.core.utilcode.util.ClickUtils +import com.mogo.eagle.core.utilcode.util.Utils +import java.util.* + + +fun View.onClick(block: (View) -> Unit) { + this.setOnClickListener { + if (ClickUtils.isClickTooFrequent(this)) { + return@setOnClickListener + } + block(it) + } +} + +fun shape(@ColorInt solid: Int = Color.TRANSPARENT, @ColorInt stroke: Int = Color.TRANSPARENT, @IntRange(from = 0) strokeWidth: Int = 0, @IntRange(from = 0) radius: Int = 0, radii: FloatArray = FloatArray(8).apply { Arrays.fill(this, radius.toFloat()) }, shape: Int = GradientDrawable.RECTANGLE): Drawable { + val drawable = GradientDrawable() + drawable.shape = shape + drawable.gradientType = GradientDrawable.LINEAR_GRADIENT + drawable.setColor(solid) + drawable.cornerRadii = radii + drawable.setStroke(strokeWidth, stroke) + return drawable +} + +fun gradient(shape: Int = GradientDrawable.RECTANGLE, @IntRange(from = 0) radius: Int = 0, radii: FloatArray = FloatArray(8).apply { Arrays.fill(this, radius.toFloat()) }, gradientType: Int = GradientDrawable.LINEAR_GRADIENT, orientation: GradientDrawable.Orientation = GradientDrawable.Orientation.TOP_BOTTOM, centerX: Float = 0.5f, centerY: Float = 0.5f, @ColorInt startColor: Int, @ColorInt centerColor: Int = startColor, @ColorInt endColor: Int): Drawable{ + val drawable = GradientDrawable(orientation, intArrayOf(endColor, centerColor, startColor)) + drawable.shape = shape + drawable.gradientType = gradientType + drawable.orientation = orientation + drawable.cornerRadii = radii + drawable.setGradientCenter(centerX, centerY) + return drawable +} + +fun Int.toPixels(context: Context? = null): Float { + val ctx = context ?: Utils.getApp() + return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, this.toFloat(), ctx.resources.displayMetrics) +} diff --git a/core/mogo-core-utils/src/main/java/com/mogo/eagle/core/utilcode/util/ClickUtils.java b/core/mogo-core-utils/src/main/java/com/mogo/eagle/core/utilcode/util/ClickUtils.java index 4eef7e8b2e..1e44253913 100644 --- a/core/mogo-core-utils/src/main/java/com/mogo/eagle/core/utilcode/util/ClickUtils.java +++ b/core/mogo-core-utils/src/main/java/com/mogo/eagle/core/utilcode/util/ClickUtils.java @@ -618,4 +618,25 @@ public class ClickUtils { mBitmapDrawable.draw(canvas); } } + + + public static boolean isClickTooFrequent(View view) { + return isClickTooFrequent(view, 500); + } + + public static boolean isClickTooFrequent(View view, int duration) { + try { + Object tag = view.getTag(R.id.tag_click_time); + long past = tag == null ? 0L : (Long)tag; + long now = System.currentTimeMillis(); + if (Math.abs(now - past) < (long)duration) { + return true; + } + + view.setTag(R.id.tag_click_time, now); + } catch (Exception var7) { + } + + return false; + } } diff --git a/core/mogo-core-utils/src/main/res/values/ids.xml b/core/mogo-core-utils/src/main/res/values/ids.xml new file mode 100644 index 0000000000..7f82523441 --- /dev/null +++ b/core/mogo-core-utils/src/main/res/values/ids.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/modules/mogo-module-adas/src/main/java/com/mogo/module/adas/AdasEventManager.java b/modules/mogo-module-adas/src/main/java/com/mogo/module/adas/AdasEventManager.java index 9f42fd98e2..e14af8f05f 100644 --- a/modules/mogo-module-adas/src/main/java/com/mogo/module/adas/AdasEventManager.java +++ b/modules/mogo-module-adas/src/main/java/com/mogo/module/adas/AdasEventManager.java @@ -5,6 +5,7 @@ import androidx.annotation.Nullable; import com.google.gson.Gson; import com.mogo.eagle.core.data.autopilot.ADASTrajectoryInfo; +import com.mogo.eagle.core.data.autopilot.AutoPilotRecordResult; import com.mogo.eagle.core.data.autopilot.AutopilotCarStateInfo; import com.mogo.eagle.core.data.autopilot.AutopilotGuardianStatusInfo; import com.mogo.eagle.core.data.autopilot.AutopilotRouteInfo; @@ -281,4 +282,14 @@ public class AdasEventManager implements } } + @Override + public void onAutopilotRecordResult(AutoPilotRecordResult result) { + if (result != null) { + for (IAdasDataListener listener : iAdasEventListeners) { + if (listener != null) { + listener.onAutopilotRecordResult(result); + } + } + } + } } diff --git a/modules/mogo-module-adas/src/main/java/com/mogo/module/adas/IAdasDataListener.java b/modules/mogo-module-adas/src/main/java/com/mogo/module/adas/IAdasDataListener.java index 4b3e5ed67f..b2867aebe8 100644 --- a/modules/mogo-module-adas/src/main/java/com/mogo/module/adas/IAdasDataListener.java +++ b/modules/mogo-module-adas/src/main/java/com/mogo/module/adas/IAdasDataListener.java @@ -1,6 +1,7 @@ package com.mogo.module.adas; import com.mogo.eagle.core.data.autopilot.ADASTrajectoryInfo; +import com.mogo.eagle.core.data.autopilot.AutoPilotRecordResult; import com.mogo.eagle.core.data.autopilot.AutopilotRouteInfo; import com.mogo.eagle.core.data.autopilot.AutopilotStationInfo; import com.mogo.eagle.core.data.autopilot.AutopilotStatusInfo; @@ -62,4 +63,10 @@ public interface IAdasDataListener { } + /** + * 采集任务结果回调 + * @param record 结果数据 + */ + default void onAutopilotRecordResult(AutoPilotRecordResult record) {} + } diff --git a/modules/mogo-module-adas/src/main/java/com/mogo/module/adas/OnAdasListenerAdapter.java b/modules/mogo-module-adas/src/main/java/com/mogo/module/adas/OnAdasListenerAdapter.java index c3f77737f4..e739c7b49e 100644 --- a/modules/mogo-module-adas/src/main/java/com/mogo/module/adas/OnAdasListenerAdapter.java +++ b/modules/mogo-module-adas/src/main/java/com/mogo/module/adas/OnAdasListenerAdapter.java @@ -3,6 +3,7 @@ package com.mogo.module.adas; import android.util.Log; import com.mogo.eagle.core.data.autopilot.ADASTrajectoryInfo; +import com.mogo.eagle.core.data.autopilot.AutoPilotRecordResult; import com.mogo.eagle.core.data.autopilot.AutopilotCarStateInfo; import com.mogo.eagle.core.data.autopilot.AutopilotGuardianStatusInfo; import com.mogo.eagle.core.data.autopilot.AutopilotRouteInfo; @@ -168,7 +169,20 @@ public class OnAdasListenerAdapter implements OnAdasListener { @Override public void onAutopilotRecord(AutopilotRecordResult result) { - + if (result != null) { + AutoPilotRecordResult real = new AutoPilotRecordResult(); + real.setDiskFree(result.getDiskFree()); + real.setId(result.getId()); + real.setDuration(result.getDuration()); + real.setNote(result.getNote()); + real.setTotal(result.getTotalSize()); + real.setType(result.getType()); + real.setFileName(result.getFilename()); + real.setKey(result.getKey()); + real.setStat(result.getStat()); + real.setTimestamp(result.getTimestamp()); + CallerAutopilotIdentifyListenerManager.INSTANCE.invokeAutopilotRecordResult(real); + } } @@ -199,8 +213,6 @@ public class OnAdasListenerAdapter implements OnAdasListener { } - - /** * 工控机升级状态 * @param info 工控机升级状态