diff --git a/OCH/common/common/src/main/java/com/mogo/och/common/module/wigets/OchRoundImageView.kt b/OCH/common/common/src/main/java/com/mogo/och/common/module/wigets/OchRoundImageView.kt new file mode 100644 index 0000000000..2f86b5a86f --- /dev/null +++ b/OCH/common/common/src/main/java/com/mogo/och/common/module/wigets/OchRoundImageView.kt @@ -0,0 +1,241 @@ +package com.mogo.och.common.module.wigets + +/* + * Copyright WeiLianYang + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + + +import android.content.Context +import android.graphics.* +import android.util.AttributeSet +import android.util.Log +import androidx.annotation.ColorInt +import androidx.appcompat.widget.AppCompatImageView +import com.mogo.och.common.module.R + + +/** + * author : WilliamYang + * date : 2022/9/18 14:54 + * description : 可设置 圆角、外边框 的 ImageView + * + * 使用方式: + * + * 1. 使用 riv_radius 设置4个角均为圆角,且圆角值一样 + * + * 2. 使用 riv_roundAsCircle 设置图片为圆形,使用 riv_radius 设置半径,当 riv_radius 未设置时,默认取宽高最小值的一半 + * + * 3. 使用 riv_topLeft_radius, riv_topRight_radius, riv_bottomLeft_radius, riv_bottomRight_radius 设置4个圆角 + * + * 4. 使用 riv_borderColor, riv_borderWidth 设置外边框颜色和宽度 + * + *

+ */ +class OchRoundImageView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : AppCompatImageView(context, attrs, defStyleAttr) { + + /** 绘制路径 **/ + private val path = Path() + + /** 绘制坐标 **/ + private val rectF = RectF() + + /** 圆角大小 **/ + private var radius = 0f + + /** 顶部左侧圆角大小 **/ + private var topLeftRadius = 0f + + /** 顶部右侧圆角大小 **/ + private var topRightRadius = 0f + + /** 底部左侧圆角大小 **/ + private var bottomLeftRadius = 0f + + /** 底部右侧圆角大小 **/ + private var bottomRightRadius = 0f + + /** 作为圆形图片使用 **/ + private var roundAsCircle = false + + /** 外边框颜色、宽度、画笔、路径、坐标 */ + private var borderColor = 0 + private var borderWidth = 0f + private val borderPaint: Paint? + private val borderPath = Path() + private val borderRectF = RectF() + + init { + val ta = context.obtainStyledAttributes(attrs, R.styleable.RoundImageView) + + roundAsCircle = ta.getBoolean(R.styleable.RoundImageView_riv_roundAsCircle, false) + borderColor = ta.getColor(R.styleable.RoundImageView_riv_borderColor, Color.TRANSPARENT) + borderWidth = ta.getDimension(R.styleable.RoundImageView_riv_borderWidth, 0f) + + radius = ta.getDimension(R.styleable.RoundImageView_riv_radius, 0f) + + topLeftRadius = ta.getDimension(R.styleable.RoundImageView_riv_topLeft_radius, 0f) + topRightRadius = ta.getDimension(R.styleable.RoundImageView_riv_topRight_radius, 0f) + bottomLeftRadius = ta.getDimension(R.styleable.RoundImageView_riv_bottomLeft_radius, 0f) + bottomRightRadius = ta.getDimension(R.styleable.RoundImageView_riv_bottomRight_radius, 0f) + + ta.recycle() + + borderPaint = Paint(Paint.ANTI_ALIAS_FLAG) + updateBorderPaint() + } + + override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { + super.onSizeChanged(w, h, oldw, oldh) + + // 当作为圆形图片使用,且半径未设置时,半径将取宽高最小值的一半 + if (roundAsCircle && radius <= 0f) { + radius = w.coerceAtMost(h) / 2f + } + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + // 当作为圆形图片使用,宽高值不同,取宽高的最小值作为宽和高 + val widthSize = MeasureSpec.getSize(widthMeasureSpec) + val heightSize = MeasureSpec.getSize(heightMeasureSpec) + if (roundAsCircle && widthSize > 0 && heightSize > 0 && widthSize != heightSize) { + val size = widthSize.coerceAtMost(heightSize) + setMeasuredDimension(size, size) + } else { + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + } + } + + override fun onDraw(canvas: Canvas) { + val halfBorderWidth = borderWidth / 2 + if (radius > 0 || topLeftRadius > 0 || topRightRadius > 0 || bottomLeftRadius > 0 || bottomRightRadius > 0) { + // 如果设置了圆角值 + path.reset() + borderPath.reset() + if (roundAsCircle) { + path.addCircle(radius, radius, radius, Path.Direction.CW) + } else { + if (topLeftRadius == 0f) topLeftRadius = radius + if (topRightRadius == 0f) topRightRadius = radius + if (bottomLeftRadius == 0f) bottomLeftRadius = radius + if (bottomRightRadius == 0f) bottomRightRadius = radius + + Log.d("RoundImageView", "onDraw: topLeftRadius=$topLeftRadius, topRightRadius=$topRightRadius, bottomLeftRadius=$bottomLeftRadius, bottomRightRadius=$bottomRightRadius") + + val radii = floatArrayOf( + topLeftRadius, topLeftRadius, topRightRadius, topRightRadius, + bottomRightRadius, bottomRightRadius, bottomLeftRadius, bottomLeftRadius + ) + + + + borderRectF.set( + paddingLeft.toFloat() + halfBorderWidth, paddingTop.toFloat() + halfBorderWidth, + measuredWidth.toFloat() - paddingRight - halfBorderWidth, + measuredHeight.toFloat() - paddingBottom - halfBorderWidth + ) + + borderPath.addRoundRect(borderRectF, radii, Path.Direction.CW) + + if (halfBorderWidth > 0) { + radii.forEachIndexed { index, f -> + if (f > 0) { + radii[index] = f + halfBorderWidth + } + } + } + rectF.set( + paddingLeft.toFloat(), + paddingTop.toFloat(), + measuredWidth.toFloat() - paddingRight, + measuredHeight.toFloat() - paddingBottom + ) + path.addRoundRect(rectF, radii, Path.Direction.CW) + } + + // 裁剪画布 + canvas.clipPath(path) + } + + super.onDraw(canvas) + + if (borderWidth > 0 && borderPaint != null) { + if (roundAsCircle) { + canvas.drawCircle(radius, radius, radius - borderWidth / 2, borderPaint) + } else { + canvas.drawPath(borderPath, borderPaint) + } + } + } + + private fun updateBorderPaint() { + borderPaint?.apply { + color = borderColor + strokeWidth = borderWidth + style = Paint.Style.STROKE + } + } + + /** + * @param radius 圆角大小,当 asCircle 为 true 时,值作为圆形图片的半径,如果为0,则将取宽高最小值的一半 + * @param borderWidth 外边框宽度 + * @param borderColor 外边框颜色 + * @param asCircle 作为圆形图片使用,默认 false + */ + fun setRadiusAndBorder( + radius: Float, + borderWidth: Float = 0f, + @ColorInt borderColor: Int = 0, + asCircle: Boolean = false, + ) { + this.radius = radius + this.borderWidth = borderWidth + this.borderColor = borderColor + this.roundAsCircle = asCircle + + updateBorderPaint() + } + + /** + * @param topLeftRadius 顶部左侧圆角大小 + * @param topRightRadius 顶部右侧圆角大小 + * @param bottomLeftRadius 底部左侧圆角大小 + * @param bottomRightRadius 底部右侧圆角大小 + * @param borderWidth 外边框宽度 + * @param borderColor 外边框颜色 + */ + fun setRadiusAndBorder( + topLeftRadius: Float = 0f, + topRightRadius: Float = 0f, + bottomLeftRadius: Float = 0f, + bottomRightRadius: Float = 0f, + borderWidth: Float = 0f, + @ColorInt borderColor: Int = 0 + ) { + this.topLeftRadius = topLeftRadius + this.topRightRadius = topRightRadius + this.bottomLeftRadius = bottomLeftRadius + this.bottomRightRadius = bottomRightRadius + this.borderWidth = borderWidth + this.borderColor = borderColor + + updateBorderPaint() + } + +} \ No newline at end of file diff --git a/OCH/common/common/src/main/res/values/attrs.xml b/OCH/common/common/src/main/res/values/attrs.xml index cb8a13ade6..5b122d83ed 100644 --- a/OCH/common/common/src/main/res/values/attrs.xml +++ b/OCH/common/common/src/main/res/values/attrs.xml @@ -114,4 +114,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OCH/taxi/unmanned-passenger/build.gradle b/OCH/taxi/unmanned-passenger/build.gradle index 06bcf2d47a..98611c69ab 100644 --- a/OCH/taxi/unmanned-passenger/build.gradle +++ b/OCH/taxi/unmanned-passenger/build.gradle @@ -73,6 +73,10 @@ dependencies { implementation project(":OCH:common:common") compileOnly project(":libraries:mogo-map") implementation project(':core:mogo-core-res') + + implementation project(":libraries:mogo-speech") + implementation 'io.github.youth5201314:banner:2.2.3' + // implementation project(':OCH:taxi:pcommon') if (Boolean.valueOf(USE_MAVEN_PACKAGE)) { diff --git a/OCH/taxi/unmanned-passenger/src/main/java/com/mogo/och/unmanned/passenger/ui/aiview/AIMessageManager.kt b/OCH/taxi/unmanned-passenger/src/main/java/com/mogo/och/unmanned/passenger/ui/aiview/AIMessageManager.kt new file mode 100644 index 0000000000..51fea51056 --- /dev/null +++ b/OCH/taxi/unmanned-passenger/src/main/java/com/mogo/och/unmanned/passenger/ui/aiview/AIMessageManager.kt @@ -0,0 +1,56 @@ +package com.mogo.och.unmanned.passenger.ui.aiview + +import com.mogo.och.unmanned.passenger.ui.aiview.bean.AIMessage +import java.util.concurrent.CopyOnWriteArrayList + + +object AIMessageManager { + + // 使用 CopyOnWriteArrayList 来存储消息回调列表,保证线程安全 + private val messageListeners: MutableList = CopyOnWriteArrayList() + + + /** + * 注册一个消息监听器。 + * + * @param listener 要注册的 AiMessageListener 实例。 + */ + fun registerListener(listener: AIMessageListener) { + messageListeners.add(listener) + } + + /** + * 取消注册一个消息监听器。 + * + * @param listener 要取消注册的 AiMessageListener 实例。 + */ + fun unregisterListener(listener: AIMessageListener) { + messageListeners.remove(listener) + } + + /** + * 发布一条消息。 + * + * 这条消息会被发送给所有已注册的监听器。 + * + * @param msg 要发布的消息。 + */ + fun post(msg: AIMessage) { + // 遍历所有已注册的监听器,并调用它们的 onReceive 方法 + messageListeners.forEach { callback -> + callback.onReceive(msg) + } + } + + /** + * 消息监听器接口。 + */ + interface AIMessageListener { + /** + * 当接收到消息时,会调用此方法。 + * + * @param msg 接收到的消息。 + */ + fun onReceive(msg: AIMessage) + } +} \ No newline at end of file diff --git a/OCH/taxi/unmanned-passenger/src/main/java/com/mogo/och/unmanned/passenger/ui/aiview/AIViewModel.kt b/OCH/taxi/unmanned-passenger/src/main/java/com/mogo/och/unmanned/passenger/ui/aiview/AIViewModel.kt new file mode 100644 index 0000000000..8d919a61c5 --- /dev/null +++ b/OCH/taxi/unmanned-passenger/src/main/java/com/mogo/och/unmanned/passenger/ui/aiview/AIViewModel.kt @@ -0,0 +1,317 @@ +package com.mogo.och.unmanned.passenger.ui.aiview + +import android.util.Log +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.mogo.eagle.core.data.ai.V2XRepository +import com.mogo.eagle.core.data.map.MogoLocation +import com.mogo.eagle.core.function.api.autopilot.IMoGoChassisLocationGCJ02Listener +import com.mogo.och.unmanned.passenger.ui.aiview.bean.AIMessage +import com.mogo.mgintelligent.speech.AsrResult +import com.mogo.mgintelligent.speech.AsrState +import com.mogo.mgintelligent.speech.IWakeUpListener +import com.mogo.mgintelligent.speech.MGSpeech +import com.mogo.och.bridge.autopilot.location.OchLocationManager +import com.mogo.och.data.taxi.BaseOrderBean +import com.mogo.och.data.taxi.TaxiOrderStatusEnum +import com.mogo.och.unmanned.taxi.utils.order.OrderListener +import com.mogo.och.unmanned.taxi.utils.order.OrderModel +import com.mogo.service.v2n.bean.MGLlmQueryBean +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch + +class AIViewModel : ViewModel(), AIMessageManager.AIMessageListener, IWakeUpListener { + + private val msgList = mutableListOf() + private var lastTimestamp = System.currentTimeMillis() + + // 记录最后一次事件发生的时间,使用 private set 限制外部修改 + // private val TIMESTAMP_THRESHOLD = 1000 * 60 * 5 // 5分钟 + private val TIMESTAMP_THRESHOLD = 1000 * 30 + + private var llmResultJob: Job? = null + + private var isChecking = false + + private val _messagesFlow = MutableStateFlow>(emptyList()) + val messagesFlow: SharedFlow> get() = _messagesFlow + + private val _asrUIStateFlow = MutableStateFlow(AsrUIState.Idle) + val asrUIStateFlow: StateFlow get() = _asrUIStateFlow + + private val mgSpeech = MGSpeech + + private val tipsTimeOut = "小智还没反应过来,请稍后再试~" + private val tipsExit = "我先退下了" + val tipsQuestions = listOf( + "可以问我:前方路口有拥堵吗?", + "可以对我说:介绍一下蘑菇车联?", + "想要对话时可以点击:小智智能体", + "可以问我:今天天气怎么样?", + ) + private val tipsWakeUpList = listOf( + "嗯,我在呢", + "在的", + "在呢", + "你说", + ) + + private val orderListener = object : OrderListener{ + override fun onCurrentOrderStatusChanged(order: BaseOrderBean?) { + if(order?.orderStatus== TaxiOrderStatusEnum.ArriveAtEnd.code){ + synchronized(msgList) { + msgList.clear() + _messagesFlow.value = msgList.toList() + } + } + } + } + + private val locationCallback = object : IMoGoChassisLocationGCJ02Listener{ + override fun onChassisLocationGCJ02(mogoLocation: MogoLocation?) { + mogoLocation?.let { + V2XRepository.provideLocation(it,0) + } + + } + } + + init { + + } + + fun setViewCallback(aiView: AiViewCallback) { + OrderModel.setOrderStatusCallback(TAG,orderListener) + OchLocationManager.addGCJ02Listener(TAG,1,locationCallback) + AIMessageManager.registerListener(this) + mgSpeech.weakUpListener = this + mgSpeech.startWeakUp() + + llmResultJob = viewModelScope.launch(Dispatchers.IO) { + V2XRepository.llmResultFlow.collect { result -> + Log.d(TAG, "llm result: $result") + + val msg = msgList.findLast { it.id == result.queryId } + + if (msg == null || msg !is AIMessage.QA) { + Log.w(TAG, "消息不匹配,${msg}") + return@collect + } + + AIMessageManager.post( + AIMessage.QA( + id = result.queryId, + title = result.answer, + tts = result.answer, + answer = result.answer, + question = msg.question, + state = AIMessage.QA.QuestionState.FINISH, + pictureUrl = result.imgUrls.getOrNull(0) ?: "", + pictureUrlList = result.imgUrls + ) + ) + } + } + } + + + override fun onCleared() { + AIMessageManager.unregisterListener(this) + mgSpeech.weakUpListener = null + mgSpeech.stopWeakUp() + llmResultJob?.cancel() + OrderModel.setOrderStatusCallback(TAG,null) + OchLocationManager.removeGCJ02Listener(TAG) + super.onCleared() + } + + override fun onReceive(msg: AIMessage) { + Log.d(TAG, "onReceive: $msg") + if (isChecking) { + if (msg is AIMessage.Event) { + msg.showScanFlag = true + } + } + + // 获更新消息 + updateMsg(msg) + } + + private fun handleMsg(newMessage: AIMessage) { + val existingIndex = findMessageIndex(newMessage.id) + if (existingIndex != -1) { + handleExistingMessage(existingIndex, newMessage) + } else { + handleNewMessage(newMessage) + } + } + + private fun findMessageIndex(messageId: String): Int { + return msgList.indexOfFirst { it.id == messageId } + } + + private fun handleExistingMessage(index: Int, newMessage: AIMessage) { + val oldMessage = msgList[index] + + if (shouldSkipUpdate(oldMessage, newMessage)) { + return + } + + newMessage.showTimestamp = oldMessage.showTimestamp + msgList[index] = newMessage + + speakMessageIfNeeded(newMessage, isLastMessage = msgList.last() == newMessage) + } + + private fun handleNewMessage(newMessage: AIMessage) { + updateTimestampIfNeeded(newMessage) + msgList.add(newMessage) + speakMessageIfNeeded(newMessage, isLastMessage = true) + } + + private fun shouldSkipUpdate(oldMessage: AIMessage, newMessage: AIMessage): Boolean { + if (oldMessage is AIMessage.QA + && (oldMessage.state == AIMessage.QA.QuestionState.FINISH || + oldMessage.state == AIMessage.QA.QuestionState.ERROR) + ) { + Log.d(TAG, "handleMsg: 消息拦截") + return true + } + return false + } + + private fun updateTimestampIfNeeded(newMessage: AIMessage) { + val time = newMessage.timestamp - lastTimestamp + if (time >= TIMESTAMP_THRESHOLD) { + newMessage.showTimestamp = true + lastTimestamp = newMessage.timestamp + } + } + + private fun speakMessageIfNeeded(newMessage: AIMessage, isLastMessage: Boolean) { + if (isLastMessage && newMessage.tts.isNotEmpty()) { + mgSpeech.speak(newMessage.tts) + } + } + + companion object { + private const val TAG = "AiViewModel" + } + + override fun onWakeUp() { + mgSpeech.isAssistantShow(true) + val random = tipsWakeUpList.random() + _asrUIStateFlow.value = AsrUIState.Listening(random) + mgSpeech.speak(random, true) { + mgSpeech.startAsr() + _asrUIStateFlow.value = AsrUIState.Listening("") + } + } + + override fun onListener(msg: AsrResult) { + when (msg.type) { + AsrState.STATE_PARTIAL -> { +// binding.tvContent.text = msg.content + + _asrUIStateFlow.value = AsrUIState.Listening(msg.content) + } + + AsrState.STATE_FINAL -> { +// assistantView?.showWaitingResult(msg.content) +// binding.tvContent.text = msg.content + _asrUIStateFlow.value = AsrUIState.Recognized(msg.content) + } + + AsrState.STATE_EXIT -> { + _asrUIStateFlow.value = AsrUIState.Idle + mgSpeech.speak(tipsExit) + mgSpeech.isAssistantShow(false) + val content = msg.content + if (content.isNotEmpty()) { + viewModelScope.launch(Dispatchers.IO) { + // 1.上报消息 + val msgId = "${System.currentTimeMillis()}" + V2XRepository.uploadLlmQuery(MGLlmQueryBean().apply { + //user = sn + conversationId = "" + query = content + queryId = msgId + }) + // 2.生成消息 + val qaMsg = AIMessage.QA( + id = msgId, + title = "", + question = content, + state = AIMessage.QA.QuestionState.UNDERSTAND, + answer = "", + ) + AIMessageManager.post(qaMsg) + // 3.延迟发送状态 + delay(2000) + val analyzeMsg = qaMsg.copy(state = AIMessage.QA.QuestionState.ANALYZE) + AIMessageManager.post(analyzeMsg) + // 4.延迟发送状态 + delay(2000) + val answerMsg = qaMsg.copy(state = AIMessage.QA.QuestionState.ANSWER) + AIMessageManager.post(answerMsg) + + // test + delay(10000) + val finishMsg = qaMsg.copy( + state = AIMessage.QA.QuestionState.ERROR, + answer = tipsTimeOut, + ) + AIMessageManager.post(finishMsg) + } + } + } + + else -> { + _asrUIStateFlow.value = AsrUIState.Idle + mgSpeech.isAssistantShow(false) + } + } + } + + private fun updateMsg(msg: AIMessage) { + synchronized(msgList) { + handleMsg(msg) + _messagesFlow.value = msgList.toList() + } + } + + private fun deleteMsg(msgId: String) { + synchronized(msgList) { + val iterator = msgList.iterator() + while (iterator.hasNext()) { + if (iterator.next().id == msgId) { + iterator.remove() + } + } + _messagesFlow.value = msgList.toList() + } + } + + + fun getQuestionsRandomIndex(): Int { + return tipsQuestions.indexOf(tipsQuestions.random()) + } + + interface AiViewCallback { + + } +} + +sealed class AsrUIState { + object Idle : AsrUIState() + data class Listening(val partialText: String) : AsrUIState() + data class Recognized(val finalText: String) : AsrUIState() +} + + + diff --git a/OCH/taxi/unmanned-passenger/src/main/java/com/mogo/och/unmanned/passenger/ui/aiview/AiView.kt b/OCH/taxi/unmanned-passenger/src/main/java/com/mogo/och/unmanned/passenger/ui/aiview/AiView.kt new file mode 100644 index 0000000000..5a53fc1343 --- /dev/null +++ b/OCH/taxi/unmanned-passenger/src/main/java/com/mogo/och/unmanned/passenger/ui/aiview/AiView.kt @@ -0,0 +1,331 @@ +package com.mogo.och.unmanned.passenger.ui.aiview + +import android.animation.ObjectAnimator +import android.content.Context +import android.util.AttributeSet +import androidx.lifecycle.lifecycleScope +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.widget.TextView +import androidx.constraintlayout.motion.widget.MotionLayout +import androidx.core.animation.addListener +import androidx.fragment.app.Fragment +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.findViewTreeLifecycleOwner +import androidx.lifecycle.findViewTreeViewModelStoreOwner +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.mogo.eagle.core.utilcode.kotlin.onClick +import com.mogo.eagle.core.utilcode.mogo.logger.CallerLogger +import com.mogo.och.common.module.manager.logchainanalytic.OchChainLogManager +import com.mogo.och.common.module.utils.BigFrameAnimatorContainer +import com.mogo.och.common.module.utils.RxUtils +import com.mogo.och.unmanned.passenger.ui.aiview.adapter.AIMessageAdapter +import com.mogo.och.unmanned.passenger.ui.aiview.adapter.OnItemClickListener +import com.mogo.och.unmanned.passenger.ui.aiview.adapter.PaddingItemDecoration +import com.mogo.och.unmanned.passenger.ui.aiview.bean.AIMessage +import com.mogo.och.unmanned.taxi.passenger.R +import kotlinx.android.synthetic.main.taxt_p_ai.view.aiMotionLayout +import kotlinx.android.synthetic.main.taxt_p_ai.view.ivIcon +import kotlinx.android.synthetic.main.taxt_p_ai.view.ivListening +import kotlinx.android.synthetic.main.taxt_p_ai.view.rvMessages +import kotlinx.android.synthetic.main.taxt_p_ai.view.rvMessagesEmpty +import kotlinx.android.synthetic.main.taxt_p_ai.view.tvContent +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import kotlinx.coroutines.suspendCancellableCoroutine + +class AiView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : MotionLayout(context, attrs, defStyleAttr), AIViewModel.AiViewCallback { + + + + private var viewModel:AIViewModel?=null + + private var aiAnimator: BigFrameAnimatorContainer?=null + private var aiAnimatorBg: BigFrameAnimatorContainer?=null + + private var tipLooperJob: Job? = null + + @Volatile + private var isTipsLooperRunning = false + + private var tipsIndex = 0 + + private var isUserScrollingTime = 0L + + + private val messageAdapter: AIMessageAdapter by lazy { AIMessageAdapter() } + private val messageLayoutManager: LinearLayoutManager by lazy { + LinearLayoutManager(context).apply { + stackFromEnd = true + } + } + + companion object{ + private const val TIPS_ROTATION_DELAY = 3000L + private const val TAG ="AiView" + private const val SCROLL_THRESHOLD = 5000L + } + + + + + init { + LayoutInflater.from(context).inflate(R.layout.taxt_p_ai, this, true) + + rvMessages.layoutManager = messageLayoutManager + rvMessages.adapter = messageAdapter + rvMessages.addItemDecoration(PaddingItemDecoration(200, 300)) + messageAdapter.onItemClickListener = OnItemClickListener { item, position -> + if (item is AIMessage.Event) { + + } + } + rvMessages.addOnScrollListener(object : RecyclerView.OnScrollListener() { + override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { + super.onScrollStateChanged(recyclerView, newState) + if (newState != RecyclerView.SCROLL_STATE_IDLE) { + isUserScrollingTime = System.currentTimeMillis() + } + } + }) + + if(aiAnimator==null) { + aiAnimator = BigFrameAnimatorContainer(R.array.ai_animator, 31, ivIcon) + } +// aiAnimator?.start() + + ivIcon.onClick { + viewModel?.onWakeUp() + } + + ivListening.onClick { + + } + + + } + + private fun showListening(show: Boolean) { + if (aiAnimatorBg == null) { + aiAnimatorBg = BigFrameAnimatorContainer(R.array.ai_animator_listening, 31, ivListening) + } + + CallerLogger.d(TAG,"showListening: $show") + if (show) { + ivListening.alpha = 1F + aiAnimatorBg?.start() + } else { + aiAnimatorBg?.stop() + ivListening.alpha = 0F + } + } + + private fun startTips() { + if (isTipsLooperRunning) { + CallerLogger.d(TAG,"${tName()} startTips: Already running") + return + } + tipLooperJob?.cancel() + + tipLooperJob = findViewTreeLifecycleOwner()?.lifecycleScope?.launch { + isTipsLooperRunning = true + CallerLogger.d(TAG,"${tName()} startTipsLooper: start") + while (isTipsLooperRunning) { + CallerLogger.d(TAG,"${tName()} startTipsLooper: next") + viewModel?.let { + val tips = it.tipsQuestions + val index = tipsIndex++ % tips.size + tvContent.changeTextWithFade(tips[index]) + } + delay(TIPS_ROTATION_DELAY) + } + } + } + + private fun stopTips() { + Log.i(TAG, "${tName()} stopTips: stop") + tipLooperJob?.cancel() + tipLooperJob = null + isTipsLooperRunning = false + } + + override fun onVisibilityAggregated(isVisible: Boolean) { + super.onVisibilityAggregated(isVisible) + CallerLogger.d(TAG,"是否展示中:${isVisible}") + try { + if(isVisible){ + aiAnimator?.start() + RxUtils.createSubscribe(3_000) { + aiMotionLayout.setTransitionListener(object :TransitionListener{ + override fun onTransitionStarted( + motionLayout: MotionLayout?, + startId: Int, + endId: Int + ) { + CallerLogger.d(TAG,"onTransitionStarted:${motionLayout?.id}_$startId $endId") + } + + override fun onTransitionChange( + motionLayout: MotionLayout?, + startId: Int, + endId: Int, + progress: Float + ) { + CallerLogger.d(TAG,"onTransitionChange:${motionLayout?.id}_$startId $endId ————${progress}") + } + + override fun onTransitionCompleted( + motionLayout: MotionLayout?, + currentId: Int + ) { + CallerLogger.d(TAG,"${tName()} onTransitionCompleted:${motionLayout?.id}_$currentId") + rvMessagesEmpty.visibility = View.VISIBLE + startContextInfo() + startListInfo() + } + + override fun onTransitionTrigger( + motionLayout: MotionLayout?, + triggerId: Int, + positive: Boolean, + progress: Float + ) { + CallerLogger.d(TAG,"onTransitionTrigger:${motionLayout?.id}_$triggerId ${triggerId} $positive $progress") + } + + }) + aiMotionLayout.transitionToEnd() + } + }else{ + aiAnimator?.stop() + } + }catch (e:Exception){ + OchChainLogManager.writeChainLog("展示崩溃","${e.message}") + } + + } + + override fun onAttachedToWindow() { + super.onAttachedToWindow() + viewModel = findViewTreeViewModelStoreOwner()?.let { + ViewModelProvider(it).get(AIViewModel::class.java) + } + viewModel?.setViewCallback(this) + } + + private fun startListInfo(){ + tipsIndex = viewModel?.getQuestionsRandomIndex()?:0 + findViewTreeLifecycleOwner()?.lifecycleScope?.launch { + viewModel?.messagesFlow?.collect { + Log.d(TAG, "${tName()} onMessages update: ${it}") + if(it.isNotEmpty()){ + rvMessagesEmpty.visibility = View.INVISIBLE + }else{ + rvMessagesEmpty.visibility = View.VISIBLE + } + messageAdapter.submitList(it) { + Log.d(TAG, "${tName()} adapter submit: ") + scrollToBottom() + } + } + } + } + + private fun startContextInfo(){ + findViewTreeLifecycleOwner()?.lifecycleScope?.launch { + viewModel?.asrUIStateFlow?.collect { + Log.d(TAG, "${tName()}asr ui state $it") + + when (it) { + is AsrUIState.Idle -> { + startTips() + showListening(false) + } + + is AsrUIState.Listening -> { + stopTips() + showListening(true) + tvContent.text = it.partialText + } + + is AsrUIState.Recognized -> { + stopTips() + tvContent.text = it.finalText + showListening(false) + } + } + } + } + } + + override fun onDetachedFromWindow() { + aiAnimator?.stop() + aiAnimator = null + aiAnimatorBg?.stop() + aiAnimatorBg = null + tipLooperJob?.cancel() + super.onDetachedFromWindow() + } + + // 滚动到RecyclerView底部 + private fun scrollToBottom() { + val delay = System.currentTimeMillis() - isUserScrollingTime + if (delay < SCROLL_THRESHOLD) { + return + } + val layoutManager = rvMessages.layoutManager as LinearLayoutManager + layoutManager.scrollToPositionWithOffset(messageAdapter.itemCount - 1, 0) + } + + fun tName(): String { + return "【${Thread.currentThread().name}】" + } + + private suspend fun TextView.changeTextWithFade(newText: String) { + fadeOutFlow() + text = newText + fadeInFlow() + } + + private suspend fun View.fadeOutFlow(duration: Long = 100) = suspendCancellableCoroutine { continuation -> + val animator = ObjectAnimator.ofFloat(this@fadeOutFlow, "alpha", 1f, 0f).apply { + this.duration = duration + } + animator.addListener(onEnd = { + continuation.resume(Unit) { + animator.cancel() + } + }) + animator.start() + continuation.invokeOnCancellation { + Log.i(TAG, "fadeOutFlow: cancel") + animator.cancel() + } + } + + private suspend fun View.fadeInFlow(duration: Long = 100) = + suspendCancellableCoroutine { continuation -> + val animator = ObjectAnimator.ofFloat(this@fadeInFlow, "alpha", 0f, 1f).apply { + this.duration = duration + } + animator.addListener(onEnd = { + continuation.resume(Unit) { + animator.cancel() + } + }) + animator.start() + continuation.invokeOnCancellation { + CallerLogger.d(TAG,"fadeInFlow: cancel") + animator.cancel() + } + } + + +} \ No newline at end of file diff --git a/OCH/taxi/unmanned-passenger/src/main/java/com/mogo/och/unmanned/passenger/ui/aiview/adapter/AIMessageAdapter.kt b/OCH/taxi/unmanned-passenger/src/main/java/com/mogo/och/unmanned/passenger/ui/aiview/adapter/AIMessageAdapter.kt new file mode 100644 index 0000000000..f38bae4934 --- /dev/null +++ b/OCH/taxi/unmanned-passenger/src/main/java/com/mogo/och/unmanned/passenger/ui/aiview/adapter/AIMessageAdapter.kt @@ -0,0 +1,43 @@ +package com.mogo.och.unmanned.passenger.ui.aiview.adapter + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.ListAdapter +import com.mogo.och.unmanned.passenger.ui.aiview.bean.AIMessage +import com.mogo.och.unmanned.taxi.passenger.R + +class AIMessageAdapter : ListAdapter(MessageDiffCallback()) { + + var onItemClickListener: OnItemClickListener? = null + + override fun onBindViewHolder(holder: MessageViewHolder, position: Int) { + getItem(position)?.let { + holder.bind(it, onItemClickListener) + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MessageViewHolder { + val inflater = LayoutInflater.from(parent.context) + return when (viewType) { + AIMessage.TYPE_QA -> QAViewHolder(inflater.inflate(R.layout.item_ai_msg_qa, parent, false)) + else -> throw IllegalArgumentException("Invalid view type") + } + } + + override fun getItemViewType(position: Int): Int { + return when (getItem(position)) { + is AIMessage.Event -> AIMessage.TYPE_EVENT + is AIMessage.QA -> AIMessage.TYPE_QA + is AIMessage.Scan -> AIMessage.TYPE_SCAN + is AIMessage.Light -> AIMessage.TYPE_LIGHT + is AIMessage.Speed -> AIMessage.TYPE_SPEED + is AIMessage.Warning -> AIMessage.TYPE_WARNING + else -> AIMessage.TYPE_EVENT + } + } + + override fun onViewRecycled(holder: MessageViewHolder) { + super.onViewRecycled(holder) + holder.viewRecycled(holder) + } +} diff --git a/OCH/taxi/unmanned-passenger/src/main/java/com/mogo/och/unmanned/passenger/ui/aiview/adapter/AIMessageViewHolder.kt b/OCH/taxi/unmanned-passenger/src/main/java/com/mogo/och/unmanned/passenger/ui/aiview/adapter/AIMessageViewHolder.kt new file mode 100644 index 0000000000..475fac8976 --- /dev/null +++ b/OCH/taxi/unmanned-passenger/src/main/java/com/mogo/och/unmanned/passenger/ui/aiview/adapter/AIMessageViewHolder.kt @@ -0,0 +1,166 @@ +package com.mogo.och.unmanned.passenger.ui.aiview.adapter + +import android.animation.ObjectAnimator +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.TextView +import androidx.core.graphics.toColorInt +import androidx.recyclerview.widget.RecyclerView +import com.bumptech.glide.Glide +import com.mogo.och.common.module.wigets.OchRoundImageView +import com.mogo.och.unmanned.passenger.ui.aiview.bean.AIMessage +import com.mogo.och.unmanned.taxi.passenger.R +import com.youth.banner.Banner +import com.youth.banner.indicator.CircleIndicator +import com.youth.banner.transformer.ScaleInTransformer +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale + + +abstract class MessageViewHolder(view: View) : RecyclerView.ViewHolder(view) { + abstract fun bind(item: AIMessage, onItemClickListener: OnItemClickListener? = null) + open fun viewRecycled(holder: MessageViewHolder){} + private val sampleDateFormat = SimpleDateFormat("HH:mm", Locale.CHINA) + protected val TAG = javaClass.simpleName + + fun handleTimestamp(item: AIMessage, tvTimestamp: TextView) { + if (item.showTimestamp) { + tvTimestamp.visibility = View.VISIBLE + tvTimestamp.text = sampleDateFormat.format(Date(item.timestamp)) + } else { + tvTimestamp.visibility = View.GONE + } + } + + fun View.setVisibilityBasedOn(condition: Boolean) { + visibility = if (condition) View.VISIBLE else View.GONE + } + + fun TextView.setTextAndVisibility(text: String) { + if (text.isEmpty()) { + visibility = View.GONE + this.text = "" + } else { + visibility = View.VISIBLE + this.text = text + } + } + + fun ImageView.showOrHideWithUrl(url: String) { + if (url.isEmpty()) { + visibility = View.GONE + + } else { + visibility = View.VISIBLE + Glide.with(this) + .load(url) + .placeholder(R.drawable.icon_pic_holder) + .error(R.drawable.icon_pic_error) +// .error(R.drawable.icon_marker_window_place_holder) +// .placeholder(R.drawable.icon_marker_window_place_holder) + .into(this) + + } + } +} + + +class QAViewHolder(val binding: View) : MessageViewHolder(binding) { + private var tvQuestion: TextView = binding.findViewById(R.id.tvQuestion) + private var tvAnswer: TextView = binding.findViewById(R.id.tvAnswer) + private var tvTimestamp: TextView = binding.findViewById(R.id.tvTimestamp) + private var tvStateContent: TextView = binding.findViewById(R.id.tvStateContent) +// private var ivPicture: OchRoundImageView = binding.findViewById(R.id.ivPicture) + private var llState: LinearLayout = binding.findViewById(R.id.llState) + private var tvStateUnderstand: TextView = binding.findViewById(R.id.tvStateUnderstand) + private var tvStateAnalyze: TextView = binding.findViewById(R.id.tvStateAnalyze) + private var tvStateAnswer: TextView = binding.findViewById(R.id.tvStateAnswer) + private var picBanner = binding.findViewById>(R.id.picBanner) + + + override fun bind(item: AIMessage, onItemClickListener: OnItemClickListener?) { + if (item is AIMessage.QA) { + binding.apply { + handleState(item.state) + tvQuestion.text = item.question + Log.d(TAG, "bind: ${item}") + + tvAnswer.setTextAndVisibility(item.answer) +// ivPicture.showOrHideWithUrl(item.pictureUrl) + + handleTimestamp(item, tvTimestamp) + + val pictureUrlList = item.pictureUrlList + if (pictureUrlList.isEmpty()) { + picBanner.visibility = View.GONE + } else { + picBanner.visibility = View.VISIBLE + picBanner.setAdapter(BannerImageAdapter(pictureUrlList)) +// .addBannerLifecycleObserver(picBanner.context) //添加生命周期观察者 + .setIndicator(CircleIndicator(picBanner.context)) + .setIndicatorSelectedColor("#40B4FF".toColorInt()) + .setIndicatorNormalColor("#FFFFFF".toColorInt()) + .setPageTransformer(ScaleInTransformer()) + } + } + } + } + + private fun handleState(state: AIMessage.QA.QuestionState) { + when (state) { + AIMessage.QA.QuestionState.UNDERSTAND -> { + llState.visibility = View.VISIBLE + tvStateContent.text = "正在理解问题…" + tvStateUnderstand.visibility = View.GONE + tvStateAnalyze.visibility = View.GONE + tvStateAnswer.visibility = View.GONE + } + + AIMessage.QA.QuestionState.ANALYZE -> { + tvStateUnderstand.visibility = View.VISIBLE + llState.visibility = View.VISIBLE + tvStateContent.text = "正在分析处理…" + tvStateAnalyze.visibility = View.GONE + tvStateAnswer.visibility = View.GONE + } + + AIMessage.QA.QuestionState.ANSWER -> { + tvStateUnderstand.visibility = View.VISIBLE + tvStateAnalyze.visibility = View.VISIBLE + llState.visibility = View.VISIBLE + tvStateContent.text = "正在总结回答…" + tvStateAnswer.visibility = View.GONE + } + + + AIMessage.QA.QuestionState.FINISH -> { + tvStateUnderstand.visibility = View.VISIBLE + tvStateAnalyze.visibility = View.VISIBLE + tvStateAnswer.visibility = View.VISIBLE + + llState.visibility = View.GONE + tvStateContent.text = "" + } + + AIMessage.QA.QuestionState.ERROR -> { + tvStateUnderstand.visibility = View.VISIBLE + tvStateAnalyze.visibility = View.VISIBLE + tvStateAnswer.visibility = View.VISIBLE + + llState.visibility = View.GONE + tvStateContent.text = "" + } + } + } +} + + + +fun interface OnItemClickListener { + fun onItemClick(item: AIMessage, position: Int) +} diff --git a/OCH/taxi/unmanned-passenger/src/main/java/com/mogo/och/unmanned/passenger/ui/aiview/adapter/BannerImageAdapter.kt b/OCH/taxi/unmanned-passenger/src/main/java/com/mogo/och/unmanned/passenger/ui/aiview/adapter/BannerImageAdapter.kt new file mode 100644 index 0000000000..58b00d0ffe --- /dev/null +++ b/OCH/taxi/unmanned-passenger/src/main/java/com/mogo/och/unmanned/passenger/ui/aiview/adapter/BannerImageAdapter.kt @@ -0,0 +1,31 @@ +package com.mogo.och.unmanned.passenger.ui.aiview.adapter + +import android.view.LayoutInflater +import android.view.ViewGroup +import android.widget.ImageView +import androidx.recyclerview.widget.RecyclerView +import com.bumptech.glide.Glide +import com.mogo.och.unmanned.taxi.passenger.R +import com.youth.banner.adapter.BannerAdapter + +open class BannerImageAdapter(imgList: List) : + BannerAdapter(imgList) { + override fun onBindView(holder: BannerHolder?, data: String?, position: Int, size: Int) { + //图片加载自己实现 + holder ?: return + val imageView = holder.view + Glide.with(imageView) + .load(data) +// .apply(RequestOptions.bitmapTransform(RoundedCorners(30))) + .into(imageView) + } + + override fun onCreateHolder(parent: ViewGroup, viewType: Int): BannerHolder { + val inflater = LayoutInflater.from(parent.context) + val view = inflater.inflate(R.layout.item_ai_banner_item, parent, false) as ImageView + return BannerHolder(view) + } +} + + +class BannerHolder(val view: ImageView) : RecyclerView.ViewHolder(view) \ No newline at end of file diff --git a/OCH/taxi/unmanned-passenger/src/main/java/com/mogo/och/unmanned/passenger/ui/aiview/adapter/MessageDiffCallback.kt b/OCH/taxi/unmanned-passenger/src/main/java/com/mogo/och/unmanned/passenger/ui/aiview/adapter/MessageDiffCallback.kt new file mode 100644 index 0000000000..c5936542d9 --- /dev/null +++ b/OCH/taxi/unmanned-passenger/src/main/java/com/mogo/och/unmanned/passenger/ui/aiview/adapter/MessageDiffCallback.kt @@ -0,0 +1,15 @@ +package com.mogo.och.unmanned.passenger.ui.aiview.adapter + +import androidx.recyclerview.widget.DiffUtil +import com.mogo.och.unmanned.passenger.ui.aiview.bean.AIMessage + +class MessageDiffCallback: DiffUtil.ItemCallback() { + + override fun areContentsTheSame(oldItem: AIMessage, newItem: AIMessage): Boolean { + return oldItem == newItem + } + + override fun areItemsTheSame(oldItem: AIMessage, newItem: AIMessage): Boolean { + return oldItem.id == newItem.id + } +} \ No newline at end of file diff --git a/OCH/taxi/unmanned-passenger/src/main/java/com/mogo/och/unmanned/passenger/ui/aiview/adapter/PaddingItemDecoration.kt b/OCH/taxi/unmanned-passenger/src/main/java/com/mogo/och/unmanned/passenger/ui/aiview/adapter/PaddingItemDecoration.kt new file mode 100644 index 0000000000..132afe2439 --- /dev/null +++ b/OCH/taxi/unmanned-passenger/src/main/java/com/mogo/och/unmanned/passenger/ui/aiview/adapter/PaddingItemDecoration.kt @@ -0,0 +1,26 @@ +package com.mogo.och.unmanned.passenger.ui.aiview.adapter + +import android.graphics.Rect +import android.view.View +import androidx.recyclerview.widget.RecyclerView + +class PaddingItemDecoration(private val topPadding: Int, private val bottomPadding: Int) : RecyclerView.ItemDecoration() { + override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) { + super.getItemOffsets(outRect, view, parent, state) + + + // 只有第一个 item 的顶部添加空白 + if (parent.getChildAdapterPosition(view) == 0) { + outRect.top = topPadding + } else{ + outRect.top = 0 + } + + // 最后一个 item 的底部添加空白 +// if (parent.getChildAdapterPosition(view) == state.itemCount - 1) { +// outRect.bottom = bottomPadding +// } else{ +// outRect.bottom = 0 +// } + } +} \ No newline at end of file diff --git a/OCH/taxi/unmanned-passenger/src/main/java/com/mogo/och/unmanned/passenger/ui/aiview/bean/AssistantMessage.kt b/OCH/taxi/unmanned-passenger/src/main/java/com/mogo/och/unmanned/passenger/ui/aiview/bean/AssistantMessage.kt new file mode 100644 index 0000000000..054e8ef6d3 --- /dev/null +++ b/OCH/taxi/unmanned-passenger/src/main/java/com/mogo/och/unmanned/passenger/ui/aiview/bean/AssistantMessage.kt @@ -0,0 +1,129 @@ +package com.mogo.och.unmanned.passenger.ui.aiview.bean + +import android.os.CountDownTimer +import android.util.Log +import kotlin.math.floor + + +sealed class AIMessage( + open val id: String, + open val title: String, + open val tts: String = "", + val timestamp: Long = System.currentTimeMillis(), + var showTimestamp: Boolean = false +) { + + companion object { + const val TYPE_SCAN = 0 + const val TYPE_EVENT = 1 + const val TYPE_QA = 2 + const val TYPE_LIGHT = 3 + const val TYPE_SPEED = 4 + const val TYPE_WARNING = 5 + } + + data class Scan( + override val id: String, + override val title: String, + val pictureUrl: String = "", + var showScanFlag: Boolean = false + ) : AIMessage(id, title) + + data class Event( + override val id: String, + override val title: String, + override val tts: String = "", + val position: String = "", + val distance: String = "", + val range: String = "", + val time: String = "", + val pictureUrl: String = "", + val videoUrl: String = "", + var showScanFlag: Boolean = false + ) : AIMessage(id, title, tts) + + data class QA( + override val id: String, + override val title: String, + override val tts: String = "", + val question: String, + val answer: String, + var state: QuestionState = QuestionState.UNDERSTAND, + val pictureUrl: String = "", + var pictureUrlList: List = listOf(), + val videoUrl: String = "", + ) : AIMessage(id, title, tts) { + + enum class QuestionState(val code: Int) { + UNDERSTAND(1), + ANALYZE(2), + ANSWER(3), + FINISH(4), + ERROR(-1), + } + } + + data class Light( + override val id: String, + override val title: String, + override val tts: String = "", + var seconds: Int, + val status: Int + ) : AIMessage(id, title, tts) { + private var countDownTimer: CountDownTimer? = null + private var listener: OnCountdownUpdateListener? = null + + fun startCountdown(millisInFuture: Long, countDownInternal: Long) { + countDownTimer?.cancel() + countDownTimer = object : CountDownTimer(millisInFuture, countDownInternal) { + override fun onTick(millisUntilFinished: Long) { + //倒计时开始 + Log.d( + "StartOrSlowDownTip", + "millisUntilFinished = $millisUntilFinished, countDownInternal = $countDownInternal" + ) + val cd = millisUntilFinished / 1000.0 +// val split = String.format("%.2f", cd).split(".") + seconds = floor(cd).toInt() + listener?.onCountdownUpdate() + } + + override fun onFinish() { + //倒计时完成 + seconds = 0 + listener?.onCountdownFinish() + } + } + countDownTimer?.start() + } + + fun stopCountdown() { + countDownTimer?.cancel() + } + + fun setOnCountdownUpdateListener(listener: OnCountdownUpdateListener) { + this.listener = listener + } + + interface OnCountdownUpdateListener { + fun onCountdownUpdate() + fun onCountdownFinish() + } + } + + + data class Speed( + override val id: String, + override val title: String, + override val tts: String = "", + val speedMax: Int, + val speedMin: Int, + ) : AIMessage(id, title, tts) + + data class Warning( + override val id: String, + override val title: String, + override val tts: String = "", + ) : AIMessage(id, title, tts) + +} \ No newline at end of file diff --git a/OCH/taxi/unmanned-passenger/src/main/java/com/mogo/och/unmanned/passenger/ui/aiview/bean/ListenUIState.kt b/OCH/taxi/unmanned-passenger/src/main/java/com/mogo/och/unmanned/passenger/ui/aiview/bean/ListenUIState.kt new file mode 100644 index 0000000000..7fd872cf8c --- /dev/null +++ b/OCH/taxi/unmanned-passenger/src/main/java/com/mogo/och/unmanned/passenger/ui/aiview/bean/ListenUIState.kt @@ -0,0 +1,3 @@ +package com.mogo.och.unmanned.passenger.ui.aiview.bean + +data class ListenUIState(val show: Boolean, val text: String, val showTips: Boolean = false) \ No newline at end of file diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_01.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_01.webp new file mode 100644 index 0000000000..6b4f4306ec Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_01.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_02.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_02.webp new file mode 100644 index 0000000000..3dbff5f787 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_02.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_03.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_03.webp new file mode 100644 index 0000000000..9752651d85 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_03.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_04.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_04.webp new file mode 100644 index 0000000000..f3b092aa6d Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_04.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_05.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_05.webp new file mode 100644 index 0000000000..e73389d1d9 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_05.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_06.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_06.webp new file mode 100644 index 0000000000..bed96396bb Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_06.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_07.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_07.webp new file mode 100644 index 0000000000..adc91a7958 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_07.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_08.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_08.webp new file mode 100644 index 0000000000..9c65009417 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_08.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_09.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_09.webp new file mode 100644 index 0000000000..15d9b8ae19 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_09.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_10.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_10.webp new file mode 100644 index 0000000000..65b78a6fb3 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_10.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_100.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_100.webp new file mode 100644 index 0000000000..fe6d63010d Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_100.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_11.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_11.webp new file mode 100644 index 0000000000..79f4b9c34f Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_11.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_12.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_12.webp new file mode 100644 index 0000000000..1bae2d5c7a Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_12.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_13.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_13.webp new file mode 100644 index 0000000000..373f770f0d Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_13.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_14.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_14.webp new file mode 100644 index 0000000000..11c1ae98fa Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_14.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_15.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_15.webp new file mode 100644 index 0000000000..65e1f0bfff Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_15.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_16.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_16.webp new file mode 100644 index 0000000000..d75150e2f2 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_16.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_17.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_17.webp new file mode 100644 index 0000000000..e2d13882d3 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_17.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_18.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_18.webp new file mode 100644 index 0000000000..0e84b6fc9c Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_18.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_19.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_19.webp new file mode 100644 index 0000000000..93a6218ce2 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_19.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_20.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_20.webp new file mode 100644 index 0000000000..5b604c5576 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_20.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_21.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_21.webp new file mode 100644 index 0000000000..a937518c16 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_21.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_22.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_22.webp new file mode 100644 index 0000000000..39fb1fea8a Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_22.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_23.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_23.webp new file mode 100644 index 0000000000..3df50a739b Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_23.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_24.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_24.webp new file mode 100644 index 0000000000..39820d1999 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_24.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_25.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_25.webp new file mode 100644 index 0000000000..0bbea56fb9 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_25.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_26.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_26.webp new file mode 100644 index 0000000000..75cda44ebf Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_26.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_27.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_27.webp new file mode 100644 index 0000000000..8fe11311ac Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_27.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_28.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_28.webp new file mode 100644 index 0000000000..5d3073a67d Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_28.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_29.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_29.webp new file mode 100644 index 0000000000..a04b1e4ca9 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_29.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_30.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_30.webp new file mode 100644 index 0000000000..fc9fcc5401 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_30.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_31.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_31.webp new file mode 100644 index 0000000000..9e6ef15aa5 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_31.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_32.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_32.webp new file mode 100644 index 0000000000..47e414d71e Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_32.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_33.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_33.webp new file mode 100644 index 0000000000..b674719078 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_33.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_34.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_34.webp new file mode 100644 index 0000000000..2286c059de Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_34.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_35.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_35.webp new file mode 100644 index 0000000000..5cbf8d8e54 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_35.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_36.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_36.webp new file mode 100644 index 0000000000..92af4bccb5 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_36.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_37.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_37.webp new file mode 100644 index 0000000000..83e072bf44 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_37.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_38.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_38.webp new file mode 100644 index 0000000000..b96b5b2ac8 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_38.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_39.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_39.webp new file mode 100644 index 0000000000..629f61cdd5 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_39.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_40.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_40.webp new file mode 100644 index 0000000000..9a5f07ff0b Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_40.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_41.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_41.webp new file mode 100644 index 0000000000..0f5d29acea Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_41.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_42.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_42.webp new file mode 100644 index 0000000000..bed3ce67b3 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_42.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_43.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_43.webp new file mode 100644 index 0000000000..404a075292 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_43.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_44.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_44.webp new file mode 100644 index 0000000000..3578bb2f19 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_44.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_45.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_45.webp new file mode 100644 index 0000000000..c5fb0d0a3b Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_45.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_46.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_46.webp new file mode 100644 index 0000000000..3b346373ee Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_46.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_47.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_47.webp new file mode 100644 index 0000000000..7d3010f838 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_47.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_48.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_48.webp new file mode 100644 index 0000000000..69bad18a38 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_48.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_49.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_49.webp new file mode 100644 index 0000000000..d39950515f Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_49.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_50.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_50.webp new file mode 100644 index 0000000000..8a07ff89ca Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_50.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_51.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_51.webp new file mode 100644 index 0000000000..413a290ff5 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_51.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_52.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_52.webp new file mode 100644 index 0000000000..04d42b353d Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_52.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_53.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_53.webp new file mode 100644 index 0000000000..5f181da3b2 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_53.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_54.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_54.webp new file mode 100644 index 0000000000..82a77ad510 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_54.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_55.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_55.webp new file mode 100644 index 0000000000..266bb0ea53 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_55.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_56.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_56.webp new file mode 100644 index 0000000000..390c687b4b Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_56.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_57.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_57.webp new file mode 100644 index 0000000000..b89ff7a53f Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_57.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_58.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_58.webp new file mode 100644 index 0000000000..37be517e39 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_58.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_59.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_59.webp new file mode 100644 index 0000000000..10500d1bd8 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_59.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_60.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_60.webp new file mode 100644 index 0000000000..3a9ca2b135 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_60.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_61.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_61.webp new file mode 100644 index 0000000000..f1f15b09b9 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_61.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_62.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_62.webp new file mode 100644 index 0000000000..36ac568e29 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_62.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_63.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_63.webp new file mode 100644 index 0000000000..92a4a699b7 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_63.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_64.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_64.webp new file mode 100644 index 0000000000..c013e4e7a9 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_64.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_65.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_65.webp new file mode 100644 index 0000000000..49aeb97d73 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_65.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_66.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_66.webp new file mode 100644 index 0000000000..aab4c5b373 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_66.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_67.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_67.webp new file mode 100644 index 0000000000..e89c0a8ce4 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_67.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_68.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_68.webp new file mode 100644 index 0000000000..d1d24aaae4 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_68.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_69.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_69.webp new file mode 100644 index 0000000000..9f74c1421b Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_69.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_70.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_70.webp new file mode 100644 index 0000000000..ff413a191a Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_70.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_71.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_71.webp new file mode 100644 index 0000000000..ecbb0f0448 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_71.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_72.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_72.webp new file mode 100644 index 0000000000..9776560e08 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_72.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_73.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_73.webp new file mode 100644 index 0000000000..b7c5ee478a Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_73.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_74.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_74.webp new file mode 100644 index 0000000000..77f04d978f Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_74.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_75.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_75.webp new file mode 100644 index 0000000000..4863c07dcd Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_75.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_76.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_76.webp new file mode 100644 index 0000000000..2890b7c416 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_76.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_77.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_77.webp new file mode 100644 index 0000000000..c46b150a45 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_77.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_78.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_78.webp new file mode 100644 index 0000000000..560743d781 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_78.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_79.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_79.webp new file mode 100644 index 0000000000..1b4f9de0f5 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_79.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_80.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_80.webp new file mode 100644 index 0000000000..09e5baded4 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_80.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_81.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_81.webp new file mode 100644 index 0000000000..7cbe07a874 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_81.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_82.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_82.webp new file mode 100644 index 0000000000..8c764eed3a Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_82.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_83.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_83.webp new file mode 100644 index 0000000000..4376a5b527 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_83.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_84.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_84.webp new file mode 100644 index 0000000000..8d23a7306b Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_84.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_85.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_85.webp new file mode 100644 index 0000000000..25ab506111 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_85.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_86.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_86.webp new file mode 100644 index 0000000000..e17fbe51a3 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_86.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_87.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_87.webp new file mode 100644 index 0000000000..d4f66c8e97 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_87.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_88.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_88.webp new file mode 100644 index 0000000000..6d5b509c01 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_88.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_89.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_89.webp new file mode 100644 index 0000000000..f165990782 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_89.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_90.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_90.webp new file mode 100644 index 0000000000..7dd83b3617 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_90.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_91.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_91.webp new file mode 100644 index 0000000000..4762c95b6c Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_91.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_92.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_92.webp new file mode 100644 index 0000000000..17a1e7ca85 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_92.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_93.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_93.webp new file mode 100644 index 0000000000..ee8fd72f33 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_93.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_94.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_94.webp new file mode 100644 index 0000000000..6865bf5d3e Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_94.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_95.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_95.webp new file mode 100644 index 0000000000..89d61e30d9 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_95.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_96.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_96.webp new file mode 100644 index 0000000000..213be04d2c Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_96.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_97.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_97.webp new file mode 100644 index 0000000000..10921d599f Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_97.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_98.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_98.webp new file mode 100644 index 0000000000..8ea38d7ca1 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_98.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_99.webp b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_99.webp new file mode 100644 index 0000000000..d8c07824a5 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_99.webp differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_00.png b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_00.png new file mode 100644 index 0000000000..e6251b3e9b Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_00.png differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_01.png b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_01.png new file mode 100644 index 0000000000..2ebcb608df Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_01.png differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_02.png b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_02.png new file mode 100644 index 0000000000..edb8b077ed Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_02.png differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_03.png b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_03.png new file mode 100644 index 0000000000..867b0283ff Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_03.png differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_04.png b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_04.png new file mode 100644 index 0000000000..d94af63ca0 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_04.png differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_05.png b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_05.png new file mode 100644 index 0000000000..02275e7c44 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_05.png differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_06.png b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_06.png new file mode 100644 index 0000000000..79172c0904 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_06.png differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_07.png b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_07.png new file mode 100644 index 0000000000..648be20fe9 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_07.png differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_08.png b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_08.png new file mode 100644 index 0000000000..8aa456573f Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_08.png differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_09.png b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_09.png new file mode 100644 index 0000000000..45e04b6df7 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_09.png differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_10.png b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_10.png new file mode 100644 index 0000000000..a125fa2a11 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_10.png differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_11.png b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_11.png new file mode 100644 index 0000000000..f025adaf45 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_11.png differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_12.png b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_12.png new file mode 100644 index 0000000000..bade4e7fa0 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_12.png differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_13.png b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_13.png new file mode 100644 index 0000000000..20415ad3d5 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_13.png differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_14.png b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_14.png new file mode 100644 index 0000000000..9667980607 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_14.png differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_15.png b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_15.png new file mode 100644 index 0000000000..f1b4aa07ec Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_15.png differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_16.png b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_16.png new file mode 100644 index 0000000000..ade00f137f Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_16.png differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_17.png b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_17.png new file mode 100644 index 0000000000..19b2d879fe Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_17.png differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_18.png b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_18.png new file mode 100644 index 0000000000..26462ef724 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_18.png differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_19.png b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_19.png new file mode 100644 index 0000000000..7838464877 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_19.png differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_20.png b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_20.png new file mode 100644 index 0000000000..3820cd4725 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_20.png differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_21.png b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_21.png new file mode 100644 index 0000000000..3cece8dc68 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_21.png differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_22.png b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_22.png new file mode 100644 index 0000000000..48966b0901 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_22.png differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_23.png b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_23.png new file mode 100644 index 0000000000..d05bea95f8 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_23.png differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_24.png b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_24.png new file mode 100644 index 0000000000..61a14d3d31 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_24.png differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_25.png b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_25.png new file mode 100644 index 0000000000..42d3cb07bb Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_25.png differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_26.png b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_26.png new file mode 100644 index 0000000000..f3ff9de974 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_26.png differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_27.png b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_27.png new file mode 100644 index 0000000000..aad3e72a94 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_27.png differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_28.png b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_28.png new file mode 100644 index 0000000000..939f057983 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_28.png differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_29.png b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_29.png new file mode 100644 index 0000000000..d537a73dc1 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_29.png differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_30.png b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_30.png new file mode 100644 index 0000000000..cbf447d296 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_30.png differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_31.png b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_31.png new file mode 100644 index 0000000000..3cb32e80f0 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_31.png differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_32.png b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_32.png new file mode 100644 index 0000000000..893b64936f Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_32.png differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_33.png b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_33.png new file mode 100644 index 0000000000..c6bd9cb25f Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_33.png differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_34.png b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_34.png new file mode 100644 index 0000000000..c30c5e3da5 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_34.png differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_35.png b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_35.png new file mode 100644 index 0000000000..216fc11d85 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_35.png differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_36.png b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_36.png new file mode 100644 index 0000000000..d4b413ff24 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_36.png differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_37.png b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_37.png new file mode 100644 index 0000000000..6f5f4d8475 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_37.png differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_38.png b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_38.png new file mode 100644 index 0000000000..988fe6e0d0 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_38.png differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_39.png b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_39.png new file mode 100644 index 0000000000..ac41b22f37 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_39.png differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_40.png b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_40.png new file mode 100644 index 0000000000..639886dd88 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_40.png differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_41.png b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_41.png new file mode 100644 index 0000000000..c9c45eda17 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_41.png differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_42.png b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_42.png new file mode 100644 index 0000000000..9e2636712e Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_42.png differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_43.png b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_43.png new file mode 100644 index 0000000000..06e4be3956 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_43.png differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_44.png b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_44.png new file mode 100644 index 0000000000..66dc6001ca Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_44.png differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_45.png b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_45.png new file mode 100644 index 0000000000..37f02d59e2 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_45.png differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_46.png b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_46.png new file mode 100644 index 0000000000..311a25105b Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_46.png differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_47.png b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_47.png new file mode 100644 index 0000000000..8d0b4affaf Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_47.png differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_48.png b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_48.png new file mode 100644 index 0000000000..124a146839 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_48.png differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_49.png b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_49.png new file mode 100644 index 0000000000..e6251b3e9b Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/ani/drawable-nodpi/anim_ai_listening_49.png differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/ani/values/arrays.xml b/OCH/taxi/unmanned-passenger/src/main/res/ani/values/arrays.xml index 1dfcf8e5e6..f17eebd89c 100644 --- a/OCH/taxi/unmanned-passenger/src/main/res/ani/values/arrays.xml +++ b/OCH/taxi/unmanned-passenger/src/main/res/ani/values/arrays.xml @@ -302,4 +302,179 @@ + + @drawable/anim_ai_01 + @drawable/anim_ai_02 + @drawable/anim_ai_03 + @drawable/anim_ai_04 + @drawable/anim_ai_05 + @drawable/anim_ai_06 + @drawable/anim_ai_07 + @drawable/anim_ai_08 + @drawable/anim_ai_09 + + @drawable/anim_ai_10 + @drawable/anim_ai_11 + @drawable/anim_ai_12 + @drawable/anim_ai_13 + @drawable/anim_ai_14 + @drawable/anim_ai_15 + @drawable/anim_ai_16 + @drawable/anim_ai_17 + @drawable/anim_ai_18 + @drawable/anim_ai_19 + + @drawable/anim_ai_20 + @drawable/anim_ai_21 + @drawable/anim_ai_22 + @drawable/anim_ai_23 + @drawable/anim_ai_24 + @drawable/anim_ai_25 + @drawable/anim_ai_26 + @drawable/anim_ai_27 + @drawable/anim_ai_28 + @drawable/anim_ai_29 + + + @drawable/anim_ai_30 + @drawable/anim_ai_31 + @drawable/anim_ai_32 + @drawable/anim_ai_33 + @drawable/anim_ai_34 + @drawable/anim_ai_35 + @drawable/anim_ai_36 + @drawable/anim_ai_37 + @drawable/anim_ai_38 + @drawable/anim_ai_39 + + @drawable/anim_ai_40 + @drawable/anim_ai_41 + @drawable/anim_ai_42 + @drawable/anim_ai_43 + @drawable/anim_ai_44 + @drawable/anim_ai_45 + @drawable/anim_ai_46 + @drawable/anim_ai_47 + @drawable/anim_ai_48 + @drawable/anim_ai_49 + + + @drawable/anim_ai_50 + @drawable/anim_ai_51 + @drawable/anim_ai_52 + @drawable/anim_ai_53 + @drawable/anim_ai_54 + @drawable/anim_ai_55 + @drawable/anim_ai_56 + @drawable/anim_ai_57 + @drawable/anim_ai_58 + @drawable/anim_ai_59 + + @drawable/anim_ai_60 + @drawable/anim_ai_61 + @drawable/anim_ai_62 + @drawable/anim_ai_63 + @drawable/anim_ai_64 + @drawable/anim_ai_65 + @drawable/anim_ai_66 + @drawable/anim_ai_67 + @drawable/anim_ai_68 + @drawable/anim_ai_69 + + @drawable/anim_ai_70 + @drawable/anim_ai_71 + @drawable/anim_ai_72 + @drawable/anim_ai_73 + @drawable/anim_ai_74 + @drawable/anim_ai_75 + @drawable/anim_ai_76 + @drawable/anim_ai_77 + @drawable/anim_ai_78 + @drawable/anim_ai_79 + + @drawable/anim_ai_80 + @drawable/anim_ai_81 + @drawable/anim_ai_82 + @drawable/anim_ai_83 + @drawable/anim_ai_84 + @drawable/anim_ai_85 + @drawable/anim_ai_86 + @drawable/anim_ai_87 + @drawable/anim_ai_88 + @drawable/anim_ai_89 + + @drawable/anim_ai_90 + @drawable/anim_ai_91 + @drawable/anim_ai_92 + @drawable/anim_ai_93 + @drawable/anim_ai_94 + @drawable/anim_ai_95 + @drawable/anim_ai_96 + @drawable/anim_ai_97 + @drawable/anim_ai_98 + @drawable/anim_ai_99 + @drawable/anim_ai_100 + + + + + @drawable/anim_ai_listening_00 + @drawable/anim_ai_listening_01 + @drawable/anim_ai_listening_02 + @drawable/anim_ai_listening_03 + @drawable/anim_ai_listening_04 + @drawable/anim_ai_listening_05 + @drawable/anim_ai_listening_06 + @drawable/anim_ai_listening_07 + @drawable/anim_ai_listening_08 + @drawable/anim_ai_listening_09 + + @drawable/anim_ai_listening_10 + @drawable/anim_ai_listening_11 + @drawable/anim_ai_listening_12 + @drawable/anim_ai_listening_13 + @drawable/anim_ai_listening_14 + @drawable/anim_ai_listening_15 + @drawable/anim_ai_listening_16 + @drawable/anim_ai_listening_17 + @drawable/anim_ai_listening_18 + @drawable/anim_ai_listening_19 + + @drawable/anim_ai_listening_20 + @drawable/anim_ai_listening_21 + @drawable/anim_ai_listening_22 + @drawable/anim_ai_listening_23 + @drawable/anim_ai_listening_24 + @drawable/anim_ai_listening_25 + @drawable/anim_ai_listening_26 + @drawable/anim_ai_listening_27 + @drawable/anim_ai_listening_28 + @drawable/anim_ai_listening_29 + + + @drawable/anim_ai_listening_30 + @drawable/anim_ai_listening_31 + @drawable/anim_ai_listening_32 + @drawable/anim_ai_listening_33 + @drawable/anim_ai_listening_34 + @drawable/anim_ai_listening_35 + @drawable/anim_ai_listening_36 + @drawable/anim_ai_listening_37 + @drawable/anim_ai_listening_38 + @drawable/anim_ai_listening_39 + + @drawable/anim_ai_listening_40 + @drawable/anim_ai_listening_41 + @drawable/anim_ai_listening_42 + @drawable/anim_ai_listening_43 + @drawable/anim_ai_listening_44 + @drawable/anim_ai_listening_45 + @drawable/anim_ai_listening_46 + @drawable/anim_ai_listening_47 + @drawable/anim_ai_listening_48 + @drawable/anim_ai_listening_49 + + + + \ No newline at end of file diff --git a/OCH/taxi/unmanned-passenger/src/main/res/drawable-nodpi/icon_image_error.png b/OCH/taxi/unmanned-passenger/src/main/res/drawable-nodpi/icon_image_error.png new file mode 100644 index 0000000000..4cadae7774 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/drawable-nodpi/icon_image_error.png differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/drawable-nodpi/icon_image_holder.png b/OCH/taxi/unmanned-passenger/src/main/res/drawable-nodpi/icon_image_holder.png new file mode 100644 index 0000000000..3febfc5587 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/drawable-nodpi/icon_image_holder.png differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/drawable-nodpi/taxi_p_ai_bg.png b/OCH/taxi/unmanned-passenger/src/main/res/drawable-nodpi/taxi_p_ai_bg.png new file mode 100644 index 0000000000..64323d5e89 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/drawable-nodpi/taxi_p_ai_bg.png differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/drawable-nodpi/taxi_p_ai_head.png b/OCH/taxi/unmanned-passenger/src/main/res/drawable-nodpi/taxi_p_ai_head.png new file mode 100644 index 0000000000..3c14d19549 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/drawable-nodpi/taxi_p_ai_head.png differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/drawable-nodpi/taxi_p_home_map_bg_1.9.png b/OCH/taxi/unmanned-passenger/src/main/res/drawable-nodpi/taxi_p_home_map_bg_1.9.png new file mode 100644 index 0000000000..af046bb4e0 Binary files /dev/null and b/OCH/taxi/unmanned-passenger/src/main/res/drawable-nodpi/taxi_p_home_map_bg_1.9.png differ diff --git a/OCH/taxi/unmanned-passenger/src/main/res/drawable/bg_assistant_item_point.xml b/OCH/taxi/unmanned-passenger/src/main/res/drawable/bg_assistant_item_point.xml new file mode 100644 index 0000000000..cab7ec214e --- /dev/null +++ b/OCH/taxi/unmanned-passenger/src/main/res/drawable/bg_assistant_item_point.xml @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/OCH/taxi/unmanned-passenger/src/main/res/drawable/icon_pic_error.xml b/OCH/taxi/unmanned-passenger/src/main/res/drawable/icon_pic_error.xml new file mode 100644 index 0000000000..5de9d5afff --- /dev/null +++ b/OCH/taxi/unmanned-passenger/src/main/res/drawable/icon_pic_error.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OCH/taxi/unmanned-passenger/src/main/res/drawable/icon_pic_holder.xml b/OCH/taxi/unmanned-passenger/src/main/res/drawable/icon_pic_holder.xml new file mode 100644 index 0000000000..67373cd139 --- /dev/null +++ b/OCH/taxi/unmanned-passenger/src/main/res/drawable/icon_pic_holder.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OCH/taxi/unmanned-passenger/src/main/res/layout/item_ai_banner_item.xml b/OCH/taxi/unmanned-passenger/src/main/res/layout/item_ai_banner_item.xml new file mode 100644 index 0000000000..b74b2bfce4 --- /dev/null +++ b/OCH/taxi/unmanned-passenger/src/main/res/layout/item_ai_banner_item.xml @@ -0,0 +1,10 @@ + + + diff --git a/OCH/taxi/unmanned-passenger/src/main/res/layout/item_ai_msg_qa.xml b/OCH/taxi/unmanned-passenger/src/main/res/layout/item_ai_msg_qa.xml new file mode 100644 index 0000000000..44171a642c --- /dev/null +++ b/OCH/taxi/unmanned-passenger/src/main/res/layout/item_ai_msg_qa.xml @@ -0,0 +1,106 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OCH/taxi/unmanned-passenger/src/main/res/layout/taxi_p_home.xml b/OCH/taxi/unmanned-passenger/src/main/res/layout/taxi_p_home.xml index e2a538eeb8..321c4e74a1 100644 --- a/OCH/taxi/unmanned-passenger/src/main/res/layout/taxi_p_home.xml +++ b/OCH/taxi/unmanned-passenger/src/main/res/layout/taxi_p_home.xml @@ -4,15 +4,17 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" - tools:parentTag="RelativeLayout"> + tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout"> - @@ -24,7 +26,7 @@ app:isWeatherEnable="true" android:layout_width="match_parent" android:layout_height="match_parent" /> - + - - + + - + + + + + + \ No newline at end of file diff --git a/OCH/taxi/unmanned-passenger/src/main/res/layout/taxt_p_ai.xml b/OCH/taxi/unmanned-passenger/src/main/res/layout/taxt_p_ai.xml new file mode 100644 index 0000000000..3260d49bf2 --- /dev/null +++ b/OCH/taxi/unmanned-passenger/src/main/res/layout/taxt_p_ai.xml @@ -0,0 +1,110 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OCH/taxi/unmanned-passenger/src/main/res/layout/taxt_u_p_base_fragment.xml b/OCH/taxi/unmanned-passenger/src/main/res/layout/taxt_u_p_base_fragment.xml index 485a16f27e..b4c353d2f4 100644 --- a/OCH/taxi/unmanned-passenger/src/main/res/layout/taxt_u_p_base_fragment.xml +++ b/OCH/taxi/unmanned-passenger/src/main/res/layout/taxt_u_p_base_fragment.xml @@ -22,13 +22,13 @@ #80000000 11 + + + + + + + + + \ No newline at end of file diff --git a/OCH/taxi/unmanned-passenger/src/main/res/xml/fragment_ai_scene.xml b/OCH/taxi/unmanned-passenger/src/main/res/xml/fragment_ai_scene.xml new file mode 100644 index 0000000000..31dd7b1a27 --- /dev/null +++ b/OCH/taxi/unmanned-passenger/src/main/res/xml/fragment_ai_scene.xml @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/core/function-impl/mogo-core-function-map/src/main/java/com/mogo/eagle/core/function/view/MapBizView.kt b/core/function-impl/mogo-core-function-map/src/main/java/com/mogo/eagle/core/function/view/MapBizView.kt index 7dbeb7c5bf..e7fb87bc64 100644 --- a/core/function-impl/mogo-core-function-map/src/main/java/com/mogo/eagle/core/function/view/MapBizView.kt +++ b/core/function-impl/mogo-core-function-map/src/main/java/com/mogo/eagle/core/function/view/MapBizView.kt @@ -12,6 +12,7 @@ import com.mogo.eagle.core.function.api.autopilot.IMoGoChassisStatesListener import com.mogo.eagle.core.function.call.autopilot.CallerChassisLocationWGS84ListenerManager import com.mogo.eagle.core.function.call.autopilot.CallerChassisStatesListenerManager import com.mogo.eagle.core.function.call.map.CallerMapUIServiceManager +import com.mogo.eagle.core.widget.media.video.TextureVideoViewOutlineProvider import com.mogo.map.MogoMap import com.mogo.map.MogoMapView import com.mogo.map.overlay.line.Polyline @@ -19,6 +20,7 @@ import com.mogo.map.overlay.point.Point import com.mogo.map.overlay.proxy.line.IMapPolylineOverlay import com.mogo.map.overlay.proxy.point.IMapPointOverlay import com.mogo.map.uicontroller.IMogoMapUIController +import me.jessyan.autosize.utils.AutoSizeUtils import kotlin.properties.Delegates @@ -141,4 +143,10 @@ class MapBizView(context: Context?, attrs: AttributeSet?) : MogoMapView(context, super.onDestroy() } + override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { + super.onSizeChanged(w, h, oldw, oldh) + this.outlineProvider = TextureVideoViewOutlineProvider(AutoSizeUtils.dp2px(context,36f).toFloat()) + this.clipToOutline = true + } + } \ No newline at end of file diff --git a/core/mogo-core-data/src/main/java/com/mogo/eagle/core/data/ai/LocationExt.kt b/core/mogo-core-data/src/main/java/com/mogo/eagle/core/data/ai/LocationExt.kt index 24a6c25820..a9885427b7 100644 --- a/core/mogo-core-data/src/main/java/com/mogo/eagle/core/data/ai/LocationExt.kt +++ b/core/mogo-core-data/src/main/java/com/mogo/eagle/core/data/ai/LocationExt.kt @@ -1,7 +1,7 @@ package com.mogo.eagle.core.data.ai import android.location.Location -import com.amap.api.location.AMapLocation +import com.mogo.eagle.core.data.map.MogoLocation import com.mogo.service.v2n.service.socket.bean.MGLocationBean /** @@ -36,15 +36,15 @@ fun MGLocationBean.toCG02Location(): Location { * @receiver MGLocationModel * @return MGLocationBean */ -fun AMapLocation.toMogoLocation(sourceType:Int = 0): MGLocationBean { +fun MogoLocation.toMogoLocation(sourceType:Int = 0): MGLocationBean { val bean = MGLocationBean() - bean.lockType = locationType + bean.lockType = locType bean.coordType = MGLocationBean.COORD_TYPE_GCJ02 bean.accuracy = accuracy bean.altitude = altitude - bean.bearing = bearing - bean.speed = speed - bean.satellites = satellites + bean.bearing = heading.toFloat() + bean.speed = gnssSpeed + bean.satellites = satellite bean.latitude = latitude bean.longitude = longitude bean.adCode = adCode diff --git a/core/mogo-core-data/src/main/java/com/mogo/eagle/core/data/ai/LocationRepository.kt b/core/mogo-core-data/src/main/java/com/mogo/eagle/core/data/ai/LocationRepository.kt index 79442308d2..e9d997c221 100644 --- a/core/mogo-core-data/src/main/java/com/mogo/eagle/core/data/ai/LocationRepository.kt +++ b/core/mogo-core-data/src/main/java/com/mogo/eagle/core/data/ai/LocationRepository.kt @@ -80,48 +80,48 @@ class LocationRepository : IRepository, AMapLocationListener { override fun onLocationChanged(loc: AMapLocation) { // Log.d(TAG, "onLocationChanged:${location.toStr()}") - var location = loc - Log.d( - TAG, - "onLocationChanged:$this , provider=${location.provider}#${location}#speed=${location.speed}#bearing=${location.bearing}" - ) - if (location.latitude <= 0 && location.longitude <= 0) return - - location.cityCode.takeIf { !it.isNullOrEmpty() }?.let { - lastCityCode = it - } - - location.adCode.takeIf { !it.isNullOrEmpty() }?.let { - lastAdCode = it - } - if (location.bearing > 0) { - bear = location.bearing - } - location.bearing = bear - - // 设置固定坐标 - if (DataConfigs.fixedPoint != null) { - location = DataConfigs.fixedPoint!! - } - - lastLocationGcj02 = location - _cg02Flow.tryEmit(location) - - - if (!DataConfigs.useCorrectLocation){ - Log.e(TAG, "CorrectLoc 本地坐标应用为纠偏坐标") - location.toMogoLocation().let { - val latLng = CoordinateUtil.gcj02LngLatToWGS84(it.longitude, it.latitude) - it.latitude = latLng[1]; - it.longitude = latLng[0] - if (!stopLocation) { - instance.lastLastCorrLoc84 = instance.lastCorrLoc84 - instance.lastCorrLoc84 = it - V2XRepository._correctLocFlow.tryEmit(it) - - } - } - return - } +// var location = loc +// Log.d( +// TAG, +// "onLocationChanged:$this , provider=${location.provider}#${location}#speed=${location.speed}#bearing=${location.bearing}" +// ) +// if (location.latitude <= 0 && location.longitude <= 0) return +// +// location.cityCode.takeIf { !it.isNullOrEmpty() }?.let { +// lastCityCode = it +// } +// +// location.adCode.takeIf { !it.isNullOrEmpty() }?.let { +// lastAdCode = it +// } +// if (location.bearing > 0) { +// bear = location.bearing +// } +// location.bearing = bear +// +// // 设置固定坐标 +// if (DataConfigs.fixedPoint != null) { +// location = DataConfigs.fixedPoint!! +// } +// +// lastLocationGcj02 = location +// _cg02Flow.tryEmit(location) +// +// +// if (!DataConfigs.useCorrectLocation){ +// Log.e(TAG, "CorrectLoc 本地坐标应用为纠偏坐标") +// location.toMogoLocation().let { +// val latLng = CoordinateUtil.gcj02LngLatToWGS84(it.longitude, it.latitude) +// it.latitude = latLng[1]; +// it.longitude = latLng[0] +// if (!stopLocation) { +// instance.lastLastCorrLoc84 = instance.lastCorrLoc84 +// instance.lastCorrLoc84 = it +// V2XRepository._correctLocFlow.tryEmit(it) +// +// } +// } +// return +// } } } \ No newline at end of file diff --git a/core/mogo-core-data/src/main/java/com/mogo/eagle/core/data/ai/V2XRepository.kt b/core/mogo-core-data/src/main/java/com/mogo/eagle/core/data/ai/V2XRepository.kt index 0cde0cf44b..6920c2a572 100644 --- a/core/mogo-core-data/src/main/java/com/mogo/eagle/core/data/ai/V2XRepository.kt +++ b/core/mogo-core-data/src/main/java/com/mogo/eagle/core/data/ai/V2XRepository.kt @@ -2,7 +2,7 @@ package com.mogo.eagle.core.data.ai import android.content.Context import android.util.Log -import com.amap.api.location.AMapLocation +import com.mogo.eagle.core.data.map.MogoLocation import com.mogo.service.v2n.MGV2N import com.mogo.service.v2n.MGV2NConst import com.mogo.service.v2n.bean.MGAccidentBean @@ -205,7 +205,7 @@ object V2XRepository : IRepository, IMGV2NListener { /** * 提供v2x坐标 */ - fun provideLocation(loc: AMapLocation, sourceType: Int = 0) { + fun provideLocation(loc: MogoLocation, sourceType: Int = 0) { loc.toMogoLocation(sourceType).also { MGV2N.getInstance().uploadLocation(it) } diff --git a/libraries/mogo-speech/.gitignore b/libraries/mogo-speech/.gitignore new file mode 100644 index 0000000000..f11e27d274 --- /dev/null +++ b/libraries/mogo-speech/.gitignore @@ -0,0 +1,11 @@ +*.iml +.gradle +/local.properties +.idea/ +.DS_Store +._.DS_Store +/build +/captures +.externalNativeBuild +.cxx +.gitlab-ci.yml \ No newline at end of file diff --git a/libraries/mogo-speech/build.gradle b/libraries/mogo-speech/build.gradle new file mode 100644 index 0000000000..449ad99f39 --- /dev/null +++ b/libraries/mogo-speech/build.gradle @@ -0,0 +1,60 @@ +plugins { + id 'com.android.library' + id 'kotlin-android' + id 'kotlin-android-extensions' + id 'kotlin-kapt' +} + +android { + compileSdkVersion rootProject.ext.android.compileSdkVersion + // buildToolsVersion rootProject.ext.android.buildToolsVersion + defaultConfig { + minSdkVersion rootProject.ext.android.minSdkVersion + targetSdkVersion rootProject.ext.android.targetSdkVersion + versionCode Integer.valueOf(VERSION_CODE) + versionName getValueFromRootProperties("${project.name.replace("-", "_").toUpperCase()}_VERSION") + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles 'consumer-rules.pro' + + buildConfigField("String", "BAIDU_APP_ID", "\"${project.property("baiduAppId")}\"") + buildConfigField("String", "BAIDU_API_KEY", "\"${project.property("baiduApiKey")}\"") + buildConfigField("String", "BAIDU_SECRET_KEY", "\"${project.property("baiduSecretKey")}\"") + + //ARouter apt 参数 + kapt { + useBuildCache = false + arguments { + arg("AROUTER_MODULE_NAME", project.getName()) + } + } + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + lintOptions { + abortOnError false + } + +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation rootProject.ext.dependencies.kotlinstdlib + implementation rootProject.ext.dependencies.androidxccorektx + implementation rootProject.ext.dependencies.androidxappcompat + implementation rootProject.ext.dependencies.material + + implementation(files("libs/bdasr_V3_20210628_cfe8c44.jar")) + implementation(files("libs/com.baidu.tts_2.6.3.c2aaa9f_20220922113422.jar")) +} diff --git a/libraries/mogo-speech/consumer-rules.pro b/libraries/mogo-speech/consumer-rules.pro new file mode 100644 index 0000000000..e69de29bb2 diff --git a/libraries/mogo-speech/gradle.properties b/libraries/mogo-speech/gradle.properties new file mode 100644 index 0000000000..220e0a97c6 --- /dev/null +++ b/libraries/mogo-speech/gradle.properties @@ -0,0 +1,8 @@ +GROUP=com.mogo.speech +POM_ARTIFACT_ID=mogo-speech +VERSION_CODE=1 + +#百度语音 +baiduAppId=117782292 +baiduApiKey=N4zeGxC1quVgyEmlmcvrteHC +baiduSecretKey=xNuc1xVgmmW6NWDXYBq6z5e3qxSU5nUa \ No newline at end of file diff --git a/libraries/mogo-speech/libs/bdasr_V3_20210628_cfe8c44.jar b/libraries/mogo-speech/libs/bdasr_V3_20210628_cfe8c44.jar new file mode 100644 index 0000000000..927c2813b5 Binary files /dev/null and b/libraries/mogo-speech/libs/bdasr_V3_20210628_cfe8c44.jar differ diff --git a/libraries/mogo-speech/libs/com.baidu.tts_2.6.3.c2aaa9f_20220922113422.jar b/libraries/mogo-speech/libs/com.baidu.tts_2.6.3.c2aaa9f_20220922113422.jar new file mode 100644 index 0000000000..7a6e87d7f1 Binary files /dev/null and b/libraries/mogo-speech/libs/com.baidu.tts_2.6.3.c2aaa9f_20220922113422.jar differ diff --git a/libraries/mogo-speech/proguard-rules.pro b/libraries/mogo-speech/proguard-rules.pro new file mode 100644 index 0000000000..481bb43481 --- /dev/null +++ b/libraries/mogo-speech/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/libraries/mogo-speech/src/main/AndroidManifest.xml b/libraries/mogo-speech/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..a253f68ad2 --- /dev/null +++ b/libraries/mogo-speech/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + \ No newline at end of file diff --git a/libraries/mogo-speech/src/main/assets/WakeUp.bin b/libraries/mogo-speech/src/main/assets/WakeUp.bin new file mode 100644 index 0000000000..44e9bbac5d --- /dev/null +++ b/libraries/mogo-speech/src/main/assets/WakeUp.bin @@ -0,0 +1 @@ +B_ZBOpfEOpfEOpfEB_ZBOpfEOpfEOpfEB_ZBOpfEOpfEOpfEB_ZBOpfEOpfEOpfEB_ZBOpfEOpfEOpfEB_ZBOpfEOpfEOpfEB_ZBOpfEOpfEOpfEB_ZBOpfEOpfEOpfEB_ZBOpfEOpfEOpfEB_ZBOpfEOpfEOpfEB_ZBOpfEOpfEOpfEB_ZBOpfEOpfEOpfEB_ZBOpfEOpfEOpfEB_ZBOpfEOpfEOpfEB_ZBOpfEOpfEOpfEB_ZBOpfEOpfEOpfEB_ZBOpfEOpfEOpfEB_ZBOpfEOpfEOpfEB_ZBOpfEOpfEOpfEB_ZBOpfEOpfEOpfEB_ZBOpfEOpfEOpfEB_ZBOpfEOpfEOpfEB_ZBOpfEOpfEOpfEB_ZBOpfEOpfEOpfEB_ZBOpfEOpfEOpfEB_ZBOpfEOpfEOpfEB_ZBOpfEOpfEOpfEB_ZBOpfEOpfEOpfEB_ZBOpfEOpfEOpfEB_ZBOpfEOpfEOpfEB_ZBOpfEOpfEOpfEB_ZBOpfEOpfEOpfEB_ZBOpfEOpfEOpfEB_ZBOpfEOpfEOpfEB_ZBOpfEOpfEOpfEB_ZBOpfEOpfEOpfEB_ZBOpfEOpfEOpfEB_ZBOpfEOpfEOpfEB_ZBOpfEOpfEOpfEB_ZBOpfEOpfEOpfEB_ZBOpfEOpfEOpfEB_ZBOpfEOpfEOpfEB_ZBOpfEOpfEOpfEB_ZBOpfEOpfEOpfEB_ZBOpfEOpfEOpfEB_ZBOpfEOpfEOpfEB_ZBOpfEOpfEOpfE \ No newline at end of file diff --git a/libraries/mogo-speech/src/main/java/com/mogo/mgintelligent/speech/AsrResult.kt b/libraries/mogo-speech/src/main/java/com/mogo/mgintelligent/speech/AsrResult.kt new file mode 100644 index 0000000000..79db05a68c --- /dev/null +++ b/libraries/mogo-speech/src/main/java/com/mogo/mgintelligent/speech/AsrResult.kt @@ -0,0 +1,50 @@ +package com.mogo.mgintelligent.speech + +import android.util.Log +import org.json.JSONObject + +data class AsrResult(val type: AsrState, val content: String) + +enum class AsrState { + /** + * 识别中 + */ + STATE_PARTIAL, + + /** + * 识别结束 + */ + STATE_FINAL, + + /** + * 退出识别 + */ + STATE_EXIT, + + /** + * 识别错误 + */ + STATE_ERROR, +} + +fun parseAsrResult(asrResult: String): AsrResult { + Log.d("ASR", "parseAsrResult: ") + val json = JSONObject(asrResult) + val error = json.optInt("error") + val resultType = json.optString("result_type") + val bestResult = json.optString("best_result") + + if (error != 0) { + return AsrResult(AsrState.STATE_ERROR, "识别失败") + } + + val state = when (resultType) { + "partial_result" -> AsrState.STATE_PARTIAL + "final_result" -> AsrState.STATE_FINAL + else -> AsrState.STATE_ERROR + } + val content = if (state == AsrState.STATE_ERROR) { + "识别错误" + } else bestResult + return AsrResult(state, content) +} \ No newline at end of file diff --git a/libraries/mogo-speech/src/main/java/com/mogo/mgintelligent/speech/DefaultSpeechListener.kt b/libraries/mogo-speech/src/main/java/com/mogo/mgintelligent/speech/DefaultSpeechListener.kt new file mode 100644 index 0000000000..d94c36296c --- /dev/null +++ b/libraries/mogo-speech/src/main/java/com/mogo/mgintelligent/speech/DefaultSpeechListener.kt @@ -0,0 +1,22 @@ +package com.mogo.mgintelligent.speech + +import com.baidu.tts.client.SpeechError +import com.baidu.tts.client.SpeechSynthesizerListener + +interface DefaultSpeechListener : SpeechSynthesizerListener { + + override fun onSynthesizeStart(utteranceId: String?) {} + + override fun onSynthesizeDataArrived(utteranceId: String?, var2: ByteArray?, var3: Int, var4: Int) {} + + override fun onSynthesizeFinish(utteranceId: String?) {} + + override fun onSpeechStart(utteranceId: String?) {} + + override fun onSpeechProgressChanged(utteranceId: String?, var2: Int) {} + + override fun onSpeechFinish(utteranceId: String?) {} + + override fun onError(utteranceId: String?, error: SpeechError?) {} + +} \ No newline at end of file diff --git a/libraries/mogo-speech/src/main/java/com/mogo/mgintelligent/speech/IWakeUpListener.kt b/libraries/mogo-speech/src/main/java/com/mogo/mgintelligent/speech/IWakeUpListener.kt new file mode 100644 index 0000000000..9b0311883d --- /dev/null +++ b/libraries/mogo-speech/src/main/java/com/mogo/mgintelligent/speech/IWakeUpListener.kt @@ -0,0 +1,14 @@ +package com.mogo.mgintelligent.speech + +interface IWakeUpListener { + + fun onWakeUp() + + fun onListener(msg: AsrResult) + +} + + +fun interface SpeakCallback { + fun finish() +} \ No newline at end of file diff --git a/libraries/mogo-speech/src/main/java/com/mogo/mgintelligent/speech/MGSpeech.kt b/libraries/mogo-speech/src/main/java/com/mogo/mgintelligent/speech/MGSpeech.kt new file mode 100644 index 0000000000..88c9829bea --- /dev/null +++ b/libraries/mogo-speech/src/main/java/com/mogo/mgintelligent/speech/MGSpeech.kt @@ -0,0 +1,289 @@ +package com.mogo.mgintelligent.speech + +import android.content.Context +import android.os.Handler +import android.os.Looper +import android.util.Log +import com.baidu.speech.EventListener +import com.baidu.speech.EventManager +import com.baidu.speech.EventManagerFactory +import com.baidu.speech.asr.SpeechConstant +import com.baidu.tts.client.SpeechError +import com.baidu.tts.client.SpeechSynthesizeBag +import com.baidu.tts.client.SpeechSynthesizer +import com.baidu.tts.client.TtsMode +import org.json.JSONObject + +object MGSpeech { + private val appId = BuildConfig.BAIDU_APP_ID + private val apiKey = BuildConfig.BAIDU_API_KEY + private val secretKey = BuildConfig.BAIDU_SECRET_KEY + private val mSpeechSynthesizer: SpeechSynthesizer = SpeechSynthesizer.getInstance() + + private var asr: EventManager? = null + private var wp: EventManager? = null + + var weakUpListener: IWakeUpListener? = null + + private val mainHandler = Handler(Looper.getMainLooper()) + + private var isListening = false + + private var currAsrContent = "" + + private var isAssistantShow = false + + var mFinishedCallback: SpeakCallback? = null + + val eventListener = EventListener { name, params, data, offset, length -> + Log.d( + TAG, + "[${Thread.currentThread().name}] onEvent: name=$name, param=$params, data=$data, offset=$offset, length=$length" + ) + postMain { + when (name) { + SpeechConstant.CALLBACK_EVENT_WAKEUP_SUCCESS -> weakUpListener?.onWakeUp() + SpeechConstant.CALLBACK_EVENT_ASR_PARTIAL -> + weakUpListener?.onListener( + parseAsrResult(params).apply { + currAsrContent = content + } + ) + + SpeechConstant.CALLBACK_EVENT_ASR_EXIT -> { + isListening = false + weakUpListener?.onListener(AsrResult(AsrState.STATE_EXIT, currAsrContent)) + } + + SpeechConstant.CALLBACK_EVENT_ASR_FINISH, + SpeechConstant.CALLBACK_EVENT_ASR_CANCEL -> { + isListening = false + } + } + } + } + + private const val TAG = "MGSpeech" + fun init(context: Context) { + + initTts(context) + + initAsr(context) + } + + private fun initAsr(context: Context) { + asr = EventManagerFactory.create(context, "asr") + wp = EventManagerFactory.create(context, "wp") + } + + fun startAsr() { + Log.d(TAG, "startAsr: ") + isListening = true + currAsrContent = "" + val params = mapOf( + SpeechConstant.APP_ID to appId, + SpeechConstant.APP_KEY to apiKey, + SpeechConstant.SECRET to secretKey, + SpeechConstant.ACCEPT_AUDIO_VOLUME to false, + SpeechConstant.VAD to SpeechConstant.VAD_DNN, + SpeechConstant.PID to 1537, + SpeechConstant.LMID to 21867, + SpeechConstant.VAD_ENDPOINT_TIMEOUT to 10000 + ) + + asr?.registerListener(eventListener) + val json = JSONObject(params).toString() + asr?.send(SpeechConstant.ASR_START, json, null, 0, 0) + } + + fun stopAsr() { + Log.d(TAG, "stopAsr: ") + isListening = false + asr?.unregisterListener(eventListener) + asr?.send(SpeechConstant.ASR_STOP, "{}", null, 0, 0) + } + + + fun startWeakUp() { + Log.d(TAG, "startWeakUp: ") + val params = mapOf( + SpeechConstant.APP_ID to appId, + SpeechConstant.APP_KEY to apiKey, + SpeechConstant.SECRET to secretKey, + SpeechConstant.WP_WORDS_FILE to "assets:///WakeUp.bin" + ) + wp?.registerListener(eventListener) + + +// // 复制此段可以自动检测常规错误 +// AutoCheckAsr(context, object : Handler() { +// override fun handleMessage(msg: Message) { +// if (msg.what == 100) { +// val autoCheck = msg.obj as AutoCheckAsr +// synchronized(autoCheck) { +// val message = +// autoCheck.obtainErrorMessage() // autoCheck.obtainAllMessage(); +//// txtLog.append(message + "\n") +// // 可以用下面一行替代,在logcat中查看代码 +// Log.d(TAG, "AutoCheckAsr: $message") +// } +// } +// } +// }, false).checkAsr(params) + + +// val json = JSONObject(params).toString() +// wp?.send(SpeechConstant.WAKEUP_START, json, null, 0, 0) + } + + fun stopWeakUp() { + Log.d(TAG, "stopWeakUp: ") + wp?.unregisterListener(eventListener) + wp?.send(SpeechConstant.WAKEUP_STOP, null, null, 0, 0) + } + + private fun initTts(context: Context) { + mSpeechSynthesizer.setContext(context) + mSpeechSynthesizer.setAppId(appId) + mSpeechSynthesizer.setApiKey(apiKey, secretKey) + mSpeechSynthesizer.initTts(TtsMode.ONLINE) + // 设置在线发声音人: 0 普通女声(默认) 1 普通男声 3 情感男声<度逍遥> 4 情感儿童声<度丫丫>, 其它发音人见文档 + mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_SPEAKER, "0") + // 设置合成的音量,0-15 ,默认 5 + mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_VOLUME, "15") + // 设置合成的语速,0-15 ,默认 5 + mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_SPEED, "7") + // 设置合成的语调,0-15 ,默认 5 + mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_PITCH, "5") + +// mSpeechSynthesizer.setParam( +// SpeechSynthesizer.PARAM_TTS_TEXT_MODEL_FILE, +// "assets:///bd_etts_common_text_txt_all_mand_eng_middle_big_v4.1.0_20230423.dat" +// ) +// mSpeechSynthesizer.setParam( +// SpeechSynthesizer.PARAM_TTS_SPEECH_MODEL_FILE, +// "assets:///bd_etts_common_speech_duyaya_mand_eng_high_am-style24k_v4.6.0_20210721_20220822104311.dat" +// ) + +// val params: MutableMap = HashMap() +// // 以下参数均为选填 +// // 设置在线发声音人: 0 普通女声(默认) 1 普通男声 3 情感男声<度逍遥> 4 情感儿童声<度丫丫>, 其它发音人见文档 +// params[SpeechSynthesizer.PARAM_SPEAKER] = "5118" +// // 设置合成的音量,0-15 ,默认 5 +// params[SpeechSynthesizer.PARAM_VOLUME] = "15" +// // 设置合成的语速,0-15 ,默认 5 +// params[SpeechSynthesizer.PARAM_SPEED] = "5" +// // 设置合成的语调,0-15 ,默认 5 +// params[SpeechSynthesizer.PARAM_PITCH] = "5" + // + // val initConfig = InitConfig(appId, apiKey, secretKey, TtsMode.ONLINE, params, null) +// // 如果您集成中出错,请将下面一段代码放在和demo中相同的位置,并复制InitConfig 和 AutoCheck到您的项目中 +// // 上线时请删除AutoCheck的调用 +// AutoCheck.getInstance(context.getApplicationContext()) +// .check(initConfig, object : Handler() { +// override fun handleMessage(msg: Message) { +// if (msg.what == 100) { +// val autoCheck: AutoCheck = msg.obj as AutoCheck +// synchronized(autoCheck) { +// val message: String = autoCheck.obtainDebugMessage() +// +// Log.e(TAG, "handleMessage: $message") +// } +// } +// } +// }) + + mSpeechSynthesizer.setSpeechSynthesizerListener(object : DefaultSpeechListener { + override fun onSpeechStart(utteranceId: String?) { + Log.d(TAG, "[${Thread.currentThread().name}] onSpeechStart: $utteranceId") + } + + override fun onSpeechFinish(utteranceId: String?) { + Log.d(TAG, "[${Thread.currentThread().name}] onSpeechFinish: $utteranceId") + if (utteranceId == null) { + return + } + val progress = utteranceId.toFloatOrNull() ?: return + if (progress == 1F) { + postMain { + mFinishedCallback?.finish() + } + } + } + + override fun onError(utteranceId: String?, error: SpeechError?) { + Log.d(TAG, "[${Thread.currentThread().name}] onError: $utteranceId, $error") + if (utteranceId == null) { + return + } + + val progress = utteranceId.toFloatOrNull() ?: return + if (progress == 1F) { + postMain { + mFinishedCallback?.finish() + } + } + } + }) + } + + fun speak(text: String, isAssistant:Boolean = false, callback: SpeakCallback? = null) { + if(text.isEmpty()){ + Log.d(TAG, "speak: 空文本") + mFinishedCallback = callback + callback?.finish() + return + } + + if (isListening) { + Log.d(TAG, "speak: 当前正在语音识别,本次tts取消,$text") + return + } + + if (isAssistantShow && !isAssistant) { + Log.d(TAG, "speak: 当前语音助手正在展示,本次tts取消,$text") + return + } + Log.d(TAG, "speak: $text") + mSpeechSynthesizer.stop() + mFinishedCallback = callback + + if (text.length <= 40) { + mSpeechSynthesizer.speak(text, "1") + return + } + + val subTxtList = text.split("[。,;,.;]".toRegex()).filter { it.isNotEmpty() } + val subTxtSize = subTxtList.size + Log.d(TAG, "speak bags: $subTxtSize") + if (subTxtSize == 1) { + mSpeechSynthesizer.speak(text, "1") + return + } + + val bags = subTxtList.mapIndexed { index, txt -> +// //需要合成的文本text的长度不能超过120个GBK字节。 + val id = "${(index + 1F) / subTxtSize}" + Log.d(TAG, "speak bags: utteranceId=$id, text=$txt ") + SpeechSynthesizeBag().apply { + setText(txt) + utteranceId = id + } + } + + mSpeechSynthesizer.batchSpeak(bags) + } + + fun isAssistantShow(isAssistantShow: Boolean) { + this.isAssistantShow = isAssistantShow + } + + fun stopSpeak() { + mSpeechSynthesizer.stop() + } + + fun postMain(runnable: Runnable) { + mainHandler.post(runnable) + } + +} \ No newline at end of file diff --git a/libraries/mogo-speech/src/main/java/com/mogo/mgintelligent/speech/debug/AutoCheck.java b/libraries/mogo-speech/src/main/java/com/mogo/mgintelligent/speech/debug/AutoCheck.java new file mode 100644 index 0000000000..8897d96c51 --- /dev/null +++ b/libraries/mogo-speech/src/main/java/com/mogo/mgintelligent/speech/debug/AutoCheck.java @@ -0,0 +1,460 @@ +package com.mogo.mgintelligent.speech.debug; + +import android.Manifest; +import android.content.Context; +import android.content.pm.PackageManager; +import android.os.Handler; +import android.os.Message; + +import androidx.core.content.ContextCompat; + +import com.baidu.tts.client.SpeechSynthesizer; +import com.baidu.tts.client.SynthesizerTool; +import com.baidu.tts.client.TtsMode; + +import org.json.JSONObject; + +import java.io.BufferedReader; +import java.io.File; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.TreeSet; + +/** + * 自动排查工具,用于集成后发现错误。 + *

+ * 可以检测如下错误: + * 1. PermissionCheck : AndroidManifest,xml 需要的部分权限 + * 2. JniCheck: 检测so文件是否安装在指定目录 + * 3. AppInfoCheck: 联网情况下 , 检测appId appKey secretKey是否正确 + * 4. ApplicationIdCheck: 显示包名applicationId, 提示用户手动去官网检查 + * 5. ParamKeyExistCheck: 检查key是否存在,目前检查 SpeechSynthesizer.PARAM_TTS_TEXT_MODEL_FILE + * 和PARAM_TTS_SPEECH_MODEL_FILE + * 6. OfflineResourceFileCheck 检查离线资源文件(需要从assets目录下复制),是否存在 + *

+ *

+ * 示例使用代码: + * AutoCheck.getInstance(getApplicationContext()).check(initConfig, new Handler() { + * + * @Override public void handleMessage(Message msg) { + * if (msg.what == 100) { + * AutoCheck autoCheck = (AutoCheck) msg.obj; + * synchronized (autoCheck) { + * String message = autoCheck.obtainDebugMessage(); + * toPrint(message); // 可以用下面一行替代,在logcat中查看代码 + * //Log.w("AutoCheckMessage",message); + * } + * } + * } + *

+ * }); + */ +public class AutoCheck { + + private static AutoCheck instance; + + private LinkedHashMap checks; + + private static Context context; + + private boolean hasError = false; + + volatile boolean isFinished = false; + + /** + * 获取实例,非线程安全 + * + * @return + */ + public static AutoCheck getInstance(Context context) { + if (instance == null || AutoCheck.context != context) { + instance = new AutoCheck(context); + } + return instance; + } + + public void check(final InitConfig initConfig, final Handler handler) { + Thread t = new Thread(new Runnable() { + @Override + public void run() { + AutoCheck obj = innerCheck(initConfig); + isFinished = true; + synchronized (obj) { // 偶发,同步线程信息 + Message msg = handler.obtainMessage(100, obj); + handler.sendMessage(msg); + } + } + }); + t.start(); + + } + + private AutoCheck innerCheck(InitConfig config) { + boolean isOnlineSdk = TtsMode.ONLINE.equals(config.getTtsMode()); + checks.put("检查申请的Android权限", new PermissionCheck(context)); + checks.put("检查4个so文件是否存在", new JniCheck(context, isOnlineSdk)); + checks.put("检查AppId AppKey SecretKey", + new AppInfoCheck(config.getAppId(), config.getAppKey(), config.getSecretKey())); + checks.put("检查包名", new ApplicationIdCheck(context, config.getAppId())); + + if (!isOnlineSdk) { + Map params = config.getParams(); + String fileKey = SpeechSynthesizer.PARAM_TTS_TEXT_MODEL_FILE; + checks.put("检查离线资TEXT文件参数", new ParamKeyExistCheck(params, fileKey, + "SpeechSynthesizer.PARAM_TTS_TEXT_MODEL_FILE未设置 ,")); + checks.put("检查离线资源TEXT文件", new OfflineResourceFileCheck(params.get(fileKey))); + fileKey = SpeechSynthesizer.PARAM_TTS_SPEECH_MODEL_FILE; + checks.put("检查离线资Speech文件参数", new ParamKeyExistCheck(params, fileKey, + "SpeechSynthesizer.PARAM_TTS_SPEECH_MODEL_FILE未设置 ,")); + checks.put("检查离线资源Speech文件", new OfflineResourceFileCheck(params.get(fileKey))); + } + + for (Map.Entry e : checks.entrySet()) { + Check check = e.getValue(); + check.check(); + if (check.hasError()) { + break; + } + } + return this; + } + + public String obtainErrorMessage() { + PrintConfig config = new PrintConfig(); + return formatString(config); + } + + public String obtainDebugMessage() { + PrintConfig config = new PrintConfig(); + config.withInfo = true; + return formatString(config); + } + + public String obtainAllMessage() { + PrintConfig config = new PrintConfig(); + config.withLog = true; + config.withInfo = true; + return formatString(config); + } + + public String formatString(PrintConfig config) { + StringBuilder sb = new StringBuilder(); + hasError = false; + + for (HashMap.Entry entry : checks.entrySet()) { + Check check = entry.getValue(); + String testName = entry.getKey(); + if (check.hasError()) { + if (!hasError) { + hasError = true; + } + + sb.append("【错误】【").append(testName).append(" 】 ").append(check.getErrorMessage()).append("\n"); + if (check.hasFix()) { + sb.append("【修复方法】【").append(testName).append(" 】 ").append(check.getFixMessage()).append("\n"); + } + } + if (config.withInfo && check.hasInfo()) { + sb.append("【请手动检查】【").append(testName).append("】 ").append(check.getInfoMessage()).append("\n"); + } + if (config.withLog && (config.withLogOnSuccess || hasError) && check.hasLog()) { + sb.append("【log】:" + check.getLogMessage()).append("\n"); + } + } + if (!hasError) { + sb.append("集成自动排查工具: 恭喜没有检测到任何问题\n"); + } + return sb.toString(); + } + + public void clear() { + checks.clear(); + hasError = false; + } + + private AutoCheck(Context context) { + this.context = context; + checks = new LinkedHashMap<>(); + } + + private static class PrintConfig { + public boolean withFix = true; + public boolean withInfo = false; + public boolean withLog = false; + public boolean withLogOnSuccess = false; + } + + private static class PermissionCheck extends Check { + private Context context; + + public PermissionCheck(Context context) { + this.context = context; + } + + @Override + public void check() { + String[] permissions = { + Manifest.permission.INTERNET, + Manifest.permission.ACCESS_NETWORK_STATE, + Manifest.permission.MODIFY_AUDIO_SETTINGS, + // Manifest.permission.WRITE_EXTERNAL_STORAGE, + // Manifest.permission.WRITE_SETTINGS, + Manifest.permission.ACCESS_WIFI_STATE, + // Manifest.permission.CHANGE_WIFI_STATE + }; + + ArrayList toApplyList = new ArrayList(); + + for (String perm : permissions) { + if (PackageManager.PERMISSION_GRANTED != ContextCompat.checkSelfPermission(context, perm)) { + toApplyList.add(perm); + // 进入到这里代表没有权限. + } + } + if (!toApplyList.isEmpty()) { + errorMessage = "缺少权限:" + toApplyList; + fixMessage = "请从AndroidManifest.xml复制相关权限"; + } + } + } + + private static class JniCheck extends Check { + private Context context; + + private String[] soNames; + + public JniCheck(Context context, boolean isOnlineSdk) { + this.context = context; + if (isOnlineSdk) { + soNames = new String[]{"libBDSpeechDecoder_V1.so"}; + } else { + soNames = new String[]{"libbd_etts.so", "libBDSpeechDecoder_V1.so", "libgnustl_shared.so"}; + } + } + + @Override + public void check() { + String path = context.getApplicationInfo().nativeLibraryDir; + appendLogMessage("Jni so文件目录 " + path); + File[] files = new File(path).listFiles(); + TreeSet set = new TreeSet<>(); + if (files != null) { + for (File file : files) { + if (file.canRead()) { + set.add(file.getName()); + } + } + } + appendLogMessage("Jni目录内文件: " + set.toString()); + for (String name : soNames) { + if (!set.contains(name)) { + errorMessage = "Jni目录" + path + " 缺少可读的so文件:" + name + ", 该目录文件列表: " + set.toString(); + fixMessage = "如果您的app内没有其它so文件,请复制demo里的src/main/jniLibs至同名目录。" + + " 如果app内有so文件,请合并目录放一起(注意目录取交集,多余的目录删除)。"; + break; + } + } + } + } + + private static class ParamKeyExistCheck extends Check { + private Map params; + private String key; + private String prefixErrorMessage; + + public ParamKeyExistCheck(Map params, String key, String prefixErrorMessage) { + this.params = params; + this.key = key; + this.prefixErrorMessage = prefixErrorMessage; + } + + @Override + public void check() { + if (params == null || !params.containsKey(key)) { + errorMessage = prefixErrorMessage + " 参数中没有设置:" + key; + fixMessage = "请参照demo在设置 " + key + "参数"; + } + } + } + + private static class OfflineResourceFileCheck extends Check { + private String filename; + + public OfflineResourceFileCheck(String filename) { + this.filename = filename; + } + + @Override + public void check() { + File file = new File(filename); + if (!file.exists()) { + errorMessage = "资源文件不存在:" + filename; + } else if (!file.canRead()) { + errorMessage = "资源文件不可读:" + filename; + } else if (!SynthesizerTool.verifyModelFile(filename)) { + errorMessage = "SDK verifyModelFile方法判断模型文件不是有效的,请重新复制:" + filename; + } + if (hasError()) { + fixMessage = "请将demo中src/main/assets目录下同名文件复制到 " + filename; + } + } + } + + private static class ApplicationIdCheck extends Check { + + private String appId; + private Context context; + + public ApplicationIdCheck(Context context, String appId) { + this.appId = appId; + this.context = context; + } + + @Override + public void check() { + infoMessage = "如果您集成过程中遇见离线合成初始化问题,请检查网页上appId:" + appId + + " 应用是否开通了合成服务,并且网页上的应用填写了Android包名:" + + getApplicationId(); + } + + private String getApplicationId() { + return context.getPackageName(); + } + } + + + private static class AppInfoCheck extends Check { + private String appId; + private String appKey; + private String secretKey; + + public AppInfoCheck(String appId, String appKey, String secretKey) { + this.appId = appId; + this.appKey = appKey; + this.secretKey = secretKey; + } + + + public void check() { + do { + appendLogMessage("try to check appId " + appId + " ,appKey=" + appKey + " ,secretKey" + secretKey); + if (appId == null || appId.isEmpty()) { + errorMessage = "appId 为空"; + fixMessage = "填写appID"; + break; + } + if (appKey == null || appKey.isEmpty()) { + errorMessage = "appKey 为空"; + fixMessage = "填写appID"; + break; + } + if (secretKey == null || secretKey.isEmpty()) { + errorMessage = "secretKey 为空"; + fixMessage = "secretKey"; + break; + } + } while (false); + try { + checkOnline(); + } catch (UnknownHostException e) { + infoMessage = "无网络或者网络不连通,忽略检测 : " + e.getMessage(); + } catch (Exception e) { + errorMessage = e.getClass().getCanonicalName() + ":" + e.getMessage(); + fixMessage = " 重新检测appId, appKey, appSecret是否正确"; + } + } + + public void checkOnline() throws Exception { + String urlpath = "https://openapi.baidu.com/oauth/2.0/token?grant_type=client_credentials&client_id=" + + appKey + "&client_secret=" + secretKey; + URL url = new URL(urlpath); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + conn.setRequestMethod("GET"); + conn.setConnectTimeout(1000); + InputStream is = conn.getInputStream(); + BufferedReader reader = new BufferedReader(new InputStreamReader(is)); + StringBuilder result = new StringBuilder(); + String line = ""; + do { + line = reader.readLine(); + if (line != null) { + result.append(line); + } + } while (line != null); + String res = result.toString(); + appendLogMessage("openapi return " + res); + JSONObject jsonObject = new JSONObject(res); + String error = jsonObject.optString("error"); + if (error != null && !error.isEmpty()) { + throw new Exception("appkey secretKey 错误" + ", error:" + error + ", json is" + result); + } + String token = jsonObject.getString("access_token"); + if (token == null || !token.endsWith("-" + appId)) { + throw new Exception("appId 与 appkey及 appSecret 不一致。appId = " + appId + " ,token = " + token); + } + } + + + } + + private abstract static class Check { + protected String errorMessage = null; + + protected String fixMessage = null; + + protected String infoMessage = null; + + protected StringBuilder logMessage; + + public Check() { + logMessage = new StringBuilder(); + } + + public abstract void check(); + + public boolean hasError() { + return errorMessage != null; + } + + public boolean hasFix() { + return fixMessage != null; + } + + public boolean hasInfo() { + return infoMessage != null; + } + + public boolean hasLog() { + return !logMessage.toString().isEmpty(); + } + + public void appendLogMessage(String message) { + logMessage.append(message + "\n"); + } + + public String getErrorMessage() { + return errorMessage; + } + + public String getFixMessage() { + return fixMessage; + } + + public String getInfoMessage() { + return infoMessage; + } + + public String getLogMessage() { + return logMessage.toString(); + } + + + } +} diff --git a/libraries/mogo-speech/src/main/java/com/mogo/mgintelligent/speech/debug/AutoCheckAsr.java b/libraries/mogo-speech/src/main/java/com/mogo/mgintelligent/speech/debug/AutoCheckAsr.java new file mode 100644 index 0000000000..396c0cb941 --- /dev/null +++ b/libraries/mogo-speech/src/main/java/com/mogo/mgintelligent/speech/debug/AutoCheckAsr.java @@ -0,0 +1,472 @@ +package com.mogo.mgintelligent.speech.debug; + +import android.Manifest; +import android.content.Context; +import android.content.pm.PackageManager; +import android.os.Handler; +import android.os.Message; +import android.util.Log; + +import androidx.core.content.ContextCompat; + +import com.baidu.speech.asr.SpeechConstant; + +import org.json.JSONObject; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.URL; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.TreeSet; + +import javax.net.ssl.HttpsURLConnection; + +public class AutoCheckAsr { + public static final boolean isOnlineLited = false; // 是否只需要是纯在线识别功能 + private LinkedHashMap checks; + + private Context context; + private Handler handler; + + private boolean hasError; + private boolean enableOffline; + private boolean isFinished = false; + + private String name; + + private static final String TAG = "AutoCheck"; + + public AutoCheckAsr(Context context, final Handler handler, boolean enableOffline) { + this.context = context; + checks = new LinkedHashMap<>(); + this.handler = handler; + this.enableOffline = enableOffline; + } + + public void checkAsr(final Map params) { + Thread t = new Thread(new Runnable() { + @Override + public void run() { + AutoCheckAsr obj = checkAsrInternal(params); + name = "识别"; + synchronized (obj) { // 偶发,同步线程信息 + isFinished = true; + Message msg = handler.obtainMessage(100, obj); + handler.sendMessage(msg); + } + } + }); + t.start(); + } + + public String obtainErrorMessage() { + PrintConfig config = new PrintConfig(); + return formatString(config); + } + + public String obtainDebugMessage() { + PrintConfig config = new PrintConfig(); + config.withInfo = true; + return formatString(config); + } + + public String obtainAllMessage() { + PrintConfig config = new PrintConfig(); + config.withLog = true; + config.withInfo = true; + config.withLogOnSuccess = true; + return formatString(config); + } + + private String formatString(PrintConfig config) { + StringBuilder sb = new StringBuilder(); + hasError = false; + + for (HashMap.Entry entry : checks.entrySet()) { + Check check = entry.getValue(); + String testName = entry.getKey(); + if (check.hasError()) { + if (!hasError) { + hasError = true; + } + + sb.append("【错误】【").append(testName).append(" 】 ").append(check.getErrorMessage()).append("\n"); + Log.e("AutoCheck", sb.toString()); + if (check.hasFix()) { + sb.append("【修复方法】【").append(testName).append(" 】 ").append(check.getFixMessage()).append("\n"); + } + } else if (config.withEachCheckInfo) { + sb.append("【无报错】【").append(testName).append(" 】 ").append("\n"); + } + if (config.withInfo && check.hasInfo()) { + sb.append("【请手动检查】【").append(testName).append("】 ").append(check.getInfoMessage()).append("\n"); + } + if (config.withLog && (config.withLogOnSuccess || hasError) && check.hasLog()) { + sb.append("【log】:" + check.getLogMessage()).append("\n"); + } + } + if (!hasError) { + sb.append("【" + name + "】集成自动排查工具: 恭喜没有检测到任何问题\n"); + } + return sb.toString(); + } + + private AutoCheckAsr checkAsrInternal(Map params) { + commonSetting(params); + checks.put("外部音频文件存在校验", new FileCheck(context, params, SpeechConstant.IN_FILE)); + checks.put("离线命令词及本地语义bsg文件存在校验", + new FileCheck(context, params, SpeechConstant.ASR_OFFLINE_ENGINE_GRAMMER_FILE_PATH)); + for (Map.Entry e : checks.entrySet()) { + Check check = e.getValue(); + check.check(); + if (check.hasError()) { + break; + } + } + return this; + } + + private void commonSetting(Map params) { + checks.put("检查申请的Android权限", new PermissionCheck(context)); + checks.put("检查so文件是否存在", new JniCheck(context)); + AppInfoCheck infoCheck = null; + try { + infoCheck = new AppInfoCheck(context, params); + checks.put("检查AppId AppKey SecretKey", infoCheck); + } catch (PackageManager.NameNotFoundException e) { + e.printStackTrace(); + Log.e(TAG, "检查AppId AppKey SecretKey 错误", e); + return; + } + if (enableOffline) { + checks.put("检查包名", new ApplicationIdCheck(context, infoCheck.appId)); + } + + } + + private static class PrintConfig { + public boolean withEachCheckInfo = false; + public boolean withInfo = false; + public boolean withLog = false; + public boolean withLogOnSuccess = false; + } + + + private static class PermissionCheck extends Check { + private Context context; + + public PermissionCheck(Context context) { + this.context = context; + } + + @Override + public void check() { + String[] permissions = { + Manifest.permission.RECORD_AUDIO, + Manifest.permission.ACCESS_NETWORK_STATE, + Manifest.permission.INTERNET, + // Manifest.permission.WRITE_EXTERNAL_STORAGE, + }; + + ArrayList toApplyList = new ArrayList(); + for (String perm : permissions) { + if (PackageManager.PERMISSION_GRANTED != ContextCompat.checkSelfPermission(context, perm)) { + toApplyList.add(perm); + // 进入到这里代表没有权限. + } + } + if (!toApplyList.isEmpty()) { + errorMessage = "缺少权限:" + toApplyList; + fixMessage = "请从AndroidManifest.xml复制相关权限"; + } + } + } + + private static class JniCheck extends Check { + private Context context; + + private String[] soNames; + + public JniCheck(Context context) { + this.context = context; + if (isOnlineLited) { + soNames = new String[]{"libBaiduSpeechSDK.so", "libvad.dnn.so"}; + } else { + soNames = new String[]{"libBaiduSpeechSDK.so", "libvad.dnn.so", + "libbd_easr_s1_merge_normal_20151216.dat.so", "libbdEASRAndroid.so", + "libbdSpilWakeup.so"}; + } + } + + @Override + public void check() { + String path = context.getApplicationInfo().nativeLibraryDir; + appendLogMessage("Jni so文件目录 " + path); + File[] files = new File(path).listFiles(); + TreeSet set = new TreeSet<>(); + if (files != null) { + for (File file : files) { + set.add(file.getName()); + } + } + // String debugMessage = "Jni目录内文件: " + set.toString(); + // boolean isSuccess = true; + for (String name : soNames) { + if (!set.contains(name)) { + errorMessage = "Jni目录" + path + " 缺少so文件:" + name + ", 该目录文件列表: " + set.toString(); + fixMessage = "如果您的app内没有其它so文件,请复制demo里的src/main/jniLibs至同名目录。" + + " 如果app内有so文件,请合并目录放一起(注意目录取交集,多余的目录删除)。"; + break; + } + } + } + } + + private static class AppInfoCheck extends Check { + private String appId; + private String appKey; + private String secretKey; + + public AppInfoCheck(Context context, Map params) throws PackageManager.NameNotFoundException { + + if (params.get(SpeechConstant.APP_ID) != null) { + appId = params.get(SpeechConstant.APP_ID).toString(); + } + if (params.get(SpeechConstant.APP_KEY) != null) { + appKey = params.get(SpeechConstant.APP_KEY).toString(); + } + + if (params.get(SpeechConstant.SECRET) != null) { + secretKey = params.get(SpeechConstant.SECRET).toString(); + } + } + + + public void check() { + do { + appendLogMessage("try to check appId " + appId + " ,appKey=" + appKey + " ,secretKey" + secretKey); + if (appId == null || appId.isEmpty()) { + errorMessage = "appId 为空"; + fixMessage = "填写appID"; + break; + } + if (appKey == null || appKey.isEmpty()) { + errorMessage = "appKey 为空"; + fixMessage = "填写appID"; + break; + } + if (secretKey == null || secretKey.isEmpty()) { + errorMessage = "secretKey 为空"; + fixMessage = "secretKey"; + break; + } + + + try { + checkOnline(); + } catch (UnknownHostException e) { + infoMessage = "无网络或者网络不连通,忽略检测 : " + e.getMessage(); + } catch (Exception e) { + errorMessage = e.getClass().getCanonicalName() + ":" + e.getMessage(); + fixMessage = " 重新检测appId, appKey, appSecret是否正确"; + } + } while (false); + } + + public void checkOnline() throws Exception { + String urlpath = "https://openapi.baidu.com/oauth/2.0/token?client_id=" + + appKey + "&client_secret=" + secretKey + "&grant_type=client_credentials"; + Log.i("AutoCheck", "Url is " + urlpath); + URL url = new URL(urlpath); + HttpsURLConnection conn = (HttpsURLConnection) url.openConnection(); + conn.setRequestMethod("GET"); + conn.setConnectTimeout(1000); + InputStream is = conn.getInputStream(); + BufferedReader reader = new BufferedReader(new InputStreamReader(is)); + StringBuilder result = new StringBuilder(); + String line = ""; + do { + line = reader.readLine(); + if (line != null) { + result.append(line); + } + } while (line != null); + String res = result.toString(); + if (!res.contains("audio_voice_assistant_get")) { + errorMessage = "appid:" + appId + ",没有audio_voice_assistant_get 权限,请在网页上开通\"语音识别\"能力"; + fixMessage = "secretKey"; + return; + } + appendLogMessage("openapi return " + res); + JSONObject jsonObject = new JSONObject(res); + String error = jsonObject.optString("error"); + if (error != null && !error.isEmpty()) { + errorMessage = "appkey secretKey 错误" + ", error:" + error + ", json is" + result; + fixMessage = " 重新检测appId对应的 appKey, appSecret是否正确"; + return; + } + String token = jsonObject.getString("access_token"); + if (token == null || !token.endsWith("-" + appId)) { + errorMessage = "appId 与 appkey及 appSecret 不一致。appId = " + appId + " ,token = " + token; + fixMessage = " 重新检测appId对应的 appKey, appSecret是否正确"; + } + } + } + + private static class ApplicationIdCheck extends Check { + + private String appId; + private Context context; + + public ApplicationIdCheck(Context context, String appId) { + this.appId = appId; + this.context = context; + } + + @Override + public void check() { + infoMessage = "如果您集成过程中遇见离线命令词或者唤醒初始化问题,请检查网页上appId:" + appId + + " 应用填写了Android包名:" + + getApplicationId(); + } + + private String getApplicationId() { + return context.getPackageName(); + } + } + + private static class FileCheck extends Check { + private Map params; + private String key; + private Context context; + private boolean allowRes = false; + private boolean allowAssets = true; + + public FileCheck(Context context, Map params, String key) { + this.context = context; + this.params = params; + this.key = key; + if (key.equals(SpeechConstant.IN_FILE)) { + allowRes = true; + allowAssets = false; + } + } + + @Override + public void check() { + if (!params.containsKey(key)) { + return; + } + String value = params.get(key).toString(); + if (allowAssets) { + int len = "assets".length(); + int totalLen = len + ":///".length(); + if (value.startsWith("assets")) { + String filename = value.substring(totalLen); + if (!":///".equals(value.substring(len, totalLen)) || filename.isEmpty()) { + errorMessage = "参数:" + key + "格式错误:" + value; + fixMessage = "修改成" + "assets:///sdcard/xxxx.yyy"; + } + try { + context.getAssets().open(filename); + } catch (IOException e) { + errorMessage = "assets 目录下,文件不存在:" + filename; + fixMessage = "demo的assets目录是:src/main/assets"; + e.printStackTrace(); + } + appendLogMessage("assets 检验完毕:" + filename); + } + } + if (allowRes) { + int len = "res".length(); + int totalLen = len + ":///".length(); + if (value.startsWith("res")) { + String filename = value.substring(totalLen); + if (!":///".equals(value.substring(len, totalLen)) || filename.isEmpty()) { + errorMessage = "参数:" + key + "格式错误:" + value; + fixMessage = "修改成" + "res:///com/baidu/android/voicedemo/16k_test.pcm"; + } + InputStream is = getClass().getClassLoader().getResourceAsStream(filename); + if (is == null) { + errorMessage = "res,文件不存在:" + filename; + fixMessage = "demo的res目录是:app/src/main/resources"; + } else { + try { + is.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + appendLogMessage("res 检验完毕:" + filename); + } + } + if (value.startsWith("/")) { + if (!new File(value).canRead()) { + errorMessage = "文件不存在:" + value; + fixMessage = "请查看文件是否存在"; + } + appendLogMessage("文件路径 检验完毕:" + value); + } + } + } + + private abstract static class Check { + protected String errorMessage = null; + + protected String fixMessage = null; + + protected String infoMessage = null; + + protected StringBuilder logMessage; + + public Check() { + logMessage = new StringBuilder(); + } + + public abstract void check(); + + public boolean hasError() { + return errorMessage != null; + } + + public boolean hasFix() { + return fixMessage != null; + } + + public boolean hasInfo() { + return infoMessage != null; + } + + public boolean hasLog() { + return !logMessage.toString().isEmpty(); + } + + public void appendLogMessage(String message) { + logMessage.append(message + "\n"); + } + + public String getErrorMessage() { + return errorMessage; + } + + public String getFixMessage() { + return fixMessage; + } + + public String getInfoMessage() { + return infoMessage; + } + + public String getLogMessage() { + return logMessage.toString(); + } + } +} + diff --git a/libraries/mogo-speech/src/main/java/com/mogo/mgintelligent/speech/debug/InitConfig.java b/libraries/mogo-speech/src/main/java/com/mogo/mgintelligent/speech/debug/InitConfig.java new file mode 100644 index 0000000000..83f7a587ef --- /dev/null +++ b/libraries/mogo-speech/src/main/java/com/mogo/mgintelligent/speech/debug/InitConfig.java @@ -0,0 +1,98 @@ +package com.mogo.mgintelligent.speech.debug; + +import com.baidu.tts.client.SpeechSynthesizerListener; +import com.baidu.tts.client.TtsMode; + +import java.util.Map; + +/** + * 合成引擎的初始化参数 + *

+ * Created by fujiayi on 2017/9/13. + */ + +public class InitConfig { + /** + * appId appKey 和 secretKey。注意如果需要离线合成功能,请在您申请的应用中填写包名。 + * 本demo的包名是com.baidu.tts.sample,定义在build.gradle中。 + */ + private String appId; + + private String appKey; + + private String secretKey; + + private String sn; + + /** + * 纯在线或者离在线融合、纯离线 + */ + private TtsMode ttsMode; + + + /** + * 初始化的其它参数,用于setParam + */ + private Map params; + + /** + * 合成引擎的回调 + */ + private SpeechSynthesizerListener listener; + + private InitConfig() { + + } + + // 在线SDK用 + public InitConfig(String appId, String appKey, String secretKey, TtsMode ttsMode, + Map params, SpeechSynthesizerListener listener) { + this.appId = appId; + this.appKey = appKey; + this.secretKey = secretKey; + this.ttsMode = ttsMode; + this.params = params; + this.listener = listener; + } + + + // 纯离线SDK用 + public InitConfig(String appId, String appKey, String secretKey, String sn, TtsMode ttsMode, + Map params, SpeechSynthesizerListener listener) { + this(appId, appKey, secretKey, ttsMode, params, listener); + this.sn = sn; + if (sn != null) { + // 纯离线sdk 才有的参数;在线版本没有 +// params.put(IOfflineResourceConst.PARAM_SN_NAME, sn); + } + } + + public SpeechSynthesizerListener getListener() { + return listener; + } + + public Map getParams() { + return params; + } + + + public String getAppId() { + return appId; + } + + public String getAppKey() { + return appKey; + } + + public String getSecretKey() { + return secretKey; + } + + public TtsMode getTtsMode() { + return ttsMode; + } + + public String getSn() { + return sn; + } +} diff --git a/libraries/mogo-speech/src/main/jniLibs/arm64-v8a/libBDSpeechDecoder_V1.so b/libraries/mogo-speech/src/main/jniLibs/arm64-v8a/libBDSpeechDecoder_V1.so new file mode 100644 index 0000000000..a8107b157e Binary files /dev/null and b/libraries/mogo-speech/src/main/jniLibs/arm64-v8a/libBDSpeechDecoder_V1.so differ diff --git a/libraries/mogo-speech/src/main/jniLibs/arm64-v8a/libBaiduSpeechSDK.so b/libraries/mogo-speech/src/main/jniLibs/arm64-v8a/libBaiduSpeechSDK.so new file mode 100644 index 0000000000..2f16021408 Binary files /dev/null and b/libraries/mogo-speech/src/main/jniLibs/arm64-v8a/libBaiduSpeechSDK.so differ diff --git a/libraries/mogo-speech/src/main/jniLibs/arm64-v8a/libbdEASRAndroid.so b/libraries/mogo-speech/src/main/jniLibs/arm64-v8a/libbdEASRAndroid.so new file mode 100644 index 0000000000..c824ce9e50 Binary files /dev/null and b/libraries/mogo-speech/src/main/jniLibs/arm64-v8a/libbdEASRAndroid.so differ diff --git a/libraries/mogo-speech/src/main/jniLibs/arm64-v8a/libbdSpilWakeup.so b/libraries/mogo-speech/src/main/jniLibs/arm64-v8a/libbdSpilWakeup.so new file mode 100644 index 0000000000..a4816337b9 Binary files /dev/null and b/libraries/mogo-speech/src/main/jniLibs/arm64-v8a/libbdSpilWakeup.so differ diff --git a/libraries/mogo-speech/src/main/jniLibs/arm64-v8a/libbd_easr_s1_merge_normal_20151216.dat.so b/libraries/mogo-speech/src/main/jniLibs/arm64-v8a/libbd_easr_s1_merge_normal_20151216.dat.so new file mode 100644 index 0000000000..826c81fc5d Binary files /dev/null and b/libraries/mogo-speech/src/main/jniLibs/arm64-v8a/libbd_easr_s1_merge_normal_20151216.dat.so differ diff --git a/libraries/mogo-speech/src/main/jniLibs/arm64-v8a/libbd_etts.so b/libraries/mogo-speech/src/main/jniLibs/arm64-v8a/libbd_etts.so new file mode 100644 index 0000000000..f313d921bb Binary files /dev/null and b/libraries/mogo-speech/src/main/jniLibs/arm64-v8a/libbd_etts.so differ diff --git a/libraries/mogo-speech/src/main/jniLibs/arm64-v8a/libgnustl_shared.so b/libraries/mogo-speech/src/main/jniLibs/arm64-v8a/libgnustl_shared.so new file mode 100644 index 0000000000..be99b75ae5 Binary files /dev/null and b/libraries/mogo-speech/src/main/jniLibs/arm64-v8a/libgnustl_shared.so differ diff --git a/libraries/mogo-speech/src/main/jniLibs/arm64-v8a/libvad.dnn.so b/libraries/mogo-speech/src/main/jniLibs/arm64-v8a/libvad.dnn.so new file mode 100644 index 0000000000..0550061c2e Binary files /dev/null and b/libraries/mogo-speech/src/main/jniLibs/arm64-v8a/libvad.dnn.so differ diff --git a/libraries/mogo-speech/src/main/jniLibs/armeabi-v7a/libBDSpeechDecoder_V1.so b/libraries/mogo-speech/src/main/jniLibs/armeabi-v7a/libBDSpeechDecoder_V1.so new file mode 100644 index 0000000000..b08c4aa686 Binary files /dev/null and b/libraries/mogo-speech/src/main/jniLibs/armeabi-v7a/libBDSpeechDecoder_V1.so differ diff --git a/libraries/mogo-speech/src/main/jniLibs/armeabi-v7a/libBaiduSpeechSDK.so b/libraries/mogo-speech/src/main/jniLibs/armeabi-v7a/libBaiduSpeechSDK.so new file mode 100644 index 0000000000..5538ab647a Binary files /dev/null and b/libraries/mogo-speech/src/main/jniLibs/armeabi-v7a/libBaiduSpeechSDK.so differ diff --git a/libraries/mogo-speech/src/main/jniLibs/armeabi-v7a/libbdEASRAndroid.so b/libraries/mogo-speech/src/main/jniLibs/armeabi-v7a/libbdEASRAndroid.so new file mode 100644 index 0000000000..0fef52af53 Binary files /dev/null and b/libraries/mogo-speech/src/main/jniLibs/armeabi-v7a/libbdEASRAndroid.so differ diff --git a/libraries/mogo-speech/src/main/jniLibs/armeabi-v7a/libbdSpilWakeup.so b/libraries/mogo-speech/src/main/jniLibs/armeabi-v7a/libbdSpilWakeup.so new file mode 100644 index 0000000000..f7809e2dd3 Binary files /dev/null and b/libraries/mogo-speech/src/main/jniLibs/armeabi-v7a/libbdSpilWakeup.so differ diff --git a/libraries/mogo-speech/src/main/jniLibs/armeabi-v7a/libbd_easr_s1_merge_normal_20151216.dat.so b/libraries/mogo-speech/src/main/jniLibs/armeabi-v7a/libbd_easr_s1_merge_normal_20151216.dat.so new file mode 100644 index 0000000000..826c81fc5d Binary files /dev/null and b/libraries/mogo-speech/src/main/jniLibs/armeabi-v7a/libbd_easr_s1_merge_normal_20151216.dat.so differ diff --git a/libraries/mogo-speech/src/main/jniLibs/armeabi-v7a/libbd_etts.so b/libraries/mogo-speech/src/main/jniLibs/armeabi-v7a/libbd_etts.so new file mode 100644 index 0000000000..66e2b2f1af Binary files /dev/null and b/libraries/mogo-speech/src/main/jniLibs/armeabi-v7a/libbd_etts.so differ diff --git a/libraries/mogo-speech/src/main/jniLibs/armeabi-v7a/libgnustl_shared.so b/libraries/mogo-speech/src/main/jniLibs/armeabi-v7a/libgnustl_shared.so new file mode 100644 index 0000000000..96c22a2586 Binary files /dev/null and b/libraries/mogo-speech/src/main/jniLibs/armeabi-v7a/libgnustl_shared.so differ diff --git a/libraries/mogo-speech/src/main/jniLibs/armeabi-v7a/libvad.dnn.so b/libraries/mogo-speech/src/main/jniLibs/armeabi-v7a/libvad.dnn.so new file mode 100644 index 0000000000..0550061c2e Binary files /dev/null and b/libraries/mogo-speech/src/main/jniLibs/armeabi-v7a/libvad.dnn.so differ diff --git a/settings.gradle b/settings.gradle index 0d12a00a55..3560a3f445 100644 --- a/settings.gradle +++ b/settings.gradle @@ -45,6 +45,7 @@ include ':libraries:mogo-adas' include ':libraries:mogo-adas-data' include ':libraries:mogo-obu' include ':libraries:mogo-hardware-devices' +include ':libraries:mogo-speech' //include ':libraries:mapmodule' // 语音