将车聊聊工程代码加入到项目中,保证能跑起来了,后续做架构升级

Signed-off-by: 董宏宇 <martindhy@gmail.com>
This commit is contained in:
董宏宇
2021-10-25 20:29:09 +08:00
parent 3bfad3d31f
commit 17f17ba459
348 changed files with 16837 additions and 9 deletions

View File

@@ -0,0 +1,74 @@
package com.mogo.chat.aspect
import android.os.Looper
import android.os.Trace
import android.util.Log
import com.mogo.commons.debug.DebugConfig
import org.aspectj.lang.ProceedingJoinPoint
import org.aspectj.lang.reflect.CodeSignature
import org.aspectj.lang.reflect.MethodSignature
open class BaseAspectj {
companion object {
@Volatile
private var enable: Boolean = DebugConfig.isDebug()
}
fun enterMethod(joinPoint: ProceedingJoinPoint) {
if (!enable) return
val signature = joinPoint.signature as CodeSignature
val cls = signature.declaringType
val methodName = signature.name
val parameterNames = signature.parameterNames
val parameterValues = joinPoint.args
val builder = StringBuilder("\u21E2 ")
builder.append(methodName).append('(')
parameterValues.forEachIndexed { index, _ ->
if (index > 0) {
builder.append(",")
}
builder.append(parameterNames[index]).append('=')
builder.append(parameterValues[index])
}
builder.append(')')
if (Looper.myLooper() != Looper.getMainLooper()) {
builder.append("[Thread:\"").append(Thread.currentThread().name).append("\"]")
}
Log.i(asTag(cls), builder.toString())
}
fun exitMethod(joinPoint: ProceedingJoinPoint, result: Any?, lengthMill: Long) {
if (!enable) return
val signature = joinPoint.signature
val cls = signature.declaringType
val methodName = signature.name
val hasReturnType = signature is MethodSignature
&& signature.returnType != Void.TYPE
val builder = StringBuilder("\u21E0 ")
.append(methodName)
.append("[")
.append(lengthMill)
.append("ms]")
if (hasReturnType) {
builder.append(" = ")
builder.append(result.let {
result.toString()
})
}
Log.i(asTag(cls), builder.toString())
}
private fun asTag(cls: Class<*>): String {
if (cls.isAnonymousClass) {
return asTag(cls.enclosingClass!!)
}
return cls.simpleName
}
}

View File

@@ -0,0 +1,14 @@
package com.mogo.chat.aspect;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Target({TYPE, METHOD, CONSTRUCTOR})
@Retention(RUNTIME)
public @interface DebugLog {
}

View File

@@ -0,0 +1,30 @@
package com.mogo.chat.aspect
import org.aspectj.lang.ProceedingJoinPoint
import org.aspectj.lang.annotation.Around
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.Pointcut
import java.util.concurrent.TimeUnit
@Aspect
class ExceptionAspectj : BaseAspectj() {
@Pointcut("execution(* com.tencent.sharp.jni.TraeAudioManager\$2.run(..))")
fun gmeTrack() {
}
@Around("gmeTrack()")
fun logExecute(joinPoint: ProceedingJoinPoint) {
try {
enterMethod(joinPoint)
val startNanos = System.nanoTime()
val result = joinPoint.proceed()
val stopNanos = System.nanoTime()
val lengthMill = TimeUnit.NANOSECONDS.toMillis(stopNanos - startNanos)
exitMethod(joinPoint, result, lengthMill)
} catch (e: Exception) {
e.printStackTrace()
}
}
}

View File

@@ -0,0 +1,46 @@
package com.mogo.chat.aspect
import org.aspectj.lang.ProceedingJoinPoint
import org.aspectj.lang.annotation.Around
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.Pointcut
import java.util.concurrent.TimeUnit
@Aspect
class LogAspectj : BaseAspectj() {
@Pointcut("within(@com.mogo.chat.aspect.DebugLog *)")
fun withinAnnotatedClass() {
}
@Pointcut("execution(!synthetic * *(..))&& withinAnnotatedClass()")
fun methodInsideAnnotatedType() {
}
@Pointcut("execution(!synthetic *.new(..))&& withinAnnotatedClass()")
fun constructorInsideAnnotatedType() {
}
@Pointcut("execution(@com.mogo.chat.aspect.DebugLog * *(..))|| methodInsideAnnotatedType()")
fun method() {
}
@Pointcut("execution(@com.mogo.chat.aspect.DebugLog *.new(..))|| constructorInsideAnnotatedType()")
fun constructor() {
}
@Around("method() || constructor()")
fun logExecute(joinPoint: ProceedingJoinPoint) {
enterMethod(joinPoint)
val startNanos = System.nanoTime()
val result = joinPoint.proceed()
val stopNanos = System.nanoTime()
val lengthMill = TimeUnit.NANOSECONDS.toMillis(stopNanos - startNanos)
exitMethod(joinPoint, result, lengthMill)
}
}

View File

@@ -0,0 +1,14 @@
package com.mogo.chat.aspect;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Target({TYPE, METHOD, CONSTRUCTOR})
@Retention(RUNTIME)
public @interface PushMsg {
}

View File

@@ -0,0 +1,63 @@
package com.mogo.chat.aspect
import android.util.Log
import android.view.View
import com.mogo.chat.constant.PUSH_MSG_AGREE_ENTER
import com.mogo.chat.constant.PUSH_MSG_HANG_UP
import com.mogo.chat.model.bean.Message
import com.mogo.chat.util.isDoubleClick
import com.mogo.chat.util.sp.recordCallTime
import com.mogo.chat.util.trackHangUp
import org.aspectj.lang.JoinPoint
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.Before
import org.aspectj.lang.annotation.Pointcut
@Aspect
class TrackAspectj {
companion object {
const val TAG = "TrackAspectj"
}
@Pointcut("execution(* android.view.View.OnClickListener.onClick(..))")
fun trackOnClick() {
}
@Before("trackOnClick()")
fun trackClick(joinPoint: JoinPoint) {
val view = joinPoint.args[0] as View
if (isDoubleClick(view.id)) {
Log.i("trackClick", "重复点击,已过滤")
return
}
}
@Pointcut("within(@com.mogo.chat.aspect.PushMsg *)")
fun withinPushClass() {
}
@Pointcut("execution(!synthetic * *(..))&& withinPushClass()")
fun methodInsidePushType() {
}
@Pointcut("execution(@com.mogo.chat.aspect.PushMsg * *(..))|| methodInsidePushType()")
fun pushMethod() {
}
@Before("pushMethod()")
fun trackPushMsg(joinPoint: JoinPoint) {
val msg = joinPoint.args[0] as Message
when (msg.status) {
PUSH_MSG_AGREE_ENTER -> {
recordCallTime()
}
PUSH_MSG_HANG_UP -> {
trackHangUp(msg.type)
}
}
}
}

View File

@@ -0,0 +1,53 @@
package com.mogo.chat.base
import com.mogo.chat.constant.TAG
import com.mogo.chat.model.ChatServiceModel
import com.mogo.chat.model.bean.AllUnit
import com.mogo.chat.model.bean.LocationCarsWithRadius
import com.mogo.chat.model.bean.toSns
import com.mogo.chat.net.request
import com.mogo.chat.util.UserInfoHelper
import com.mogo.chat.util.log
open class BaseController {
val chatServiceModel: ChatServiceModel = ChatServiceModel()
fun getUserInfo(onSuccess: (() -> Unit)? = null, onError: ((Exception) -> Unit)? = null) {
if (!UserInfoHelper.userInfo.sn.isBlank()) {
log(TAG, "userInfo getSn : ${UserInfoHelper.userInfo}")
onSuccess?.invoke()
return
}
log(TAG, "requestUserInfo")
requestUserInfo(onSuccess, onError)
}
private fun requestUserInfo(onSuccess: (() -> Unit)? = null, onError: ((Exception) -> Unit)? = null) {
request<BaseResponse<AllUnit>> {
loader {
val list = mutableListOf<Double>()
list.add(0.0)
list.add(0.0)
val locCarsWithRadius =
LocationCarsWithRadius(list, 0, "circle")
chatServiceModel.requestLiveCars(locCarsWithRadius)
}
onSuccess {
handleUserData(it.result)
onSuccess?.invoke()
}
onError { e ->
log(TAG, "onError : ${e.message ?: "$TAG -> error"}")
onError?.invoke(e)
}
}
}
private fun handleUserData(allUnit: AllUnit) {
log(TAG, "handleUserData:allUnit $allUnit")
val sns = allUnit.toSns()
log(TAG, "handleUserData:sns $sns")
UserInfoHelper.userInfo = sns
}
}

View File

@@ -0,0 +1,159 @@
package com.mogo.chat.base
import com.alibaba.android.arouter.launcher.ARouter
import com.google.gson.Gson
import com.mogo.chat.constant.CALL_TYPE_VOICE
import com.mogo.chat.constant.HttpConstants
import com.mogo.chat.constant.PUSH_MSG_AGREE_ENTER
import com.mogo.chat.constant.TAG
import com.mogo.chat.model.bean.*
import com.mogo.chat.net.HttpApi
import com.mogo.chat.provider.ServiceApi
import com.mogo.chat.util.UserInfoHelper.userInfo
import com.mogo.chat.util.log
import com.mogo.chat.util.sp.getRoomId
import com.mogo.cloud.passport.MoGoAiCloudClientConfig
import com.mogo.eagle.core.data.constants.MogoServicePaths
import com.mogo.service.IMogoServiceApis
open class BaseRepository {
protected fun getNetWorkApi(baseUrl: String = HttpConstants.getBaseUrl()): HttpApi {
var serviceApi: IMogoServiceApis? = null
val mogoService = ARouter.getInstance().build(MogoServicePaths.PATH_SERVICE_APIS).navigation()
if (mogoService is IMogoServiceApis) {
serviceApi = mogoService
}
return serviceApi!!.networkApi.createNoCallAdapter(HttpApi::class.java, baseUrl)
}
suspend fun requestLiveCars(locCarsWithRadius: LocationCarsWithRadius): BaseResponse<AllUnit> {
val map = hashMapOf<String, String>()
map["sn"] = MoGoAiCloudClientConfig.getInstance().sn
map["data"] = Gson().toJson(locCarsWithRadius)
return apiCall {
getNetWorkApi().requestLiveCars(map)
}
}
suspend fun requestConnectStatus(requestType: Int, snReceiver: String = "", roomID: Int, status: Int, callType: Int): BaseResponse<Any> {
val map = hashMapOf<String, String>()
val sn = MoGoAiCloudClientConfig.getInstance().sn
var r = roomID
if (r == 0) {
r = getRoomId()
}
val location = ServiceApi.locationClient()?.lastKnowLocation
val connectStatusParam = ConnectStatusParam(sn, snReceiver, r, status, callType)
if (status == PUSH_MSG_AGREE_ENTER) {
log(TAG, "被动匹配 agree enter===$userInfo")
connectStatusParam.nickName = userInfo.nickName
connectStatusParam.headImgUrl = userInfo.headImgUrl
connectStatusParam.carInfo = userInfo.carInfo
connectStatusParam.cardIdAge = userInfo.cardIdAge
connectStatusParam.cardIdSex = userInfo.cardIdSex
connectStatusParam.cityName = userInfo.cityName
connectStatusParam.lat = location?.latitude
connectStatusParam.lon = location?.longitude
}
log(TAG, "connectStatusParam:$connectStatusParam")
map["sn"] = MoGoAiCloudClientConfig.getInstance().sn
map["data"] = Gson().toJson(connectStatusParam)
return if (requestType == CALL_TYPE_VOICE) {
apiCall {
getNetWorkApi().requestConnectStatus(sn, map)
}
} else {
apiCall {
getNetWorkApi().requestVehicleTeamConnectStatus(map)
}
}
}
suspend fun startMatch(param: MatchRequestParam): BaseResponse<Any> {
val map = hashMapOf<String, String>()
log(TAG, "startMatch paras: $param")
map["data"] = Gson().toJson(param)
return apiCall {
getNetWorkApi().startMatch(MoGoAiCloudClientConfig.getInstance().sn, map)
}
}
suspend fun cancelMatch(): BaseResponse<Any> {
val map = hashMapOf<String, String>()
return apiCall {
getNetWorkApi().cancelMatch(MoGoAiCloudClientConfig.getInstance().sn, map)
}
}
suspend fun inviteJoinVehicleTeam(param: CallRequestParam): BaseResponse<Any> {
val map = hashMapOf<String, String>()
log(TAG, "inviteJoinVehicleTeam paras: $param")
map["sn"] = MoGoAiCloudClientConfig.getInstance().sn
map["data"] = Gson().toJson(param)
return apiCall {
getNetWorkApi().inviteJoinVehicleTeam(map)
}
}
suspend fun requestRoomInfo(snReceiver: String, callType: Int): BaseResponse<RoomInfo> {
val map = hashMapOf<String, String>()
val location = ServiceApi.locationClient()?.lastKnowLocation
log(TAG, "requestRoomInfo===$userInfo")
val roomParam = CallRequestParam(
MoGoAiCloudClientConfig.getInstance().sn,
snReceiver,
userInfo.nickName!!,
userInfo.headImgUrl!!,
userInfo.carInfo ?: "",
location?.latitude,
location?.longitude,
callType
)
log(TAG, "roomParam:$roomParam")
map["data"] = Gson().toJson(roomParam)
return apiCall {
getNetWorkApi().requestRoomInfo(map)
}
}
/**
* 添加关注(后面可能增加取消关注,到时候再增加参数吧)
*/
suspend fun dealFocus(sn: String): BaseResponse<Any> {
val map = hashMapOf<String, String>()
map["data"] = "{\"focusSn\":$sn}"
map["sn"] = MoGoAiCloudClientConfig.getInstance().sn
return apiCall {
getNetWorkApi().dealFocus(map)
}
}
/**
* 获取关注状态
*
* 1 表示已关注0 表示未关注
*/
suspend fun requestFocusStatus(sn: String): BaseResponse<FocusStatus> {
val map = hashMapOf<String, String>()
map["data"] = "{\"focusSn\":$sn}"
map["sn"] = MoGoAiCloudClientConfig.getInstance().sn
return apiCall {
getNetWorkApi().requestFocusStatus(map)
}
}
suspend fun switchCarStatus(status: Int): BaseResponse<Any> {
val map = hashMapOf<String, String>()
map["data"] = Gson().toJson(CarSwitchStatus(MoGoAiCloudClientConfig.getInstance().sn, status))
return apiCall {
getNetWorkApi().switchCarStatus(map)
}
}
suspend fun <T : Any> apiCall(call: suspend () -> BaseResponse<T>): BaseResponse<T> {
return call.invoke()
}
}

View File

@@ -0,0 +1,13 @@
package com.mogo.chat.base
import com.mogo.chat.constant.HttpConstants
class BaseResponse<out T>(val code: Int, val msg: String, val result: T) {
fun isSuccess(baseUrl: String): Boolean {
return when (baseUrl) {
HttpConstants.getBaseUrl() -> true
else -> false
}
}
}

View File

@@ -0,0 +1,18 @@
package com.mogo.chat.callcenter
import com.mogo.chat.constant.TAG
import com.mogo.chat.util.log
class CallController private constructor(){
companion object{
val callController by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED){
log(TAG,"getCallController")
getCallControllerProxy()
}
private fun getCallControllerProxy(): ICallControl {
return CallControllerHandler().newProxyInstance()
}
}
}

View File

@@ -0,0 +1,44 @@
package com.mogo.chat.callcenter
import com.mogo.chat.constant.TAG
import com.mogo.chat.util.log
import java.lang.reflect.InvocationHandler
import java.lang.reflect.Method
import java.lang.reflect.Proxy
class CallControllerHandler : InvocationHandler {
private var obj: Any? = null
private val interceptor = CallInterceptor()
constructor()
constructor(target: ICallControl) {
this.obj = target
}
fun newProxyInstance(): ICallControl {
log(TAG, "newProxyInstance")
return Proxy.newProxyInstance(
CallProxy::class.java.classLoader,
arrayOf(ICallControl::class.java),
CallControllerHandler(CallProxy())
) as ICallControl
}
override fun invoke(proxy: Any?, method: Method, args: Array<out Any>?): Any? {
log(TAG, "invoke method===${method.name}")
val intercept = interceptor.process(method)
log(TAG, "invoke method==$method,intercept=$intercept")
return if (intercept) {
method.invoke(obj, *args.orEmpty())
} else {
if (proxy is ICallControl) {
//执行挂断或其他操作
interceptor.handler(proxy, method, args)
} else {
return null
}
}
}
}

View File

@@ -0,0 +1,63 @@
package com.mogo.chat.callcenter
import com.mogo.chat.callcenter.CallTypeManager.Companion.callTypeManager
import com.mogo.chat.callcenter.ConvertFactory.Companion.factory
import com.mogo.chat.common.gme.GMEApi
import com.mogo.chat.constant.TAG
import com.mogo.chat.model.bean.Message
import com.mogo.chat.util.log
import java.lang.reflect.Method
class CallInterceptor {
/**
* 对实现[ICallControl]的接口对象在调用时进行拦截
*/
fun process(method: Method): Boolean {
var process = true
getIMType(method) {
process = when (it) {
IMType.INIT_CALL -> true
//主动拨打电话请求roomId成功 更改通话状态
IMType.READY_TO_CALL_SENDER,
//用户来电
IMType.READY_TO_CALL_RECEIVER,
//被动匹配用户来电
IMType.READY_TO_MATCH_RECEIVER -> callTypeManager.callStatus.isInit() || !GMEApi.isRoomEntered()
IMType.CALLING_SENDER, IMType.CALLING_RECEIVER -> callTypeManager.callStatus.isInit() || callTypeManager.callStatus.canStartCalling() || callTypeManager.callStatus.isCalling()
IMType.CALLING_MATCH -> callTypeManager.callStatus.canMatched()
IMType.CALLING_REFUSE -> true //任何时候都可以接收 车队邀请拒绝消息
IMType.NULL -> true
else -> {
log(TAG, "CallInterceptor IMType : $it has been interceptor, check func")
true
}
}
}
return process
}
/**
* 对拦截的方法进行处理
*/
fun handler(proxy: ICallControl, method: Method, args: Array<out Any>?) {
getIMType(method) {
log(TAG, "CallInterceptor IMType : $it has been handler")
if (it.isReadyCalling() || it.isMatching()) {
// 当前状态是准备打电话,并且即将转变的状态为将要接电话,需要根据类型拒绝
args?.get(0)?.let { data ->
if (data is Message) {
proxy.refuseCall(data)
}
}
}
}
}
private inline fun getIMType(method: Method, convert: (IMType) -> Unit) {
val imType = factory.parseAnnotationToIMType(method)
log(TAG, "CallInterceptor thread ${Thread.currentThread().name}")
log(TAG, "CallInterceptor coming IMType===$imType, current IMType=${callTypeManager.callStatus}")
convert.invoke(imType)
}
}

View File

@@ -0,0 +1,149 @@
package com.mogo.chat.callcenter
import com.mogo.chat.aspect.DebugLog
import com.mogo.chat.callcenter.CallTypeManager.Companion.callTypeManager
import com.mogo.chat.callcenter.ConvertFactory.Companion.factory
import com.mogo.chat.constant.CALL_TYPE_VOICE
import com.mogo.chat.constant.TAG
import com.mogo.chat.model.bean.AddFriendMessage
import com.mogo.chat.model.bean.Message
import com.mogo.chat.util.log
import com.mogo.chat.util.sp.hasFocus
import com.mogo.chat.util.sp.newFocus
/**
* 主要处理消息下发,以及消息状态改变
*/
class CallProxy : ICallControl {
private var isInitiativeMatch: Boolean = false
private var callBacks = mutableMapOf<String, ICallMessage>()
private var messageCallBack: MessageCallBack = MessageCallBackImpl()
private var message: Message? = null
@DebugLog
override fun addCallBack(clzName: String, callBack: ICallMessage) {
log(TAG,"addCallBack clzName : $clzName")
callBacks[clzName] = callBack
}
override fun removeCallBack(clzName: String) {
callBacks.remove(clzName)
}
override fun getMsgCallBack(): MutableMap<String, ICallMessage> {
return callBacks
}
override fun refreshMessage(message: Message) {
factory.parseMessageToCallProxy(message)
}
override fun newFocusAdd() {
if (!hasFocus()) { //没有新的关注,需要添加
newFocus(true)
}
dispatchNotice()
}
private fun dispatchNotice() {
}
override fun initCall() {
callTypeManager.callStatus = IMType.INIT_CALL
isInitiativeMatch = false
dispatchMsg(null, MSG_INIT_CALL)
}
override fun readyToCallSender() {
callTypeManager.callStatus = IMType.READY_TO_CALL_SENDER
}
override fun refuseCall(message: Message) {
dispatchMsg(message, MSG_REFUSE_CALL)
}
override fun readyToCallReceiver(message: Message) {
log(TAG, "readyToCallReceiver")
if (message.type == CALL_TYPE_VOICE) {
callTypeManager.callStatus = IMType.READY_TO_CALL_RECEIVER
}
dispatchMsg(message, MSG_READY_TO_CALL_RECEIVER)
}
override fun callingSender(message: Message) {
callTypeManager.callStatus = IMType.CALLING_SENDER
dispatchMsg(message, MSG_CALL_SENDER)
}
override fun callingSenderWithoutMessage() {
callTypeManager.callStatus = IMType.CALLING_SENDER
dispatchMsg(null, MSG_CALL_SENDER_WITHOUT_MSG)
}
override fun callingReceiver() {
if (callTypeManager.callStatus == IMType.READY_TO_MATCH_RECEIVER) {
callTypeManager.callStatus = IMType.CALLING_MATCH
} else {
callTypeManager.callStatus = IMType.CALLING_RECEIVER
}
dispatchMsg(message, MSG_CALL_RECEIVER)
}
private fun dispatchMsg(message: Message?, @DispatchMsg dispatchMethod: String) {
log(TAG, "dispatchMsg===${callBacks.size}")
this.message = message
dispatch(messageCallBack, message, dispatchMethod)
}
@DebugLog
private fun dispatch(
callBack: MessageCallBack,
message: Message?, @DispatchMsg dispatchMethod: String
) {
when (dispatchMethod) {
MSG_INIT_CALL -> callBack.initCall()
MSG_CALL_RECEIVER -> callBack.msgCallingReceiver(message!!)
MSG_CALL_MATCH -> callBack.msgCallMatch(message!!)
MSG_READY_TO_CALL_RECEIVER -> callBack.msgReadyToCallReceiver(message!!)
MSG_CALL_SENDER -> callBack.msgCallingSender(message!!)
MSG_REFUSE_CALL -> callBack.msgRefuseCall(message!!)
MSG_CALL_SENDER_WITHOUT_MSG -> callBack.msgCallingSenderWithoutMessage()
MSG_CALL_READY_TO_MATCH_RECEIVER -> callBack.msgReadyToCallReceiver(message!!)
MSG_EXIT_ROOM -> callBack.updateExitRoomStatus(message)
MSG_CALL_RECEIVER_REFUSE -> callBack.callingReceiverRefuse(message!!)
}
}
override fun matching() {
callTypeManager.callStatus = IMType.MATCHING
}
override fun callingMatch(message: Message) {
callTypeManager.callStatus = IMType.CALLING_MATCH
isInitiativeMatch = true
dispatchMsg(message, MSG_CALL_MATCH)
}
override fun readyToMatchReceiver(message: Message) {
callTypeManager.callStatus = IMType.READY_TO_MATCH_RECEIVER
dispatchMsg(message, MSG_CALL_READY_TO_MATCH_RECEIVER)
}
override fun matchFailed() {
callTypeManager.callStatus = IMType.MATCHING_FAILED
}
override fun updateExitRoomStatus(message: Message?) {
dispatchMsg(message, MSG_EXIT_ROOM)
}
override fun callingReceiverRefuse(message: Message) {
dispatchMsg(message, MSG_CALL_RECEIVER_REFUSE)
}
override fun addFriend(addFriendMsg: AddFriendMessage) {
}
}

View File

@@ -0,0 +1,15 @@
package com.mogo.chat.callcenter
import java.lang.annotation.Retention
import java.lang.annotation.RetentionPolicy.RUNTIME
@Target(
AnnotationTarget.CLASS,
AnnotationTarget.FUNCTION,
AnnotationTarget.PROPERTY_GETTER,
AnnotationTarget.PROPERTY_SETTER,
AnnotationTarget.CONSTRUCTOR
)
@Retention(RUNTIME)
annotation class CallStatus(val status: IMType)

View File

@@ -0,0 +1,45 @@
package com.mogo.chat.callcenter
import com.mogo.chat.constant.TAG
import com.mogo.chat.util.log
/**
* 管理IMType或者叫callType即管理各种状态值
*/
class CallTypeManager private constructor() {
companion object {
val callTypeManager by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
CallTypeManager()
}
}
private val typeChangeListenerList: ArrayList<ICallTypeChangedListener> = ArrayList()
var callStatus: IMType = IMType.INIT_CALL
set(value) {
log(TAG,"setCallType===$value")
field = value
typeChangedListener?.invoke(value)
typeChangeListenerList.forEach {
it.onCallTypeChanged(value)
}
}
private var typeChangedListener:((type: IMType)->Unit)? = null
fun setCallTypeChangedListener(listener: (type: IMType) -> Unit) {
this.typeChangedListener = listener
}
fun addCallTypeChangedListener(listener: ICallTypeChangedListener) {
log(TAG,"添加状态改变回调===$listener")
if(!typeChangeListenerList.contains(listener)) {
typeChangeListenerList.add(listener)
}
}
fun removeCallTypeChangedListener(listener: ICallTypeChangedListener) {
typeChangeListenerList.remove(listener)
}
}

View File

@@ -0,0 +1,172 @@
package com.mogo.chat.callcenter
import com.google.gson.Gson
import com.mogo.chat.callcenter.CallController.Companion.callController
import com.mogo.chat.callcenter.CallTypeManager.Companion.callTypeManager
import com.mogo.chat.common.gme.GMEApi
import com.mogo.chat.constant.*
import com.mogo.chat.model.bean.AddFriendMessage
import com.mogo.chat.model.bean.Message
import com.mogo.chat.model.control.ChatController
import com.mogo.chat.service.ChatServiceHandler
import com.mogo.chat.util.UserInfoHelper.currentCallType
import com.mogo.chat.util.log
import com.mogo.chat.util.sp.getRoomId
import com.mogo.cloud.passport.MoGoAiCloudClientConfig
import java.lang.reflect.Method
/**
* 解析WebSocket返回的数据
*/
class ConvertFactory private constructor() {
companion object {
val factory by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
ConvertFactory()
}
}
fun parseAnnotationToIMType(method: Method): IMType {
if (method.isAnnotationPresent(CallStatus::class.java)) {
val callStatus = method.getAnnotation(CallStatus::class.java)
return callStatus.status
}
return IMType.NULL
}
fun parseLongConnMsgOnError(ex: Exception) {
log(TAG, "${ex.message}")
}
fun parseLongConnMsgToCallProxy(message: String) {
log(TAG, "handleMessage -> $message")
val map = Gson().fromJson<Map<String, Any>>(message, Map::class.java)
val msgType = map["msgType"]
if (msgType != null && msgType is Double) {
when (msgType.toInt()) {
0 -> { // 建立连接,发送的消息
val localUserId = map["localUserId"] as Double
log(TAG, "handleMessage --- localUserId:${localUserId.toInt()}")
//初始化房间
GMEApi.init("" + localUserId.toInt())
}
2 -> { // 添加好友提示
log(TAG, "添加好友提示====")
// parseMapToCallProxy(map)
}
3 -> { //新增粉丝提示
log(TAG, "新增粉丝提示====")
callController.newFocusAdd()
}
}
} else {
val messageObj = Gson().fromJson(message, Message::class.java)
parseMessageToCallProxy(messageObj)
}
}
@Deprecated("临时注释掉os2.0暂时不提示添加好友了 ")
private fun parseMapToCallProxy(map: Map<String, Any>) {
log(TAG, "准备刷新好友提示==")
val addFriendMsg = AddFriendMessage(
sn = map["sn"] as String,
nickName = map["nickName"] as String,
headImgUrl = map["headImgUrl"] as String,
message = map["message"] as String,
alertType = (map["alertType"] as Double).toInt()
)
callController.addFriend(addFriendMsg)
}
/**
* 解析服务端发来的Message消息并对具体类型做消息下发
*/
fun parseMessageToCallProxy(message: Message) {
//message type如果是语音电话或者是匹配聊天时接收到服务端发来的数据都是snReceiver如果为本机设备则过滤
if (message.type == CALL_TYPE_VOICE || message.type == CALL_TYPE_MATCHING) {
if (message.snSender == MoGoAiCloudClientConfig.getInstance().sn) {
return
}
}
log(TAG, "parseMessageToCallProxy====$message")
when (message.status) {
PUSH_MSG_AGREE_ENTER -> {
if (!GMEApi.isRoomEntered()) {
SocketClientFactory.socketClient.startHeartBeat()
}
when (message.type) {
CALL_TYPE_VOICE -> {
if (GMEApi.isRoomEntered() && currentCallType == CALL_TYPE_VEHICLE_TEAM) {
log(TAG, "收到来电进房消息,此时正在车队通话。需要挂断电话")
ChatController.requestConnectStatus(message.snSender, PUSH_MSG_HANG_UP, {}, {}, message.roomId)
return
}
if (callTypeManager.callStatus.isMatching()) {
log(TAG, "收到语音通话消息,此时正在匹配,需要取消匹配")
ChatServiceHandler.cancelMatch { }
}
callController.callingSender(message)
}
CALL_TYPE_MATCHING -> {
if (GMEApi.isRoomEntered() && currentCallType == CALL_TYPE_VEHICLE_TEAM) {
log(TAG, "收到匹配成功消息,此时正在车队通话,需要挂断电话")
ChatController.requestConnectStatus(message.snSender, PUSH_MSG_HANG_UP, {}, {}, message.roomId)
return
}
callController.callingMatch(message)
}
CALL_TYPE_VEHICLE_TEAM -> {
if (callTypeManager.callStatus.isReadyCalling()) {
log(TAG, "收到车队通话消息,此时正在打电话,需要拒绝通话")
ChatServiceHandler.refuseCall(message.snSender, {}, {})
}
if (callTypeManager.callStatus.isMatching()) {
log(TAG, "收到车队通话消息,此时正在匹配,需要取消匹配")
ChatServiceHandler.cancelMatch { }
}
callController.callingSender(message)
}
}
}
PUSH_MSG_DENY_ENTER -> {
when (message.type) {
CALL_TYPE_VOICE, CALL_TYPE_MATCHING -> {
if (GMEApi.isRoomEntered() && currentCallType == CALL_TYPE_VEHICLE_TEAM) {
log(TAG, "已经进房,收到被拒绝消息,则不处理(为兼容车队需求),否则会引发退房操作")
return
}
//此处 callingReceiverRefuse 未做整合,放到此类型最上面执行,因为后续可能在回调页面上有不同的执行操作
callController.callingReceiverRefuse(message)
SocketClientFactory.socketClient.stopHeartBeat()
callController.updateExitRoomStatus()
}
CALL_TYPE_VEHICLE_TEAM -> {
callController.callingReceiverRefuse(message)
}
}
}
PUSH_MSG_HANG_UP -> {
callController.updateExitRoomStatus(message)
}
PUSH_MSG_CANCEL_MATCH -> {
if(callTypeManager.callStatus.isMatching()){
callController.initCall()
}
}
PUSH_MSG_CREATE_ROOM -> {
when (message.type) {
CALL_TYPE_VOICE -> {
if (callTypeManager.callStatus.isReadyCalling() || callTypeManager.callStatus.isCalling()) {
log(TAG, "收到来电,此时正在打电话或者通话中,需要拒绝通话")
ChatController.requestConnectStatus(message.snSender, PUSH_MSG_DENY_ENTER, {}, {}, getRoomId())
}
callController.readyToCallReceiver(message)
}
CALL_TYPE_VEHICLE_TEAM -> callController.readyToCallReceiver(message)
CALL_TYPE_MATCHING -> callController.readyToMatchReceiver(message)
}
}
}
}
}

View File

@@ -0,0 +1,21 @@
package com.mogo.chat.callcenter
import androidx.annotation.StringDef
import java.lang.annotation.Retention
import java.lang.annotation.RetentionPolicy.SOURCE
const val MSG_INIT_CALL = "initCall"
const val MSG_CALL_RECEIVER = "callingReceiver"
const val MSG_CALL_SENDER = "callingSender"
const val MSG_CALL_SENDER_WITHOUT_MSG = "callingSenderWithoutMessage"
const val MSG_CALL_MATCH = "callingMatch"
const val MSG_CALL_READY_TO_MATCH_RECEIVER = "readyToMatchReceiver"
const val MSG_READY_TO_CALL_RECEIVER = "readyToCallReceiver"
const val MSG_REFUSE_CALL = "refuseCall"
const val MSG_EXIT_ROOM = "exitRoom"
const val MSG_CALL_RECEIVER_REFUSE = "callingReceiverRefuse"
@StringDef(MSG_INIT_CALL, MSG_CALL_RECEIVER, MSG_CALL_SENDER, MSG_CALL_SENDER_WITHOUT_MSG,
MSG_CALL_MATCH, MSG_CALL_READY_TO_MATCH_RECEIVER, MSG_READY_TO_CALL_RECEIVER, MSG_REFUSE_CALL, MSG_EXIT_ROOM,MSG_CALL_RECEIVER_REFUSE)
@Retention(SOURCE)
annotation class DispatchMsg

View File

@@ -0,0 +1,60 @@
package com.mogo.chat.callcenter
import com.mogo.chat.model.bean.AddFriendMessage
import com.mogo.chat.model.bean.Message
interface ICallControl {
fun addCallBack(clzName: String, callBack: ICallMessage)
fun removeCallBack(clzName: String)
fun getMsgCallBack(): MutableMap<String, ICallMessage>
fun refreshMessage(message: Message)
fun refuseCall(message: Message)
fun newFocusAdd()
@CallStatus(status = IMType.INIT_CALL)
fun initCall()
@CallStatus(status = IMType.READY_TO_CALL_SENDER)
fun readyToCallSender()
@CallStatus(status = IMType.READY_TO_CALL_RECEIVER)
fun readyToCallReceiver(message: Message)
@CallStatus(status = IMType.CALLING_SENDER)
fun callingSender(message: Message)
@CallStatus(status = IMType.CALLING_SENDER)
fun callingSenderWithoutMessage()
@CallStatus(status = IMType.CALLING_RECEIVER)
fun callingReceiver()
@CallStatus(status = IMType.MATCHING)
fun matching()
@CallStatus(status = IMType.CALLING_MATCH)
fun callingMatch(message: Message)
@CallStatus(status = IMType.READY_TO_MATCH_RECEIVER)
fun readyToMatchReceiver(message: Message)
@CallStatus(status = IMType.MATCHING_FAILED)
fun matchFailed()
@CallStatus(status = IMType.INIT_CALL)
fun updateExitRoomStatus(message: Message? = null)
@CallStatus(status = IMType.CALLING_REFUSE)
fun callingReceiverRefuse(message: Message)
@CallStatus(status = IMType.CALLING_ADD_FRIEND)
fun addFriend(addFriendMsg: AddFriendMessage)
}

View File

@@ -0,0 +1,52 @@
package com.mogo.chat.callcenter
import com.mogo.chat.model.bean.Message
import com.mogo.chat.model.bean.TeammateInfo
/**
* 对WebSocket消息进行下发处理
*/
interface ICallMessage {
fun initStatus() {
}
fun addNewFocus() {
}
fun receiverCalling(message: Message) {
}
fun receiverCallingAgree() {
}
fun refuseMatchToShowCalling(message: Message) {
}
fun callSuccess() {
}
fun vehicleTeamEnterRoom() {
}
fun receiverVehicleTeamInvitation() {
}
fun vehicleTeamMemberChange(teamMember: List<TeammateInfo>?) {
}
fun matchSuccess() {
}
fun receiverCallRefuse() {
}
fun receiverVehicleTeamInviteRefuse(){
}
fun receiverSomeoneExitVehicleTeam() {
}
fun receiverHangUpInfo() {
}
}

View File

@@ -0,0 +1,5 @@
package com.mogo.chat.callcenter
interface ICallTypeChangedListener {
fun onCallTypeChanged(callType: IMType)
}

View File

@@ -0,0 +1,81 @@
package com.mogo.chat.callcenter
import com.mogo.chat.constant.CALL_TYPE_MATCHING
import com.mogo.chat.constant.CALL_TYPE_VOICE
fun IMType.isMatching(): Boolean {
return this == IMType.MATCHING
}
fun IMType.isMatchFailed():Boolean{
return this == IMType.MATCHING_FAILED
}
fun IMType.isInit():Boolean{
return this == IMType.INIT_CALL
}
fun IMType.canMatched(): Boolean {
return this == IMType.MATCHING || this == IMType.MATCHING_FAILED
}
fun IMType.isCalling(): Boolean {
return this in IMType.CALLING_SENDER..IMType.CALLING_RECEIVER || this == IMType.CALLING_MATCH
}
fun IMType.isReadyCalling(): Boolean {
return this in IMType.READY_TO_CALL_SENDER..IMType.READY_TO_CALL_RECEIVER
}
/**
* 判断是否可以开始通话
*/
fun IMType.canStartCalling(): Boolean {
return this in IMType.READY_TO_CALL_SENDER..IMType.READY_TO_CALL_RECEIVER || this == IMType.READY_TO_MATCH_RECEIVER
}
/**
* 用于判断是否可以向上通知匹配超时,如果本次匹配已经被外来直聊通话打断,则不能向上反馈匹配超时
*/
fun IMType.canMatchTimeout(): Boolean {
return this !in IMType.READY_TO_CALL_SENDER..IMType.CALLING_RECEIVER
}
/**
* 未在通话中,在初始化或拨号中
*/
fun IMType.notInCall(): Boolean {
return this in IMType.INIT_CALL..IMType.READY_TO_CALL_RECEIVER
}
/**
* 将IMType转换成接口调用时传递的callType参数
*/
fun IMType.exchangeToCallType(): Int {
return when (this) {
IMType.CALLING_MATCH, IMType.READY_TO_MATCH_RECEIVER -> CALL_TYPE_MATCHING
else -> CALL_TYPE_VOICE
}
}
/**
* 在通话状态,指的是在通话状态中,包括拨号中和通话中
*/
fun IMType.isInCallStatus(): Boolean {
return isCalling() || isReadyCalling()
}
enum class IMType(val imType: Int) {
NULL(0),
INIT_CALL(1000),
READY_TO_CALL_SENDER(1001),
READY_TO_CALL_RECEIVER(1002),
CALLING_SENDER(1003),
CALLING_RECEIVER(1004),
MATCHING(1005),
CALLING_MATCH(1006),
READY_TO_MATCH_RECEIVER(1007),
MATCHING_FAILED(1008),
CALLING_ADD_FRIEND(1009),
CALLING_REFUSE(1010);
}

View File

@@ -0,0 +1,39 @@
package com.mogo.chat.callcenter
import com.mogo.chat.constant.TAG
import com.mogo.chat.model.bean.Message
import com.mogo.chat.util.log
/**
* Activity或者Service实现接口实现方法注意切换为UI线程
*/
interface MessageCallBack {
fun initCall()
fun addNewFocus()
fun msgCallingReceiver(message: Message)
fun msgCallingSender(message: Message)
fun msgCallingSenderWithoutMessage() {
log(TAG, "msgCallingSenderWithoutMessage ---> GME返回信息有人进房")
}
fun msgRefuseCall(message: Message)
fun msgCallMatch(message: Message)
fun msgReadyToCallReceiver(message: Message)
fun updateExitRoomStatus(message: Message? = null)
fun callingReceiverRefuse(message: Message)
fun msgError(socketException: Exception) {
//do nothing
}
}

View File

@@ -0,0 +1,217 @@
package com.mogo.chat.callcenter
import com.mogo.chat.common.gme.GMEApi
import com.mogo.chat.constant.*
import com.mogo.chat.model.bean.Message
import com.mogo.chat.model.bean.isCall
import com.mogo.chat.model.bean.isMatch
import com.mogo.chat.model.bean.toSns
import com.mogo.chat.model.control.ChatController
import com.mogo.chat.model.control.MatchController
import com.mogo.chat.model.control.VehicleTeamController
import com.mogo.chat.service.ChatServiceHandler
import com.mogo.chat.util.UserInfoHelper
import com.mogo.chat.util.log
import com.mogo.chat.util.sp.getRoomId
import com.mogo.chat.util.sp.saveRoomId
class MessageCallBackImpl : MessageCallBack {
/**
* 重置初始化状态
*/
override fun initCall() {
log(TAG, "initCall --->")
// 后台返回匹配超时会走此回调所以在此调用了MatchController的matchTimeOutFromNet的方法在方法里面判断了此时是否正在进行匹配以及后续操作
MatchController.matchTimeOutFromNet()
UserInfoHelper.currentRoomId = 0
saveRoomId(0)
UserInfoHelper.currentCallType = CALL_TYPE_DEFAULT
log(TAG, "初始化")
CallController.callController.getMsgCallBack().forEach {
it.value.initStatus()
}
}
override fun addNewFocus() {
}
/**
* 来电同意接听
*/
override fun msgCallingReceiver(message: Message) {
UserInfoHelper.tmpSenderInfo = message.toSns()
UserInfoHelper.currentCallType = message.type
when (UserInfoHelper.currentCallType) {
CALL_TYPE_VOICE, CALL_TYPE_MATCHING -> {
log(TAG, "来电同意接听")
CallController.callController.getMsgCallBack().forEach {
it.value.receiverCallingAgree()
}
}
}
}
override fun msgCallingSender(message: Message) {
log(TAG, "msgCallingSender message : $message")
if (message.type == CALL_TYPE_VEHICLE_TEAM && GMEApi.isRoomEntered()) {
log(TAG, "msgCallingSender 收到成员进房通知,本人已经进房 : ${getRoomId()}")
CallController.callController.getMsgCallBack().forEach {
log(TAG,"vehicleTeamMemberChange key : ${it.key}")
it.value.vehicleTeamMemberChange(message.teamMember)
}
return
}
if (message.roomId > 0) {
if (message.isCall() || message.isMatch()) {
UserInfoHelper.tmpSenderInfo = message.toSns()
}
UserInfoHelper.currentRoomId = message.roomId
UserInfoHelper.currentCallType = message.type
ChatController.enterRoom(message.roomId)
when (UserInfoHelper.currentCallType) {
CALL_TYPE_VOICE -> {
log(TAG, "打/接电话成功回调")
CallController.callController.getMsgCallBack().forEach {
it.value.callSuccess()
}
}
CALL_TYPE_VEHICLE_TEAM -> {
log(TAG, "车队邀请同意回调")
CallController.callController.getMsgCallBack().forEach {
log(TAG,"vehicleTeamEnterRoom key : ${it.key}")
it.value.vehicleTeamEnterRoom()
}
}
}
//首次邀请加入车队成功,会返回邀请人和被邀请人列表
if (message.teamMember != null && message.teamMember.isNotEmpty()) {
CallController.callController.getMsgCallBack().forEach {
log(TAG,"vehicleTeamMemberChange key : ${it.key}")
it.value.vehicleTeamMemberChange(message.teamMember)
}
}
}
}
override fun msgRefuseCall(message: Message) {
log(TAG, "已在通话进程中,根据类型拒接或挂断 ---> $message")
if (message.isCall() || message.isMatch()) {
UserInfoHelper.tmpSenderInfo = message.toSns()
}
UserInfoHelper.currentCallType = message.type
when (UserInfoHelper.currentCallType) {
CALL_TYPE_VOICE -> {
log(TAG, "正在拨打电话时来电,挂断来电")
ChatServiceHandler.refuseCall(message.snSender, {}, {}, message.roomId)
}
CALL_TYPE_MATCHING -> {
log(TAG, "正在匹配时来电,取消匹配")
MatchController.cancelMatch {
CallTypeManager.callTypeManager.callStatus = IMType.INIT_CALL
log(TAG, "取消匹配成功给IMService回调展示来电界面")
CallController.callController.getMsgCallBack().forEach {
it.value.refuseMatchToShowCalling(message)
}
}
}
CALL_TYPE_VEHICLE_TEAM -> {
if (CallTypeManager.callTypeManager.callStatus.isReadyCalling()) {
log(TAG, "正在拨打电话时来电,通知进入车队,挂断拨打电话")
ChatController.requestConnectStatus(UserInfoHelper.userInfo.sn, PUSH_MSG_DENY_ENTER)
} else if (GMEApi.isRoomEntered() || CallTypeManager.callTypeManager.callStatus.isCalling()) {
log(TAG, "已经进房或者通话过程中(无论是车队通话还是打电话、匹配),收到通知进入车队邀请,拒绝加入车队")
VehicleTeamController.requestVehicleTeamConnectStatus(message.snSender, PUSH_MSG_DENY_ENTER, CALL_TYPE_VEHICLE_TEAM)
} else {
log(TAG, "仍有条件没有考虑到find bug")
}
}
}
}
override fun msgCallMatch(message: Message) {
log(TAG, "msgCallMatch ---> $message")
if (message.roomId > 0) {
if (message.isCall() || message.isMatch()) {
UserInfoHelper.tmpSenderInfo = message.toSns()
}
UserInfoHelper.currentRoomId = message.roomId
UserInfoHelper.currentCallType = message.type
ChatController.enterRoom(message.roomId)
MatchController.resetMatchStatus()
log(TAG, "匹配成功回调")
CallController.callController.getMsgCallBack().forEach {
it.value.matchSuccess()
}
}
}
override fun msgReadyToCallReceiver(message: Message) {
log(TAG, "msgReadyToCallReceiver ---> $message")
UserInfoHelper.tmpSenderInfo = message.toSns()
UserInfoHelper.currentRoomId = message.roomId
UserInfoHelper.currentCallType = message.type
when (UserInfoHelper.currentCallType) {
CALL_TYPE_VEHICLE_TEAM -> {
log(TAG, "收到车队邀请")
CallController.callController.getMsgCallBack().forEach {
it.value.receiverVehicleTeamInvitation()
}
}
CALL_TYPE_VOICE, CALL_TYPE_MATCHING -> {
log(TAG, "收到来电")
CallController.callController.getMsgCallBack().forEach {
it.value.receiverCalling(message)
}
}
}
}
override fun updateExitRoomStatus(message: Message?) {
if (message != null) {
log(TAG, "updateExitRoomStatus message : $message")
if (message.teamMember != null && message.teamMember.isNotEmpty()) {
log(TAG, "收到某人退出车队通知")
CallController.callController.getMsgCallBack().forEach {
it.value.receiverSomeoneExitVehicleTeam()
it.value.vehicleTeamMemberChange(message.teamMember)
}
} else {
SocketClientFactory.socketClient.stopHeartBeat()
log(TAG, "接收到被动挂断电话、车队消息")
CallController.callController.getMsgCallBack().forEach {
it.value.receiverHangUpInfo()
}
log(TAG, "exitRoom")
ChatController.updateExitRoomStatus()
}
} else {
SocketClientFactory.socketClient.stopHeartBeat()
log(TAG, "接收到被动挂断电话、车队消息")
CallController.callController.getMsgCallBack().forEach {
it.value.receiverHangUpInfo()
}
log(TAG, "exitRoom")
ChatController.updateExitRoomStatus()
}
}
override fun callingReceiverRefuse(message: Message) {
log(TAG, "收到被邀请人拒绝通知")
when(message.type){
CALL_TYPE_VOICE, CALL_TYPE_MATCHING -> {
CallController.callController.getMsgCallBack().forEach {
it.value.receiverCallRefuse()
}
}
CALL_TYPE_VEHICLE_TEAM -> {
CallController.callController.getMsgCallBack().forEach {
it.value.receiverVehicleTeamInviteRefuse()
}
}
}
}
}

View File

@@ -0,0 +1,13 @@
package com.mogo.chat.callcenter
import com.mogo.websocket.SocketClient
open class SocketClientFactory {
companion object{
val socketClient by lazy(LazyThreadSafetyMode.SYNCHRONIZED){
SocketClient()
}
}
}

View File

@@ -0,0 +1,59 @@
package com.mogo.chat.common.gme
import android.os.Handler
class EnginePollHelper private constructor() {
companion object {
private var s_enginePollHelper: EnginePollHelper? = null
private var s_pollEnabled = true
fun createEnginePollHelper() {
if (s_enginePollHelper == null) {
s_enginePollHelper = EnginePollHelper()
s_enginePollHelper!!.startTimer()
}
}
fun destroyEnginePollHelper() {
if (s_enginePollHelper != null) {
s_enginePollHelper!!.stopTimer()
s_enginePollHelper = null
}
}
fun pauseEnginePollHelper() {
s_pollEnabled = false
}
fun resumeEnginePollHelper() {
s_pollEnabled = true
}
}
private val mHandler = Handler()
private val mRunnable = object : Runnable {
override fun run() {
if (s_pollEnabled) {
if (GMEHelper.getInstance().getTmgContext() != null) {
try {
GMEHelper.getInstance().getTmgContext()?.Poll()
} catch (e: Exception) {
e.printStackTrace()
}
}
}
mHandler.postDelayed(this, 15)
}
}
private fun startTimer() {
mHandler.postDelayed(mRunnable, 15)
}
private fun stopTimer() {
mHandler.removeCallbacks(mRunnable)
}
}

View File

@@ -0,0 +1,244 @@
package com.mogo.chat.common.gme
import android.content.Intent
import com.mogo.chat.callcenter.CallController.Companion.callController
import com.mogo.chat.callcenter.CallTypeManager.Companion.callTypeManager
import com.mogo.chat.callcenter.SocketClientFactory
import com.mogo.chat.callcenter.isCalling
import com.mogo.chat.callcenter.isReadyCalling
import com.mogo.chat.constant.*
import com.mogo.chat.util.UserInfoHelper.currentCallType
import com.mogo.chat.util.log
import com.mogo.chat.util.sp.getVoiceType
import com.mogo.commons.AbsMogoApplication
import com.tencent.TMG.ITMGContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
object GMEApi : IGMEEvent {
private var openID: String = ""
private var startSay: Boolean = false
fun init(openID: String): Boolean {
GMEApi.openID = openID
return GMEHelper.getInstance().init(AbsMogoApplication.getApp().applicationContext, SDKID, SDKKEY, openID)
}
private fun getTmgContext(): ITMGContext? {
return GMEHelper.getInstance().getTmgContext()
}
fun setVoiceType(voiceType: Int) {
getTmgContext()?.GetAudioEffectCtrl()?.SetVoiceType(voiceType)
}
//播放音效
fun setPlayEffect(soundId: Int, filePath: String, loop: Boolean) {
getTmgContext()?.GetAudioEffectCtrl()?.PlayEffect(soundId, filePath, loop)
}
//暂停播放音效
fun pausePlayEffect(soundId: Int) {
getTmgContext()?.GetAudioEffectCtrl()?.PauseEffect(soundId)
}
private fun enableAudio() {
startSay = true
enableMic(true)
enableSpeaker(true)
val audioControl = getTmgContext()!!.GetAudioCtrl()
audioControl.TrackingVolume(0.75.toFloat())
audioControl.EnableAudioCaptureDevice(true)
audioControl.EnableAudioSend(true)
audioControl.EnableAudioPlayDevice(true)
audioControl.EnableAudioRecv(true)
reMuteMic()
setVoiceType(getVoiceType())
}
fun enterRoom(roomId: String) {
getTmgContext()?.SetRecvMixStreamCount(6)
getTmgContext()?.SetAdvanceParams("SetSpeakerStreamType", "0")
val auth = auth(roomId)
log(TAG, "auth:$auth")
auth?.let {
realEnterRoom(roomId, ROOM_TYPE, it)
}
}
private fun auth(roomID: String): ByteArray? {
return GMEHelper.getInstance().auth(roomID)
}
private fun realEnterRoom(roomID: String, roomType: Int, authBuffer: ByteArray) {
GMEHelper.getInstance().enterRoom(roomID, roomType, authBuffer)
}
private fun enableMic(isEnable: Boolean) {
GMEHelper.getInstance().enableMic(isEnable)
}
private fun enableSpeaker(isEnable: Boolean) {
GMEHelper.getInstance().enableSpeaker(isEnable)
}
private fun exitRoom() {
startSay = false
GMEHelper.getInstance().exitRoom()
}
fun unInit() {
GMEHelper.getInstance().unInit()
}
fun pause() {
GMEHelper.getInstance().pause()
}
fun resume() {
GMEHelper.getInstance().resume()
}
private var igmCallBackList: MutableSet<IGMEEventCallBack> = mutableSetOf()
fun addEnterRoomEventCall(igmCallBack: IGMEEventCallBack) {
igmCallBackList.add(igmCallBack)
GMEHelper.getInstance().addEventCall(this)
addEventCall(ITMGContext.ITMG_MAIN_EVENT_TYPE.ITMG_MAIN_EVENT_TYPE_ENTER_ROOM, this)
}
private fun addEventCall(type: ITMGContext.ITMG_MAIN_EVENT_TYPE, call: IGMEEvent) {
GMEHelper.getInstance().addEventCall(type, call)
}
fun removeEventCall(type: ITMGContext.ITMG_MAIN_EVENT_TYPE, call: IGMEEvent) {
ITMGContext.ITMG_MAIN_EVENT_TYPE.ITMG_MAIN_EVENT_TYPE_ACCOMPANY_FINISH.name
GMEHelper.getInstance().removeEventCall(type, call)
}
private fun removeEventCall(call: IGMEEvent) {
GMEHelper.getInstance().removeEventCall(call)
}
fun isRoomEntered(): Boolean {
return GMEHelper.getInstance().isRoomEntered()
}
fun getMicState(): Int {
return GMEHelper.getInstance().getMicState()
}
fun getRoomType(): Int {
return GMEHelper.getInstance().getRoomType()
}
fun muteMic() {
GMEHelper.getInstance().muteMic()
}
fun reMuteMic(micVolume: Int = 100) {
GMEHelper.getInstance().reMuteMic(micVolume)
}
fun isMicMute(): Boolean {
return GMEHelper.getInstance().isMicMute()
}
override fun onEventExitRoom() {
//退房成功停止Volume刷新
log(TAG, "exitRoom")
updateExitRoomStatus()
}
override fun onEventUserUpdate(eventId: Int, intent: Intent) {
val userList = TMGCallbackHelper.parseUserList(intent)
onSubEvent(eventId, userList)
}
private fun onSubEvent(eventId: Int, userList: Array<String>) {
log(TAG, "==Event==onSubEvent: $eventId")
when (eventId) {
ITMGContext.ITMG_EVENT_ID_USER_ENTER -> {
if (userList.isNotEmpty()) {
val filterList = userList.filter {
log(TAG, "成员进房====$it===ownId:$openID")
it.toInt() > 99999
}
log(TAG, "成员进房==去掉99999====$filterList")
if (filterList.isNotEmpty()) {
// 去掉了99999以下的管理员用户还剩下两个人那就开始通话
log(TAG, "GMEApi 成员进房==去掉了99999以下的管理员用户还有人那就开始通话")
if(!startSay){
startSay()
}
// 如果进房的不是自己,且自身状态并未及时改变,计划在此处增加一个容错判断
if (!filterList.contains(openID) && (callTypeManager.callStatus.isReadyCalling() || callTypeManager.callStatus.isCalling())) {
callController.callingSenderWithoutMessage()
}
} else {
log(TAG, "成员进房==去掉了99999以下的管理员用户没有别人了啥也不干")
}
} else {
log(TAG, "成员进房==进房成员列表为空,啥也不干")
}
}
ITMGContext.ITMG_EVENT_ID_USER_EXIT -> {
//退房成功停止Volume刷新
if (userList.isNotEmpty()) {
val filterList = userList.filter {
log(TAG, "成员退房====$it")
it.toInt() > 99999
}
log(TAG, "成员退房==去掉99999====$filterList")
if (filterList.isEmpty()) {
log(TAG, "成员退房==去掉了99999以下的管理员用户没有人了啥也不干")
} else {
log(TAG, "成员退房==去掉了99999以下的管理员用户还有别人, type为语音电话或者匹配则可退房 currentCallType : $currentCallType")
if(currentCallType != CALL_TYPE_VEHICLE_TEAM){
dealHangUpAfter()
}
}
} else {
log(TAG, "成员退房==人都走干净了,准备退房")
dealHangUpAfter()
}
}
ITMGContext.ITMG_EVENT_ID_USER_HAS_AUDIO -> {
log(TAG, "user_update--user send audio")
}
}
}
private fun dealHangUpAfter() {
igmCallBackList.forEach {
it.gmeHangUp()
}
SocketClientFactory.socketClient.stopHeartBeat()
}
private fun startSay() {
log(TAG, "开始讲话================")
GlobalScope.launch(Dispatchers.Main) {
if (isRoomEntered()) {
enableAudio()
log(TAG, "============enableAudio() : ${getMicState()}")
} else {
log(TAG, "============还未进入房间")
}
}
}
fun updateExitRoomStatus(igmCallBack: IGMEEventCallBack? = null) {
SocketClientFactory.socketClient.stopHeartBeat()
exitRoom()
getTmgContext()?.GetAudioCtrl()?.StopTrackingVolume()
igmCallBack?.let {
igmCallBackList.remove(it)
}
removeEventCall(this)
callController.initCall()
}
}

View File

@@ -0,0 +1,224 @@
package com.mogo.chat.common.gme
import android.content.Context
import android.text.TextUtils
import com.mogo.chat.constant.TAG
import com.mogo.chat.util.log
import com.mogo.commons.AbsMogoApplication
import com.mogo.commons.debug.DebugConfig
import com.tencent.TMG.ITMGContext
import com.tencent.av.sig.AuthBuffer
class GMEHelper {
private constructor() {
}
companion object {
@Volatile
private var instance: GMEHelper? = null
fun getInstance(): GMEHelper {
if (instance == null) {
synchronized(GMEHelper::class) {
if (instance == null) {
instance = GMEHelper()
}
}
}
return instance!!
}
}
private var tmgContext: ITMGContext? = null
private var mSdkAppID: String? = null
private var mSdkKey: String? = null
private var mUserID: String? = null
private var initFinish: Boolean = false
fun init(context: Context, sdkAppId: String, sdkKey: String, openID: String): Boolean {
tmgContext = ITMGContext.GetInstance(context)
mSdkAppID = sdkAppId
mSdkKey = sdkKey
mUserID = openID
tmgContext?.let {
// 初始化SDK使用SDK必须先调用此接口
it.Init(sdkAppId, openID)
if (DebugConfig.isDebug()) {
// debug模式打印全日志线上环境使用默认设置
it.SetLogLevel(ITMGContext.ITMG_LOG_LEVEL_INFO, ITMGContext.ITMG_LOG_LEVEL_VERBOSE)
}
// 设置Poll请周期性的调用Poll接口以保证接口正常使用。
EnginePollHelper.createEnginePollHelper()
// 设置委托
it.SetTMGDelegate(TMGCallbackDispatcher.getInstance().itmgDelegate)
auth("0")
initFinish = true
return true
}
return false
}
fun initFinish(): Boolean {
return initFinish
}
fun getTmgContext(): ITMGContext? {
return tmgContext ?: ITMGContext.GetInstance(AbsMogoApplication.getApp().applicationContext)
}
fun auth(roomID: String): ByteArray? {
// 离线语音房间号参数必须填 null
val authBuffer = AuthBuffer.getInstance()
.genAuthBuffer(Integer.parseInt(mSdkAppID!!), roomID, mUserID, mSdkKey)
if (TextUtils.equals("0", roomID)) {
tmgContext!!.GetPTT().ApplyPTTAuthbuffer(authBuffer)
}
return authBuffer
}
fun enterRoom(roomID: String, roomType: Int, authBuffer: ByteArray) {
val result = tmgContext?.EnterRoom(roomID, roomType, authBuffer)
log(TAG, "enterRoom-result:${result}")
}
/**
* 麦克风静音
*/
fun muteMic() {
tmgContext?.GetAudioCtrl()?.SetMicVolume(0)
}
/**
* 解除麦克风静音
*/
fun reMuteMic(micVolume: Int = 150) {
tmgContext?.GetAudioCtrl()?.SetMicVolume(micVolume)
}
/**
* 麦克风是否静音之所以判断101是因为文档中写如果获取音量值返回值为101则代表没有调用过设置音量接口说明从未静音
* @return true- 处于静音状态 false-处于非静音状态
*/
fun isMicMute(): Boolean {
return tmgContext?.GetAudioCtrl()?.GetMicVolume() != 101 && tmgContext?.GetAudioCtrl()?.GetMicVolume() == 0
}
fun isRoomEntered(): Boolean {
return if (initFinish) {
tmgContext!!.IsRoomEntered()
} else {
false
}
}
fun getMicState(): Int {
return if (initFinish) {
tmgContext!!.GetAudioCtrl().GetMicState()
} else {
0
}
}
fun getRoomType(): Int {
return if (initFinish) {
tmgContext!!.GetRoom().GetRoomType()
} else {
0
}
}
fun enableMic(isEnable: Boolean) {
tmgContext?.GetAudioCtrl()?.SetSpeakerVolume(200)
tmgContext?.GetAudioCtrl()?.EnableMic(isEnable)
}
/**
* 开启扬声器
* 扬声器状态
* 0关闭
* 1打开
* 2扬声器正在操作
*/
fun enableSpeaker(isEnable: Boolean) {
tmgContext?.GetAudioCtrl()?.EnableSpeaker(isEnable)
val speakerStatus = tmgContext?.GetAudioCtrl()?.GetSpeakerState()
log(TAG, "speakerStatus: $speakerStatus")
}
fun exitRoom() {
tmgContext?.ExitRoom()
}
fun unInit() {
tmgContext?.Uninit()
}
fun pause() {
tmgContext?.Pause()
}
fun resume() {
tmgContext?.Resume()
}
fun addEventCall(type: ITMGContext.ITMG_MAIN_EVENT_TYPE, call: IGMEEvent) {
TMGCallbackDispatcher.getInstance().addDelegate(type, call)
}
fun removeEventCall(type: ITMGContext.ITMG_MAIN_EVENT_TYPE, call: IGMEEvent) {
TMGCallbackDispatcher.getInstance().removeDelegate(type, call)
}
fun addEventCall(call: IGMEEvent) {
addEventCall(ITMGContext.ITMG_MAIN_EVENT_TYPE.ITMG_MAIN_EVNET_TYPE_USER_UPDATE, call)
addEventCall(ITMGContext.ITMG_MAIN_EVENT_TYPE.ITMG_MAIN_EVENT_TYPE_EXIT_ROOM, call)
addEventCall(ITMGContext.ITMG_MAIN_EVENT_TYPE.ITMG_MAIN_EVENT_TYPE_ROOM_DISCONNECT, call)
addEventCall(ITMGContext.ITMG_MAIN_EVENT_TYPE.ITMG_MAIN_EVNET_TYPE_USER_VOLUMES, call)
addEventCall(ITMGContext.ITMG_MAIN_EVENT_TYPE.ITMG_MAIN_EVENT_TYPE_ACCOMPANY_FINISH, call)
addEventCall(ITMGContext.ITMG_MAIN_EVENT_TYPE.ITMG_MAIN_EVENT_TYPE_CHANGE_ROOM_TYPE, call)
addEventCall(ITMGContext.ITMG_MAIN_EVENT_TYPE.ITMG_MAIN_EVENT_TYPE_AUDIO_DATA_EMPTY, call)
addEventCall(
ITMGContext.ITMG_MAIN_EVENT_TYPE.ITMG_MAIN_EVENT_TYPE_NUMBER_OF_USERS_UPDATE,
call
)
addEventCall(
ITMGContext.ITMG_MAIN_EVENT_TYPE.ITMG_MAIN_EVENT_TYPE_NUMBER_OF_AUDIOSTREAMS_UPDATE,
call
)
}
fun removeEventCall(call: IGMEEvent) {
removeEventCall(ITMGContext.ITMG_MAIN_EVENT_TYPE.ITMG_MAIN_EVNET_TYPE_USER_UPDATE, call)
removeEventCall(ITMGContext.ITMG_MAIN_EVENT_TYPE.ITMG_MAIN_EVENT_TYPE_EXIT_ROOM, call)
removeEventCall(ITMGContext.ITMG_MAIN_EVENT_TYPE.ITMG_MAIN_EVENT_TYPE_ROOM_DISCONNECT, call)
removeEventCall(ITMGContext.ITMG_MAIN_EVENT_TYPE.ITMG_MAIN_EVNET_TYPE_USER_VOLUMES, call)
removeEventCall(
ITMGContext.ITMG_MAIN_EVENT_TYPE.ITMG_MAIN_EVENT_TYPE_ACCOMPANY_FINISH,
call
)
removeEventCall(
ITMGContext.ITMG_MAIN_EVENT_TYPE.ITMG_MAIN_EVENT_TYPE_CHANGE_ROOM_TYPE,
call
)
removeEventCall(
ITMGContext.ITMG_MAIN_EVENT_TYPE.ITMG_MAIN_EVENT_TYPE_AUDIO_DATA_EMPTY,
call
)
removeEventCall(
ITMGContext.ITMG_MAIN_EVENT_TYPE.ITMG_MAIN_EVENT_TYPE_NUMBER_OF_USERS_UPDATE,
call
)
removeEventCall(
ITMGContext.ITMG_MAIN_EVENT_TYPE.ITMG_MAIN_EVENT_TYPE_NUMBER_OF_AUDIOSTREAMS_UPDATE,
call
)
}
fun getUserId(): String? {
return mUserID
}
}

View File

@@ -0,0 +1,50 @@
package com.mogo.chat.common.gme
import android.content.Intent
import com.mogo.chat.constant.TAG
import com.mogo.chat.util.log
import com.tencent.TMG.ITMGContext
interface IGMEEvent {
fun onEvent(type: ITMGContext.ITMG_MAIN_EVENT_TYPE, data: Intent) {
log(TAG,"IGMEEvent 接收到gme消息 onEvent --$type")
when (type) {
ITMGContext.ITMG_MAIN_EVENT_TYPE.ITMG_MAIN_EVENT_TYPE_ENTER_ROOM -> {
parseErrorInfo(data) { code, msg ->
onEventEnterRoom(code, msg)
}
}
ITMGContext.ITMG_MAIN_EVENT_TYPE.ITMG_MAIN_EVENT_TYPE_EXIT_ROOM -> {
onEventExitRoom()
}
ITMGContext.ITMG_MAIN_EVENT_TYPE.ITMG_MAIN_EVNET_TYPE_USER_UPDATE -> {
parseSubEvent(data) { subEvent ->
onEventUserUpdate(subEvent,data)
}
}
else -> return
}
}
fun onEventEnterRoom(errorCode: Int, msg: String){
}
fun onEventExitRoom()
fun onEventUserUpdate(eventId: Int,intent:Intent)
private fun parseErrorInfo(data: Intent, parse: (code: Int, msg: String) -> Unit){
val nErrCode = TMGCallbackHelper.parseIntentParams2(data).nErrCode
val strMsg = TMGCallbackHelper.parseIntentParams2(data).strErrMsg
return parse.invoke(nErrCode, strMsg)
}
private fun parseSubEvent(data: Intent, parse: (subEvent: Int) -> Unit) {
val subEvent = TMGCallbackHelper.parseSubEvent(data).toInt()
return parse.invoke(subEvent)
}
}

View File

@@ -0,0 +1,5 @@
package com.mogo.chat.common.gme
interface IGMEEventCallBack {
fun gmeHangUp()
}

View File

@@ -0,0 +1,64 @@
package com.mogo.chat.common.gme
import android.content.Intent
import com.tencent.TMG.ITMGContext
import java.util.ArrayList
import java.util.HashMap
class TMGCallbackDispatcher private constructor() {
private val mapCallbacks = HashMap<ITMGContext.ITMG_MAIN_EVENT_TYPE, ArrayList<IGMEEvent>>()
var itmgDelegate: ITMGContext.ITMGDelegate? = null
companion object {
private var s_dispatcher: TMGCallbackDispatcher? = null
fun getInstance(): TMGCallbackDispatcher {
if (s_dispatcher == null) {
s_dispatcher = TMGCallbackDispatcher()
}
return s_dispatcher!!
}
}
init {
itmgDelegate = object : ITMGContext.ITMGDelegate() {
override fun OnEvent(type: ITMGContext.ITMG_MAIN_EVENT_TYPE?, data: Intent?) {
if (mapCallbacks.containsKey(type)) {
val lst = mapCallbacks[type]
for (i in lst!!.indices) {
lst[i].onEvent(type!!, data!!)
}
}
super.OnEvent(type, data)
}
}
}
fun addDelegate(type: ITMGContext.ITMG_MAIN_EVENT_TYPE, dispatcher: IGMEEvent) {
if (mapCallbacks.containsKey(type)) {
val lstDispatcher = mapCallbacks[type]
if (!lstDispatcher!!.contains(dispatcher)) {
lstDispatcher.add(dispatcher)
}
return
} else {
val lstCallbacks = ArrayList<IGMEEvent>()
lstCallbacks.add(dispatcher)
mapCallbacks[type] = lstCallbacks
}
}
fun removeDelegate(type: ITMGContext.ITMG_MAIN_EVENT_TYPE, dispatcher: IGMEEvent) {
if (mapCallbacks.containsKey(type)) {
val lstDispatcher = mapCallbacks[type]
if (lstDispatcher!!.contains(dispatcher)) {
lstDispatcher.remove(dispatcher)
}
return
}
}
}

View File

@@ -0,0 +1,62 @@
package com.mogo.chat.common.gme;
import android.content.Intent;
public class TMGCallbackHelper {
private TMGCallbackHelper(){
}
public static Integer parseSubEvent(Intent intent){
return intent.getIntExtra("event_id", 0);
}
public static String[] parseUserList(Intent intent){
String[] userList = intent.getStringArrayExtra("user_list");
if (userList == null) {
userList = new String[0];
}
return userList;
}
public static class Params2 {
public int nErrCode;
public String strErrMsg;
}
private static final Params2 params2 = new Params2();
public static Params2 parseIntentParams2(Intent intent) {
params2.nErrCode = intent.getIntExtra("result", -1);
params2.strErrMsg = intent.getStringExtra("error_info");
return params2;
}
public static class ParamsUerInfo {
public int nEventID;
public String[] identifierList;
}
private static final ParamsUerInfo paramsUerInfo = new ParamsUerInfo();
public static ParamsUerInfo parseUserInfoUpdateInfoIntent(Intent intent) {
paramsUerInfo.nEventID = intent.getIntExtra("event_id", 0);
paramsUerInfo.identifierList = intent.getStringArrayExtra("user_list");
return paramsUerInfo;
}
public static class ParamsAudioDeviceInfo {
public boolean bState;
public int nErrCode;
}
private static final ParamsAudioDeviceInfo paramsAudioDeviceInfo = new ParamsAudioDeviceInfo();
static ParamsAudioDeviceInfo parseAudioDeviceInfoIntent(Intent intent) {
paramsAudioDeviceInfo.bState = intent.getBooleanExtra("audio_state", false);
paramsAudioDeviceInfo.nErrCode = intent.getIntExtra("audio_errcode", 0);
return paramsAudioDeviceInfo;
}
}

View File

@@ -0,0 +1,72 @@
package com.mogo.chat.constant
const val TAG = "CALL_CHAT" //全局TAG
// 与相关接口复用
/**
* 服务端下发消息状态为0存在三种场景
* 1.语音聊天创建房间成功,给拨打方发送创建房间消息。
* 2.有匹配过的车机,表示愿意聊天,如果有人匹配不到,会给有愿意聊天的用户发来打电话邀请
* 3.接收车队邀请消息
*/
const val PUSH_MSG_CREATE_ROOM = 0
/**
* 服务端下发消息状态为1存在三种场景
* 1.语音聊天对方同意,下发同意消息
* 2.匹配成功消息
* 3.车队邀请对方同意,下发同意消息
*/
const val PUSH_MSG_AGREE_ENTER = 1
/**
* 服务端下发消息状态为2存在三种场景
* 1.语音邀请被拒绝
* 2.被动匹配接收到来电邀请,被拒绝
* 3.车队邀请被拒绝
*/
const val PUSH_MSG_DENY_ENTER = 2
/**
* 服务端下发消息状态为3存在两种场景
* 1.语音和匹配接收到消息下发,挂断电话
* 2.车队接收到消息下发,如果消息中有队员,则接收到队员退房消息,若无队员,则挂断车队电话
*/
const val PUSH_MSG_HANG_UP = 3
/**
* 表示超时取消匹配
*/
const val PUSH_MSG_CANCEL_MATCH = 4
const val PUSH_MSG_ADD_FRIEND = 5 // 提示添加好友
//GME相关信息
const val ROOM_TYPE = 1
const val SDKID = "1400280276"
const val SDKKEY = "I0USylN9YQq0CAiq"
// 通话类型
const val CALL_TYPE_DEFAULT = -1
const val CALL_TYPE_VOICE = 0
const val CALL_TYPE_MATCHING = 1
const val CALL_TYPE_VIDEO = 2
const val CALL_TYPE_VEHICLE_TEAM = 3
//WebSocket发送数据相关
const val SOCKET_HAND_SHAKE = 0
const val SOCKET_HEART_BEAT = 1
// 关注状态
const val NOT_FOCUS = 0
const val HAS_FOCUS = 1
const val NO_FOCUS_STATUS = -1
// 在线状态
const val ONLINE_STATUS = 0
const val OFFLINE_STATUS = 1
const val NO_LINE_STATUS = -1
// 提示加好友
const val ADD_FRIEND_MESSAGE = "ADD_FRIEND_MESSAGE"

View File

@@ -0,0 +1,42 @@
package com.mogo.chat.constant
import com.mogo.commons.debug.DebugConfig.*
class HttpConstants {
companion object {
const val DEV_BASE_URL_OWNER = "http://dzt-show.zhidaohulian.com/"
const val DEV_CONFIG_URL = "http://dzt-test.zhidaohulian.com/"
const val RELEASE_BASE_URL_OWNER = "http://dzt.zhidaohulian.com/"
const val SOCKET_SERVER = "ws://62.234.196.121:4001/ws"
const val DEV_SOCKET_SERVER = "ws://dzt-test.zhidaohulian.com/ws"
fun getBaseUrl(): String {
return when (getNetMode()) {
NET_MODE_DEV, NET_MODE_QA, NET_MODE_DEMO -> DEV_BASE_URL_OWNER
NET_MODE_RELEASE -> RELEASE_BASE_URL_OWNER
else -> RELEASE_BASE_URL_OWNER
}
}
fun getSocketServer(): String {
return when (getNetMode()) {
NET_MODE_DEV, NET_MODE_QA, NET_MODE_DEMO -> DEV_SOCKET_SERVER
NET_MODE_RELEASE -> SOCKET_SERVER
else -> SOCKET_SERVER
}
}
fun getConfig(): String {
return when (getNetMode()) {
NET_MODE_DEV, NET_MODE_QA, NET_MODE_DEMO -> DEV_CONFIG_URL
NET_MODE_RELEASE -> RELEASE_BASE_URL_OWNER
else -> RELEASE_BASE_URL_OWNER
}
}
}
}

View File

@@ -0,0 +1,7 @@
package com.mogo.chat.constant
enum class RequestCode {
MATCH_ACTIVITY,
PERSONAL_CENTER_ACTIVITY
}

View File

@@ -0,0 +1,9 @@
package com.mogo.chat.constant
enum class ResultCode {
MATCH_CANCEL,
MATCH_ENTRY_ROOM,
MATCH_FAIL,
PERSONAL_CENTER_CALL
}

View File

@@ -0,0 +1,15 @@
package com.mogo.chat.exception
class ApiException : CommonException {
companion object {
val NULL_REQUEST_DATA_API_EXCEPTION = ApiException(2, "request data is null")
val ENTER_ROOM_API_EXCEPTION = ApiException(3, "roomId is null or already enter room")
}
constructor(code: Int, msg: String) : super(code, msg)
fun getErrorMsg():String{
return msg
}
}

View File

@@ -0,0 +1,18 @@
package com.mogo.chat.exception
open class CommonException :Exception{
companion object{
val NETWORK_EXCEPTION = CommonException(1, "network is error")
val NULL_EXCEPTION = CommonException(1, "exception is null")
}
protected var code: Int = 0
protected var msg: String = ""
constructor(code: Int, msg: String) : super(msg) {
this.code = code
this.msg = msg
}
}

View File

@@ -0,0 +1,29 @@
package com.mogo.chat.model
import com.mogo.chat.base.BaseRepository
import com.mogo.chat.base.BaseResponse
import com.mogo.chat.constant.HttpConstants.Companion.getConfig
import com.mogo.chat.model.bean.OnLineStatus
import com.mogo.chat.model.bean.UserInfoBySns
import com.mogo.chat.model.bean.UserInfoBySnsRequest
class ChatServiceModel : BaseRepository() {
//传入对方SN需要注意以后扩展时传参的改变
suspend fun isOnLine(sn: String): BaseResponse<OnLineStatus> {
val map = hashMapOf<String, String>()
map["sn"] = sn
return apiCall {
getNetWorkApi().isOnLine(map)
}
}
suspend fun queryUserInfo(sn: String): BaseResponse<UserInfoBySns> {
val sns = arrayListOf(sn)
val requestData = UserInfoBySnsRequest(sns)
return apiCall {
getNetWorkApi(getConfig()).queryUserInfoBySnS(requestData)
}
}
}

View File

@@ -0,0 +1,33 @@
package com.mogo.chat.model
import com.google.gson.Gson
import com.mogo.chat.base.BaseRepository
import com.mogo.chat.base.BaseResponse
import com.mogo.chat.model.bean.FocusBlackListRequest
import com.mogo.chat.model.bean.FocusData
import com.mogo.chat.model.bean.FriendRequest
import com.mogo.cloud.passport.MoGoAiCloudClientConfig
class FocusModel : BaseRepository() {
companion object {
const val TAG = "FocusModel"
}
suspend fun getFocusPage(pageIndex: Int): BaseResponse<FocusData> {
val friendRequest = Gson().toJson(FriendRequest(pageIndex, 10))
val map = mapOf("sn" to MoGoAiCloudClientConfig.getInstance().sn, "data" to friendRequest)
return apiCall { getNetWorkApi().getFocusPage(map) }
}
/**
* targetSn:要操作的目标SN
* operate: 2:拉黑 3:取消拉黑
*/
suspend fun dealBlackList(targetSn: String, operate: Int): BaseResponse<Any> {
val focusBlackListRequest = Gson().toJson(FocusBlackListRequest(targetSn, operate))
val map = mapOf("sn" to MoGoAiCloudClientConfig.getInstance().sn, "data" to focusBlackListRequest)
return apiCall { getNetWorkApi().dealBlackList(map) }
}
}

View File

@@ -0,0 +1,29 @@
package com.mogo.chat.model
import com.google.gson.Gson
import com.mogo.chat.base.BaseRepository
import com.mogo.chat.base.BaseResponse
import com.mogo.chat.model.bean.FriendData
import com.mogo.chat.model.bean.FriendOrSelfOnLine
import com.mogo.chat.model.bean.FriendRequest
import com.mogo.cloud.passport.MoGoAiCloudClientConfig
class FriendModel : BaseRepository() {
companion object {
const val TAG = "FriendModel"
}
suspend fun getFriendPage(pageIndex: Int): BaseResponse<FriendData> {
val friendRequest = Gson().toJson(FriendRequest(pageIndex, 10))
val map = mapOf("sn" to MoGoAiCloudClientConfig.getInstance().sn, "data" to friendRequest)
return apiCall { getNetWorkApi().getFriendPage(map) }
}
suspend fun canCallToFocus(snReceiver: String): BaseResponse<Any> {
val canCallStatus = Gson().toJson(FriendOrSelfOnLine(MoGoAiCloudClientConfig.getInstance().sn, snReceiver))
val map = mapOf("sn" to MoGoAiCloudClientConfig.getInstance().sn, "data" to canCallStatus)
return apiCall { getNetWorkApi().getChatStatus(map) }
}
}

View File

@@ -0,0 +1,6 @@
package com.mogo.chat.model.bean
/**
* 添加好友的push消息内容
*/
data class AddFriendMessage(val sn:String,val nickName: String, val headImgUrl: String, val message: String,val alertType: Int)

View File

@@ -0,0 +1,26 @@
package com.mogo.chat.model.bean
import com.mogo.cloud.passport.MoGoAiCloudClientConfig
fun AllUnit.toSns(): Sns {
return Sns(MoGoAiCloudClientConfig.getInstance().sn,
localNickName,
localHeadImgUrl,
localCarInfo,
cardIdSex,
cityName,
cardIdAge
)
}
data class AllUnit(
var localUserId: Int,
var localNickName: String,
var localHeadImgUrl: String,
var localCarInfo: String,
var sns: List<Sns>,
var cardIdAge: String,
var cityName: String,
var cardIdSex: String
)

View File

@@ -0,0 +1,13 @@
package com.mogo.chat.model.bean
import com.mogo.chat.constant.CALL_TYPE_VOICE
class CallRequestParam(var snSender: String, var snReceiver: String, var nickName:String, var headImgUrl:String, var carInfo:String, var lat: Double?, var lon:Double?, callType: Int = CALL_TYPE_VOICE) {
var type: Int = callType
override fun toString(): String {
return "RoomParam(snSender='$snSender', snReceiver='$snReceiver', nickName='$nickName', headImgUrl='$headImgUrl', carInfo='$carInfo', lat=$lat, lon=$lon, type=$type)"
}
}

View File

@@ -0,0 +1,9 @@
package com.mogo.chat.model.bean
data class CarMessage(
val `receiver`: Int,
val message: Message,
val msgType: Int,
val pkgName: String,
val title: String
)

View File

@@ -0,0 +1,12 @@
package com.mogo.chat.model.bean
class CarSwitchStatus {
var sn:String
var status:Int
constructor(sn: String, status: Int) {
this.sn = sn
this.status = status
}
}

View File

@@ -0,0 +1,38 @@
package com.mogo.chat.model.bean
class ConnectStatusParam {
var snSender:String
var snReceiver:String
var roomId:Int
var status:Int
var type:Int
var lat: Double? = null//发送方的纬度
var lon: Double? = null//发送方的经度
var nickName: String? = null//发送方的昵称
var headImgUrl: String? = null//发送方的用户头像
var carInfo: String? = null//发送方的车辆信息
var cardIdAge: String? = null//年龄
var cardIdSex: String? = null//性别
var cityName: String? = null//城市
constructor(snSender: String, snReceiver: String, roomId: Int, status: Int,type:Int) {
this.snSender = snSender
this.snReceiver = snReceiver
this.roomId = roomId
this.status = status
this.type = type
}
override fun toString(): String {
return "ConnectStatusParam(snSender='$snSender', snReceiver='$snReceiver', roomId=$roomId, status=$status, type=$type, lat=$lat, lon=$lon, nickName=$nickName, headImgUrl=$headImgUrl, carInfo=$carInfo, cardIdAge=$cardIdAge, cardIdSex=$cardIdSex, cityName=$cityName)"
}
}

View File

@@ -0,0 +1,3 @@
package com.mogo.chat.model.bean
data class FocusStatus(val isFocus: Int)

View File

@@ -0,0 +1,87 @@
package com.mogo.chat.model.bean
data class FriendData(
val pageIndex: Int,
val pageSize: Int,
val totalPages: Int,
val totalElements: Int,
val friendList: List<FriendList>
)
data class FocusData(
val pageIndex: Int,
val pageSize: Int,
val totalPages: Int,
val totalElements: Int,
val friendList: List<FocusList>
)
/**
* carStatus == 1 可拨打
* carStatus == 0 不可拨打
*/
fun FriendList.canCall(): Boolean {
return carStatus == 1
}
fun FriendList.toSns(): Sns {
return Sns(sn, nickName, headImgUrl, carInfo, cardIdSex, cityName, cardIdAge)
}
data class FriendList(
val sn: String,
val nickName: String?,
val headImgUrl: String,
val carStatus: Int,
val cardIdSex: String,
val cityName: String,
val cardIdAge: String,
val carInfo: String
)
fun FocusList.isInBlackList(): Boolean {
return blackFlag == 1
}
data class FocusList(
val sn: String,
val nickName: String,
val headImgUrl: String,
var carStatus: Int, //车机是否在线 0是不在线 1是在线
val cardIdSex: String,
val cityName: String,
val cardIdAge: String,
val carInfo: String,
var eachFocusFlag: Boolean,//是否互粉
var blackFlag: Int //是否加入黑名单 0是未加入 1是加入
)
class FriendOrSelfOnLine {
var snSender: String
var snReceiver: String
constructor(snSender: String, snReceiver: String) {
this.snSender = snSender
this.snReceiver = snReceiver
}
}
class FriendRequest {
var pageIndex: Int
var pageSize: Int
constructor(pageIndex: Int, pageSize: Int) {
this.pageIndex = pageIndex
this.pageSize = pageSize
}
}
class FocusBlackListRequest {
var focusSn: String
var operate: Int
constructor(focusSn: String, operate: Int) {
this.focusSn = focusSn
this.operate = operate
}
}

View File

@@ -0,0 +1,8 @@
package com.mogo.chat.model.bean
data class Header(
val delayTime: Int,
val maxSpeed: Int,
val stayTime: Int,
val type: Int
)

View File

@@ -0,0 +1,3 @@
package com.mogo.chat.model.bean
data class InflectionBean(val inflectionId:Int,val inflectionName:String,val normalRes:Int,val checkedRes:Int,val exampleVoiceRaw:Int)

View File

@@ -0,0 +1,6 @@
package com.mogo.chat.model.bean
/**
* 长连接初步建立时的初始化信息封装
*/
data class InitMessage(val nickName:String,val headImg:String,val localUserId:Int)

View File

@@ -0,0 +1,22 @@
package com.mogo.chat.model.bean
class LiveBroadcast {
var eventId: String? = null
var sn: String
var type: Int = 0
var videoChannel: String
constructor(sn: String, videoChannel: String) {
this.sn = sn
this.videoChannel = videoChannel
}
constructor(eventId: String, sn: String, type: Int, videoChannel: String) {
this.eventId = eventId
this.sn = sn
this.type = type
this.videoChannel = videoChannel
}
}

View File

@@ -0,0 +1,16 @@
package com.mogo.chat.model.bean
class LiveBroadcastResult {
var videoChannel: String
var livePlayUrl: String
var playUrl: PlayUrl
constructor(videoChannel: String, livePlayUrl: String, playUrl: PlayUrl) {
this.videoChannel = videoChannel
this.livePlayUrl = livePlayUrl
this.playUrl = playUrl
}
}

View File

@@ -0,0 +1,14 @@
package com.mogo.chat.model.bean
class LocationCarsWithRadius {
var coordinates: List<Double>
var radius: Int
var type: String
var keyWord: String? = null
constructor(coordinates: List<Double>, radius: Int, type: String) {
this.coordinates = coordinates
this.radius = radius
this.type = type
}
}

View File

@@ -0,0 +1,12 @@
package com.mogo.chat.model.bean
data class MatchRequestParam(
var nickName: String,
var headImgUrl: String,
var carInfo: String,
var lat: Double,
var lon: Double,
var age: Int,
var cardIdSex: String? = null,
var cityName: String? = null,
)

View File

@@ -0,0 +1,55 @@
package com.mogo.chat.model.bean
import com.mogo.chat.constant.CALL_TYPE_MATCHING
import com.mogo.chat.constant.CALL_TYPE_VEHICLE_TEAM
import com.mogo.chat.constant.CALL_TYPE_VOICE
import java.io.Serializable
fun Message.isMatch(): Boolean {
return type == CALL_TYPE_MATCHING
}
fun Message.isCall(): Boolean {
return type == CALL_TYPE_VOICE
}
fun Message.isVehicleTeam(): Boolean {
return type == CALL_TYPE_VEHICLE_TEAM
}
//Message返回数据结构根据匹配传入信息 返回具体字段,服务端做转发,
//在Launcher2.0中传入数据不存在经纬度信息的而在车聊聊单独App中是存在经纬度但是不存在性别、城市、车型信息
//因此后期如若需要在车聊聊中添加缺少信息 需要做数据合并
fun Message.toSns(): Sns {
return Sns(
snSender,
roomId,
nickName,
headImgUrl,
carInfo,
cardIdSex,
cityName,
cardIdAge,
lat,
lon
)
}
data class Message(
val roomId: Int,
val snReceiver: String,
val snSender: String,
val status: Int,
val type: Int, //0语音通话 1匹配模式 2直播 3;车队
val nickName: String?,
val headImgUrl: String?,
val carInfo: String?,
val lat: Double,
val lon: Double,
val cardIdSex: String?,
val cityName: String?,
val cardIdAge: String?,
val teamMember: List<TeammateInfo>?
) : Serializable

View File

@@ -0,0 +1,11 @@
package com.mogo.chat.model.bean
fun OnLineStatus.onLine(): Boolean {
return carOnLine == 1 && onLine == 1
}
/**
* carOnLineACC ON状态 1:在线 2:不在线
* onLine:是否隐身 1:在线 2:隐身
*/
data class OnLineStatus(val carOnLine: Int, val onLine: Int)

View File

@@ -0,0 +1,14 @@
package com.mogo.chat.model.bean
class PlayUrl {
var rtmp: String
var flv: String
var hls: String
constructor(rtmp: String, flv: String, hls: String) {
this.rtmp = rtmp
this.flv = flv
this.hls = hls
}
}

View File

@@ -0,0 +1,5 @@
package com.mogo.chat.model.bean
class Results<out T>(val t :T) {
}

View File

@@ -0,0 +1,10 @@
package com.mogo.chat.model.bean
class RoomInfo {
var roomId:Int
constructor(roomId: Int) {
this.roomId = roomId
}
}

View File

@@ -0,0 +1,107 @@
package com.mogo.chat.model.bean
import android.text.TextUtils
import java.io.Serializable
class Sns : Serializable {
var sn: String = ""
var lat: Double = 0.0
var lon: Double = 0.0
var direction: Int = 0
var canLive: Int = 0//1:可直播 0:不可直播
var canVoice: Int = 1 //1:可语音 0不可语音
var nickName: String? = null
set(value) {
field = if (TextUtils.isEmpty(value)) {
"小蘑菇"
} else {
value
}
}
var userId: Int = 0 //用户ID 进入语音通话间唯一识别号、
var headImgUrl: String? = null
var carInfo: String? = null
var cardIdSex: String? = null
var cityName: String? = null
var cardIdAge: String? = null
constructor(sn: String, nickName: String?, headImgUrl: String?) {
this.canVoice = 1
this.sn = sn
this.nickName = nickName
this.headImgUrl = headImgUrl
}
constructor(
sn: String,
userId: Int,
nickName: String?,
carInfo: String?,
headImgUrl: String?,
lat: Double,
lon: Double
) {
this.sn = sn
this.lat = lat
this.lon = lon
this.direction = 0
this.canLive = 1
this.canVoice = 1
this.userId = userId
this.nickName = nickName
this.headImgUrl = headImgUrl
this.carInfo = carInfo
}
/**
* 可能会创建空sns在ChatService的onReceive里面的有此需求
*/
constructor(
sn: String = "",
nickName: String? = "",
headImgUrl: String? = "",
carInfo: String? = "",
gender: String? = "",
cityName: String? = "",
userAge: String? = ""
) {
this.sn = sn
this.nickName = nickName
this.headImgUrl = headImgUrl
this.carInfo = carInfo
this.cardIdSex = gender
this.cityName = cityName
this.cardIdAge = userAge
}
constructor(
sn: String = "",
roomId: Int,
nickName: String? = "",
headImgUrl: String? = "",
carInfo: String? = "",
cardIdSex: String? = "",
cityName: String? = "",
cardIdAge: String? = "",
lat: Double,
lon: Double
){
this.sn = sn
this.userId = roomId
this.nickName = nickName
this.headImgUrl = headImgUrl
this.carInfo = carInfo
this.cardIdSex = cardIdSex
this.cityName = cityName
this.cardIdAge = cardIdAge
this.lat = lat
this.lon = lon
}
override fun toString(): String {
return "Sns(sn='$sn', lat=$lat, lon=$lon, direction=$direction, canLive=$canLive, canVoice=$canVoice, nickName=$nickName, userId=$userId, headImgUrl=$headImgUrl, carInfo=$carInfo, cardIdSex=$cardIdSex, cityName=$cityName, cardIdAge=$cardIdAge)"
}
}

View File

@@ -0,0 +1,20 @@
package com.mogo.chat.model.bean
class SocketMsg {
var msgType:Int = 0
var sn:String? = null
var roomId:Int = 0
constructor(msgType: Int, sn: String?) {
this.msgType = msgType
this.sn = sn
}
constructor(msgType: Int, sn: String, roomId: Int) {
this.msgType = msgType
this.sn = sn
this.roomId = roomId
}
}

View File

@@ -0,0 +1,14 @@
package com.mogo.chat.model.bean
/**
* image: String //图片地址
* displayTime: Int //Splash页面图片显示时间
* effectiveFlag: Long //授权配置
* content: String //语音内容
*/
data class SplashConfig(
val image: String,
val displayTime: Int,
val effectiveFlag: Long,
val content: String
)

View File

@@ -0,0 +1,6 @@
package com.mogo.chat.model.bean
data class SplashConfigRequest(
val dataSource: String,
val serverType: String
)

View File

@@ -0,0 +1,17 @@
package com.mogo.chat.model.bean
/**
* created by wujifei on 2020/11/17 11:43
* describe:队员信息
*/
data class TeammateInfo(
var sn: String,
var nickName: String,
var headImgUrl: String,
var cardIdAge: Int,
var cardIdSex: String,
var cityName: String,
var lat: Double,
var lon: Double,
var carInfo: String,
var vehicleTeamLeader: Boolean)

View File

@@ -0,0 +1,5 @@
package com.mogo.chat.model.bean
data class TopicRequest(var topicGuide: TopicGuide)
data class TopicGuide(var topicContent:ArrayList<String>)

View File

@@ -0,0 +1,28 @@
package com.mogo.chat.model.bean
class UserInfoBySnsRequest {
var sns: List<String>? = null
constructor(sns: List<String>?) {
this.sns = sns
}
}
data class UserInfoBySns(val info: List<Info>)
data class Info(
val carAndUserInfoRedisVo: CarAndUserInfoRedisVo,
val realTimeLocationVo: RealTimeLocationVo
)
data class CarAndUserInfoRedisVo(
val sn:String?,
val userNickName: String?,
val headImgUrl: String?,
val cardIdSex: String?,
val cardIdAge: String?,
val lastBrandName: String?,
val lastActiveCity: String?
)
data class RealTimeLocationVo(val lon: Double, val lat: Double)

View File

@@ -0,0 +1,177 @@
package com.mogo.chat.model.control
import com.mogo.chat.R
import com.mogo.chat.base.BaseController
import com.mogo.chat.base.BaseResponse
import com.mogo.chat.callcenter.CallController.Companion.callController
import com.mogo.chat.callcenter.CallTypeManager.Companion.callTypeManager
import com.mogo.chat.callcenter.exchangeToCallType
import com.mogo.chat.common.gme.GMEApi
import com.mogo.chat.common.gme.IGMEEventCallBack
import com.mogo.chat.constant.CALL_TYPE_VOICE
import com.mogo.chat.constant.PUSH_MSG_DENY_ENTER
import com.mogo.chat.constant.PUSH_MSG_HANG_UP
import com.mogo.chat.constant.TAG
import com.mogo.chat.exception.ApiException.Companion.ENTER_ROOM_API_EXCEPTION
import com.mogo.chat.model.bean.RoomInfo
import com.mogo.chat.net.request
import com.mogo.chat.util.CallTimer.Companion.callTimer
import com.mogo.chat.util.MediaController
import com.mogo.chat.util.UserInfoHelper
import com.mogo.chat.util.audio.AudioFocusUtil
import com.mogo.chat.util.log
import com.mogo.chat.util.sp.saveRoomId
import com.mogo.chat.util.trackCall
import com.mogo.commons.AbsMogoApplication
object ChatController : BaseController(), IGMEEventCallBack {
private var roomId = 0
fun call(
snReceiver: String,
mRoomID: Int,
onSuccess: ((roomId: Int) -> Unit),
onError: (Exception) -> Unit
) {
log(TAG, "ChatController call ----- snReceiver: $snReceiver")
roomId = mRoomID
if (roomId == 0) {
requestRoomInfo(snReceiver, onSuccess, onError)
} else {
enterRoom(roomId)
callController.callingReceiver()
}
}
private fun requestRoomInfo(
snReceiver: String,
onSuccess: ((roomId: Int) -> Unit),
onError: (Exception) -> Unit
) {
request<BaseResponse<RoomInfo>> {
start {
playCallingAudio()
}
loader {
chatServiceModel.requestRoomInfo(
snReceiver,
callTypeManager.callStatus.exchangeToCallType()
)
}
onSuccess {
roomId = it.result.roomId
log(TAG, "ChatController 获取房间信息成功:$roomId")
//创建房间
if (roomId == 0 || GMEApi.isRoomEntered()) {
log(TAG, "ChatController 已经进入房间 ---> ${GMEApi.isRoomEntered()}")
onError.invoke(ENTER_ROOM_API_EXCEPTION)
return@onSuccess
}
callController.readyToCallSender()
onSuccess.invoke(roomId)
}
onError {
log(TAG, "ChatController 获取房间信息失败")
stopCallingAudio()
onError.invoke(it)
}
}
}
private var isPlayingCallingAudio = false
private fun playCallingAudio() {
if (!isPlayingCallingAudio) {
isPlayingCallingAudio = true
MediaController.startPlay(
AbsMogoApplication.getApp().applicationContext,
R.raw.call,
true
)
}
}
private fun stopCallingAudio() {
if (isPlayingCallingAudio) {
MediaController.release()
isPlayingCallingAudio = false
}
}
fun enterRoom(mRoomId: Int) {
log(TAG, "ChatController enterRoom ---> roomID:$mRoomId")
roomId = mRoomId
stopCallingAudio()
requestAudioFocus()
GMEApi.addEnterRoomEventCall(this@ChatController)
GMEApi.enterRoom(mRoomId.toString())
saveRoomId(mRoomId)
callTimer.start()
}
private fun requestAudioFocus() {
val isCan = AudioFocusUtil.getInstance().findMicFocus(AbsMogoApplication.getApp().applicationContext)
if (!isCan) {
AudioFocusUtil.getInstance().sendGetFocusIntent(AbsMogoApplication.getApp().applicationContext)
}
}
override fun gmeHangUp() {
requestConnectStatus(
UserInfoHelper.tmpSenderInfo.sn,
PUSH_MSG_HANG_UP
)
}
fun requestConnectStatus(
snReceiver: String,
status: Int,
onSuccess: (() -> Unit)? = null,
onError: ((Exception) -> Unit)? = null,
_roomId: Int = 0
) {
if (status != PUSH_MSG_HANG_UP) {
trackCall(callTypeManager.callStatus.exchangeToCallType(), status)
}
if (_roomId != 0) {
roomId = _roomId
}
request<BaseResponse<Any>> {
loader {
chatServiceModel.requestConnectStatus(CALL_TYPE_VOICE,
snReceiver, roomId, status,
callTypeManager.callStatus.exchangeToCallType()
)
}
onSuccess {
log(TAG, "ChatController 同步房间信息成功")
onSuccess?.invoke()
when (status) {
PUSH_MSG_HANG_UP, PUSH_MSG_DENY_ENTER -> {
updateExitRoomStatus()
}
}
}
onError {
log(TAG, "ChatController 同步房间信息失败:$it")
if (status == PUSH_MSG_HANG_UP) {
onSuccess?.invoke()
updateExitRoomStatus()
return@onError
}
onError?.invoke(it)
}
}
}
fun updateExitRoomStatus() {
log(TAG, "ChatController exitRoom")
roomId = 0
callTimer.stop()
stopCallingAudio()
AudioFocusUtil.getInstance().sendReleaseFocusIntent(AbsMogoApplication.getApp().applicationContext)
GMEApi.updateExitRoomStatus(this)
}
}

View File

@@ -0,0 +1,206 @@
package com.mogo.chat.model.control
import com.mogo.chat.R
import com.mogo.chat.aspect.DebugLog
import com.mogo.chat.base.BaseController
import com.mogo.chat.base.BaseResponse
import com.mogo.chat.callcenter.CallTypeManager.Companion.callTypeManager
import com.mogo.chat.callcenter.IMType
import com.mogo.chat.callcenter.canMatchTimeout
import com.mogo.chat.constant.TAG
import com.mogo.chat.model.bean.MatchRequestParam
import com.mogo.chat.net.request
import com.mogo.chat.net.taskDelayAsync
import com.mogo.chat.provider.ServiceApi
import com.mogo.chat.util.*
import com.mogo.chat.util.audio.AudioFocusUtil
import com.mogo.commons.AbsMogoApplication
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.withContext
object MatchController : BaseController() {
private const val MAX_AMOUNT_OF_CANCEL_MATCHING = 5
private var cancelMatchCount = 0
private var timeOutJob: Job? = null
private var retryJob: Job? = null
private const val DEFAULT_MATCH_TIME_OUT_TIME = 30 * 1000L // 30 * 1000L
private var arrayOfVoiceSources = intArrayOf(R.raw.match1, R.raw.match2, R.raw.match3)
private var isMatching = false
private var timeOut: (() -> Unit)? = null
private var startMatchTime: Long = 0L
fun resetMatchStatus() {
isMatching = false
stopTimeOutRecord()
log(TAG, "matchCallBack====$startMatchTime")
val time: Long = (System.currentTimeMillis() - startMatchTime)
startMatchTime = 0L
trackNormalEvent(TRACK_MATCH_SUCCESS, mutableMapOf("matchtime" to time))
}
fun startMatch(
invokeFlag: String?,
onSuccess: () -> Unit,
onError: (Exception) -> Unit,
timeOut: () -> Unit
) {
MatchController.timeOut = timeOut
request<BaseResponse<Any>> {
loader {
val location = ServiceApi.locationClient()?.lastKnowLocation
val param = MatchRequestParam(
UserInfoHelper.userInfo.nickName!!,
UserInfoHelper.userInfo.headImgUrl!!,
UserInfoHelper.userInfo.carInfo ?: "",
location?.latitude ?: 0.0,
location?.longitude ?: 0.0,
(UserInfoHelper.userInfo.cardIdAge ?: "0").toInt(),
UserInfoHelper.userInfo.cardIdSex,
UserInfoHelper.userInfo.cityName
)
if (!invokeFlag.isNullOrEmpty()) {
param.cardIdSex = UserInfoHelper.userInfo.cardIdSex
param.cityName = UserInfoHelper.userInfo.cityName
}
chatServiceModel.startMatch(param)
}
onSuccess {
log(TAG, "开始匹配成功")
onSuccess.invoke()
startTimeOutRecord()
// 开始播放匹配音乐
playMatchingAudio()
startMatchTime = System.currentTimeMillis()
callTypeManager.callStatus = IMType.MATCHING
isMatching = true
}
onError {
log(TAG, "开始匹配失败--${it.message}")
isMatching = false
onError.invoke(it)
}
}
}
private var isRecordingTimeOut = false
private fun startTimeOutRecord() {
if (!isRecordingTimeOut) {
isRecordingTimeOut = true
log(TAG, "开始本地超时计时===")
timeOutJob = taskDelayAsync(DEFAULT_MATCH_TIME_OUT_TIME) {
log(TAG, "本地计时匹配超时===")
withContext(Dispatchers.Main) {
// 本地计时匹配超时
if (startMatchTime != 0L) {
val time: Long = (System.currentTimeMillis() - startMatchTime)
startMatchTime = 0L
trackNormalEvent(TRACK_MATCH_FAIL, mutableMapOf("matchtime" to time))
}
stopMatchingAudio()
if (callTypeManager.callStatus.canMatchTimeout()) {
timeOut?.invoke()
}
isMatching = false
isRecordingTimeOut = false
}
}
}
}
private var isPlayingMatchingAudio = false
private fun playMatchingAudio() {
if (!isPlayingMatchingAudio) {
isPlayingMatchingAudio = true
AudioFocusUtil.getInstance().requireDuck()
MediaController.startPlay(
AbsMogoApplication.getApp().applicationContext,
arrayOfVoiceSources.random(),
true
)
}
}
private fun stopMatchingAudio() {
if (isPlayingMatchingAudio) {
MediaController.release()
isPlayingMatchingAudio = false
AudioFocusUtil.getInstance().releaseDuck()
}
}
@DebugLog
fun stopTimeOutRecord() {
if (isRecordingTimeOut) {
log(TAG, "结束本地超时计时=====")
stopMatchingAudio()
timeOutJob?.cancel()
isRecordingTimeOut = false
}
}
/**
* 服务端返回匹配超时
*/
fun matchTimeOutFromNet() {
log(TAG, "服务端返回匹配超时===$isMatching")
if (isMatching) {
if (startMatchTime != 0L) {
val time: Long = (System.currentTimeMillis() - startMatchTime)
startMatchTime = 0L
trackNormalEvent(TRACK_MATCH_FAIL, mutableMapOf("matchtime" to time))
}
timeOutJob?.cancel()
stopMatchingAudio()
if (callTypeManager.callStatus.canMatchTimeout()) {
timeOut?.invoke()
}
timeOut = null
isMatching = false
}
}
fun cancelMatch(onSuccess: (() -> Unit)? = null) {
callTypeManager.callStatus = IMType.INIT_CALL
stopTimeOutRecord()
if (startMatchTime != 0L) {
val time = (System.currentTimeMillis() - startMatchTime)
trackNormalEvent(TRACK_MATCH_CANCEL, mutableMapOf("matchtime" to time))
startMatchTime = 0L
}
request<BaseResponse<Any>> {
loader {
chatServiceModel.cancelMatch()
}
onSuccess {
log(TAG, "取消匹配成功")
cancelMatchCount = 0
onSuccess?.invoke()
isMatching = false
}
onError {
log(TAG, "取消匹配失败--$cancelMatchCount--${it.message}")
isMatching = false
if (cancelMatchCount < MAX_AMOUNT_OF_CANCEL_MATCHING) {
retryJob = taskDelayAsync(1000) {
withContext(Dispatchers.Main) {
cancelMatchCount++
cancelMatch()
}
}
} else {
retryJob?.cancel()
retryJob = null
cancelMatchCount = 0
onSuccess?.invoke()
}
}
}
}
}

View File

@@ -0,0 +1,83 @@
package com.mogo.chat.model.control
import com.mogo.chat.base.BaseController
import com.mogo.chat.base.BaseResponse
import com.mogo.chat.common.gme.IGMEEventCallBack
import com.mogo.chat.constant.CALL_TYPE_VEHICLE_TEAM
import com.mogo.chat.constant.PUSH_MSG_DENY_ENTER
import com.mogo.chat.constant.PUSH_MSG_HANG_UP
import com.mogo.chat.constant.TAG
import com.mogo.chat.model.bean.CallRequestParam
import com.mogo.chat.net.request
import com.mogo.chat.provider.ServiceApi
import com.mogo.chat.util.UserInfoHelper
import com.mogo.chat.util.log
import com.mogo.chat.util.sp.getRoomId
import com.mogo.cloud.passport.MoGoAiCloudClientConfig
import com.mogo.commons.network.Utils
object VehicleTeamController : BaseController(), IGMEEventCallBack {
fun inviteJoinVehicleTeam( invitedSn: String, onSuccess: () -> Unit, onError: (Exception) -> Unit) {
getUserInfo({
request<BaseResponse<Any>> {
loader {
val location = ServiceApi.locationClient()?.lastKnowLocation
val param = CallRequestParam(
MoGoAiCloudClientConfig.getInstance().sn, invitedSn,
UserInfoHelper.userInfo.nickName!!,
UserInfoHelper.userInfo.headImgUrl!!,
UserInfoHelper.userInfo.carInfo ?: "",
location?.latitude ?: 0.0,
location?.longitude ?: 0.0,
CALL_TYPE_VEHICLE_TEAM)
chatServiceModel.inviteJoinVehicleTeam(param)
}
onSuccess {
onSuccess.invoke()
}
onError {
onError.invoke(it)
}
}
},{
log(TAG,"获取用户信息失败,请稍后重试")
onError.invoke(Exception("获取用户信息失败,请稍后重试"))
})
}
override fun gmeHangUp() {
requestVehicleTeamConnectStatus(UserInfoHelper.userInfo.sn, PUSH_MSG_HANG_UP,CALL_TYPE_VEHICLE_TEAM)
}
fun requestVehicleTeamConnectStatus(
snReceiver: String = "",
status: Int,
type:Int,
onSuccess: (() -> Unit)? = null,
onError: ((Exception) -> Unit)? = null) {
request<BaseResponse<Any>> {
loader {
chatServiceModel.requestConnectStatus(CALL_TYPE_VEHICLE_TEAM, snReceiver, getRoomId(), status,type)
}
onSuccess {
log(TAG, "同步房间信息成功")
onSuccess?.invoke()
when (status) {
PUSH_MSG_HANG_UP, PUSH_MSG_DENY_ENTER -> {
ChatController.updateExitRoomStatus()
}
}
}
onError {
log(TAG, "同步房间信息失败:$it")
if (status == PUSH_MSG_HANG_UP) {
onSuccess?.invoke()
ChatController.updateExitRoomStatus()
return@onError
}
onError?.invoke(it)
}
}
}
}

View File

@@ -0,0 +1,92 @@
package com.mogo.chat.net
import androidx.lifecycle.LifecycleOwner
import com.mogo.chat.exception.ApiException
import com.mogo.chat.exception.ApiException.Companion.NULL_REQUEST_DATA_API_EXCEPTION
import com.mogo.chat.exception.CommonException.Companion.NETWORK_EXCEPTION
import com.mogo.chat.exception.CommonException.Companion.NULL_EXCEPTION
import com.mogo.chat.base.BaseResponse
import kotlinx.coroutines.*
import java.net.SocketTimeoutException
import java.net.UnknownHostException
import java.util.concurrent.TimeoutException
class Request<T> {
private lateinit var loader: suspend () -> T
private var start: (() -> Unit)? = null
private var onSuccess: ((T) -> Unit)? = null
private var onError: ((java.lang.Exception) -> Unit)? = null
private var onComplete: (() -> Unit)? = null
private var addLifecycle: LifecycleOwner? = null
infix fun loader(loader: suspend () -> T) {
this.loader = loader
}
infix fun start(start: (() -> Unit)?) {
this.start = start
}
infix fun onSuccess(onSuccess: ((T) -> Unit)?) {
this.onSuccess = onSuccess
}
infix fun onError(onError: ((java.lang.Exception) -> Unit)?) {
this.onError = onError
}
infix fun onComplete(onComplete: (() -> Unit)?) {
this.onComplete = onComplete
}
infix fun addLifecycle(addLifecycle: LifecycleOwner?) {
this.addLifecycle = addLifecycle
}
fun request() {
GlobalScope.launch(context = Dispatchers.Main) {
start?.invoke()
try {
val deferred = GlobalScope.async(Dispatchers.IO, start = CoroutineStart.LAZY) {
loader()
}
val result = deferred.await()
if (result != null && result is BaseResponse<*>) {
if (result.code == 0) {
onSuccess?.invoke(result)
} else {
throw ApiException(result.code, result.msg)
}
} else {
throw NULL_REQUEST_DATA_API_EXCEPTION
}
} catch (e: Exception) {
e.printStackTrace()
//数据打点
if (e == null) {
onError?.invoke(NULL_EXCEPTION)
}
when (e) {
is UnknownHostException -> onError?.invoke(NETWORK_EXCEPTION)
is TimeoutException -> onError?.invoke(NETWORK_EXCEPTION)
is SocketTimeoutException -> onError?.invoke(NETWORK_EXCEPTION)
else -> onError?.invoke(java.lang.Exception(e.message))
}
} finally {
onComplete?.invoke()
}
}
}
}
inline fun <T> request(buildRequest: Request<T>.() -> Unit) {
Request<T>().apply(buildRequest).request()
}

View File

@@ -0,0 +1,108 @@
package com.mogo.chat.net
import com.mogo.chat.base.BaseResponse
import com.mogo.chat.model.bean.*
import retrofit2.http.*
interface HttpApi {
//更改车机显示状态
@FormUrlEncoded
@POST("yycp-chat-service/car/circle/no/snStatus/v1")
suspend fun switchCarStatus(@FieldMap status: Map<String, String>): BaseResponse<Any>
//获取半径周边范围内的活跃车机
@FormUrlEncoded
@POST("yycp-chat-service/car/circle/no/getSn/v1")
suspend fun requestLiveCars(@FieldMap sns: Map<String, String>): BaseResponse<AllUnit>
//获取某台车机的视频直播流
@FormUrlEncoded
@POST("appDataService/integratedServices/app/push/no/livePush/v1")
suspend fun requestLive(@FieldMap liveBroadcast: Map<String, String>): BaseResponse<LiveBroadcastResult>
//直播心跳
@FormUrlEncoded
@POST("appDataService/integratedServices/app/push/no/heartbeat/v1")
suspend fun heartBeat(@FieldMap heartBeat: Map<String, String>): BaseResponse<Any>
//语音房间信息原路径dataService
@FormUrlEncoded
@POST("/yycp-chat-service/car/sender/no/createRoom/v1")
suspend fun requestRoomInfo(@FieldMap roomInfo: Map<String, String>): BaseResponse<RoomInfo>
//语音状态同步原路径dataService
@FormUrlEncoded
@POST("/yycp-chat-service/car/voiceRoom/no/operate/v1")
suspend fun requestConnectStatus(@Query("sn") sn: String, @FieldMap connectStatus: Map<String, String>): BaseResponse<Any>
// 开始匹配
@FormUrlEncoded
@POST("/yycp-chat-service/car/voiceRoom/no/findMatch/v1")
suspend fun startMatch(@Query("sn") sn: String, @FieldMap match: Map<String, String>): BaseResponse<Any>
// 取消匹配
@FormUrlEncoded
@POST("/yycp-chat-service/car/voiceRoom/no/cancleMatch/v1")
suspend fun cancelMatch(@Query("sn") sn: String, @FieldMap cancelMatch: Map<String, String>): BaseResponse<Any>
//邀请加入车队
@FormUrlEncoded
@POST("/yycp-chat-service/car/chat/no/inviteJoinTeam/v1")
suspend fun inviteJoinVehicleTeam(@FieldMap inviteVehicleTeam: Map<String, String>): BaseResponse<Any>
//车队状态同步
@FormUrlEncoded
@POST("/yycp-chat-service/car/chat/no/operateTeamRoom/v1")
suspend fun requestVehicleTeamConnectStatus(@FieldMap connectStatus: Map<String, String>): BaseResponse<Any>
//获取配置的加载图片
@FormUrlEncoded
@POST("deva/voiceGuideConfig/findVoiceGuideConfigBySn/v1")
suspend fun getSplashConfig(@FieldMap config: Map<String, String>): BaseResponse<SplashConfig>
//获取配置的引导话题
@FormUrlEncoded
@POST("/yycp-chat-service/car/topic/no/getNowTopic/v1")
suspend fun getTopicGuideContent(@FieldMap topGuideContent: Map<String, String>): BaseResponse<TopicRequest>
// 获取关注状态
@FormUrlEncoded
@POST("/yycp-chat-service/car/chatFoucs/no/checkFocus/v1")
suspend fun requestFocusStatus(@FieldMap focusSn: Map<String, String>): BaseResponse<FocusStatus>
//黑名单操作
@FormUrlEncoded
@POST("/yycp-chat-service/car/chatFoucs/no/operate/v1")
suspend fun dealBlackList(@FieldMap blackList: Map<String, String>): BaseResponse<Any>
// 添加关注
@FormUrlEncoded
@POST("/yycp-chat-service/car/chatFoucs/no/addFocus/v1")
suspend fun dealFocus(@FieldMap focusSn: Map<String, String>): BaseResponse<Any>
//查询好友列表
@JvmSuppressWildcards
@FormUrlEncoded
@POST("/yycp-chat-service/car/chatFoucs/no/getFocusPage/v1")
suspend fun getFriendPage(@FieldMap focusSn: Map<String, Any>): BaseResponse<FriendData>
//查询粉丝列表
@JvmSuppressWildcards
@FormUrlEncoded
@POST("/yycp-chat-service/car/chatFoucs/no/getByFocusPage/v1")
suspend fun getFocusPage(@FieldMap focusSn: Map<String, Any>): BaseResponse<FocusData>
// 获取通话状态
@FormUrlEncoded
@POST("/yycp-chat-service/car/chat/no/chatStatus/v1")
suspend fun getChatStatus(@FieldMap chatStatus: Map<String, String>): BaseResponse<Any>
//查询用户是否在线
@FormUrlEncoded
@POST("/yycp-chat-service/car/queryOnLineBySn/v1")
suspend fun isOnLine(@FieldMap onLine: Map<String, String>): BaseResponse<OnLineStatus>
@POST("/yycp-realtimeLocations/realTimeLocationServer/queryRsAncCarAndUserInfoBySns")
suspend fun queryUserInfoBySnS(@Body userInfoBySnsRequest: UserInfoBySnsRequest): BaseResponse<UserInfoBySns>
}

View File

@@ -0,0 +1,29 @@
package com.mogo.chat.net
import kotlinx.coroutines.*
@PublishedApi
internal var ThreadPool =
newFixedThreadPoolContext(Runtime.getRuntime().availableProcessors() * 2, "ThreadPool")
fun taskBlockOnMainThread(delayTime: Long = 0, job: suspend () -> Unit) = GlobalScope.launch(Dispatchers.Main) {
delay(delayTime)
job()
}
/**
* 并发执行,常用于最外层,延时操作
* 特点带返回值
*/
fun <T> taskDelayAsync(delayTime: Long = 0, job: suspend () -> T) = GlobalScope.async(ThreadPool) {
delay(delayTime)
job()
}
/**
* 并发执行
*/
fun <T> taskAsync(job:suspend () -> T) = GlobalScope.async {
job()
}

View File

@@ -0,0 +1,25 @@
package com.mogo.chat.provider
import com.mogo.commons.AbsMogoApplication
import com.mogo.map.location.IMogoLocationClient
import com.mogo.module.common.MogoApisHandler
import com.mogo.service.map.IMogoMapService
import com.mogo.service.statusmanager.IMogoStatusManager
class ServiceApi {
companion object {
fun mapService(): IMogoMapService?{
return MogoApisHandler.getInstance().apis.mapServiceApi
}
fun statusManager(): IMogoStatusManager? {
return MogoApisHandler.getInstance().apis.statusManagerApi
}
fun locationClient(): IMogoLocationClient? {
return MogoApisHandler.getInstance().apis.mapServiceApi.getSingletonLocationClient(AbsMogoApplication.getApp().applicationContext)
}
}
}

View File

@@ -0,0 +1,244 @@
package com.mogo.chat.service
import com.google.gson.Gson
import com.mogo.chat.base.BaseController
import com.mogo.chat.base.BaseResponse
import com.mogo.chat.callcenter.CallTypeManager.Companion.callTypeManager
import com.mogo.chat.callcenter.exchangeToCallType
import com.mogo.chat.callcenter.isCalling
import com.mogo.chat.callcenter.isReadyCalling
import com.mogo.chat.common.gme.GMEApi
import com.mogo.chat.constant.*
import com.mogo.chat.model.bean.FocusStatus
import com.mogo.chat.model.bean.OnLineStatus
import com.mogo.chat.model.bean.UserInfoBySns
import com.mogo.chat.model.bean.onLine
import com.mogo.chat.model.control.ChatController
import com.mogo.chat.model.control.MatchController
import com.mogo.chat.model.control.VehicleTeamController
import com.mogo.chat.net.request
import com.mogo.chat.service.InvokeDataProxy.Companion.covertMapToSns
import com.mogo.chat.util.UserInfoHelper.currentCallType
import com.mogo.chat.util.UserInfoHelper.tmpSenderInfo
import com.mogo.chat.util.log
import com.mogo.chat.util.sp.setCarOnLineStatus
import com.mogo.chat.util.trackHangUp
import com.mogo.cloud.passport.MoGoAiCloudClientConfig
object ChatServiceHandler : BaseController() {
fun call(
snReceiver: String,
onSuccess: (() -> Unit),
onError: ((Exception) -> Unit)
) {
queryUserInfo(snReceiver, {
val map = Gson().fromJson<Map<String, String>>(it, Map::class.java)
val sns = covertMapToSns(map)
tmpSenderInfo = sns
}, {
log(TAG, "queryUserInfo is null ,reason : ${it.message}")
})
getUserInfo({
realCall(snReceiver, onSuccess, onError)
}, { exception ->
onError.invoke(exception)
})
}
private fun realCall(
snReceiver: String,
onSuccess: (() -> Unit),
onError: ((Exception) -> Unit)
) {
ChatController.call(snReceiver, 0, {
onSuccess.invoke()
}, { exception ->
onError.invoke(exception)
})
}
fun hangUp(sn: String?, onSuccess: () -> Unit, onError: (Exception) -> Unit, roomId: Int = -1) {
log(TAG, "hangUp sn : $sn , tmpSenderInfo : ${tmpSenderInfo.sn}")
realHangUp(onSuccess, onError, roomId)
}
fun refuseCall(sn: String?, onSuccess: () -> Unit, onError: (Exception) -> Unit, roomId: Int = -1) {
var hangUpSn: String? = sn
if (hangUpSn.isNullOrBlank()) {
if (tmpSenderInfo.sn.isBlank()) {
return
}
hangUpSn = tmpSenderInfo.sn
}
realRefuse(hangUpSn, onSuccess, onError, roomId)
}
private fun realHangUp(onSuccess: () -> Unit, onError: (Exception) -> Unit, roomId: Int = -1) {
log(TAG, "realHangUp currentCallType : $currentCallType ")
when {
currentCallType == CALL_TYPE_VEHICLE_TEAM -> {
trackHangUp(CALL_TYPE_VEHICLE_TEAM, "2")
log(TAG, "hangUp VehicleTeam")
VehicleTeamController.requestVehicleTeamConnectStatus(MoGoAiCloudClientConfig.getInstance().sn, PUSH_MSG_HANG_UP, CALL_TYPE_VEHICLE_TEAM, onSuccess, onError)
}
GMEApi.isRoomEntered() || callTypeManager.callStatus.isCalling() -> {
trackHangUp(callTypeManager.callStatus.exchangeToCallType(), "2")
log(TAG, "hangUp Call")
ChatController.requestConnectStatus(tmpSenderInfo.sn, PUSH_MSG_HANG_UP, onSuccess, onError, roomId)
}
else -> {
log(TAG, "realHangUp " +
"currentCallStatus : ${callTypeManager.callStatus} + " +
"currentCallType : $currentCallType")
}
}
}
private fun realRefuse(sn: String, onSuccess: () -> Unit, onError: (Exception) -> Unit, roomId: Int = -1) {
log(TAG, "realRefuse currentCallType : $currentCallType ")
when {
currentCallType == CALL_TYPE_VEHICLE_TEAM -> {
log(TAG, "refuse VehicleTeam")
VehicleTeamController.requestVehicleTeamConnectStatus(sn, PUSH_MSG_DENY_ENTER, CALL_TYPE_VEHICLE_TEAM, onSuccess, onError)
}
callTypeManager.callStatus.isReadyCalling() -> {
log(TAG, "refuse call")
ChatController.requestConnectStatus(sn, PUSH_MSG_DENY_ENTER, onSuccess, onError, roomId)
}
else -> {
log(TAG, "realRefuse " +
"currentCallStatus : ${callTypeManager.callStatus} + " +
"currentCallType : $currentCallType")
}
}
}
fun answer(
sn: String,
roomId: Int,
onSuccess: (() -> Unit)? = null,
onError: ((Exception) -> Unit)? = null
) {
log(TAG, "answer currentCallType ==== $currentCallType")
getUserInfo({
when (currentCallType) {
CALL_TYPE_VEHICLE_TEAM -> {
VehicleTeamController.requestVehicleTeamConnectStatus(sn, PUSH_MSG_AGREE_ENTER, CALL_TYPE_VEHICLE_TEAM, {
onSuccess?.invoke()
}, onError)
}
else -> {
ChatController.requestConnectStatus(sn, PUSH_MSG_AGREE_ENTER, {
ChatController.enterRoom(roomId)
onSuccess?.invoke()
}, onError, roomId)
}
}
}, {
onError?.invoke(it)
})
}
fun startMatch(
flag: String,
onSuccess: () -> Unit,
onError: (Exception) -> Unit,
timeOut: () -> Unit
) {
getUserInfo({
MatchController.startMatch(flag, onSuccess, onError, timeOut)
}, { exception ->
onError.invoke(exception)
})
}
fun cancelMatch(onSuccess: () -> Unit) {
MatchController.cancelMatch(onSuccess)
}
fun inviteJoinVehicleTeam(invitedSn: String, onSuccess: () -> Unit, onError: (Exception) -> Unit) {
VehicleTeamController.inviteJoinVehicleTeam(invitedSn, onSuccess, onError)
}
fun isFriend(snReceiver: String, onSuccess: (Boolean) -> Unit, onError: (Exception) -> Unit) {
request<BaseResponse<FocusStatus>> {
loader {
chatServiceModel.requestFocusStatus(snReceiver)
}
onSuccess {
onSuccess.invoke(it.result.isFocus == HAS_FOCUS)
}
onError {
onError.invoke(it)
}
}
}
fun addFriend(snReceiver: String, onSuccess: () -> Unit, onError: (Exception) -> Unit) {
request<BaseResponse<Any>> {
loader {
chatServiceModel.dealFocus(snReceiver)
}
onSuccess {
onSuccess.invoke()
}
onError {
onError.invoke(it)
}
}
}
fun invisibleUser(status: Int, onSuccess: () -> Unit, onError: (Exception) -> Unit) {
request<BaseResponse<Any>> {
loader {
chatServiceModel.switchCarStatus(status)
}
onSuccess {
setCarOnLineStatus(status == ONLINE_STATUS)
onSuccess.invoke()
}
onError {
onError.invoke(it)
}
}
}
fun isOnLine(sn: String, onSuccess: (Boolean) -> Unit, onError: (Exception) -> Unit) {
request<BaseResponse<OnLineStatus>> {
loader {
chatServiceModel.isOnLine(sn)
}
onSuccess {
val onLineStatus = it.result
log(TAG, "onLineStatus : $onLineStatus")
onSuccess.invoke(onLineStatus.onLine())
}
onError {
onError.invoke(it)
}
}
}
fun queryUserInfo(
sn: String,
onSuccess: (userInfo: String) -> Unit,
onError: (Exception) -> Unit
) {
request<BaseResponse<UserInfoBySns>> {
loader {
chatServiceModel.queryUserInfo(sn)
}
onSuccess {
val userInfoBySns = it.result
val info = userInfoBySns.info[0]
val map = InvokeDataProxy.convertUserInfoToInvokeMap(info)
val userData = Gson().toJson(map)
onSuccess.invoke(userData)
}
onError {
onError.invoke(it)
}
}
}
}

View File

@@ -0,0 +1,200 @@
package com.mogo.chat.service
import android.app.Service
import android.content.Context
import android.content.Intent
import android.os.IBinder
import com.google.gson.Gson
import com.mogo.chat.callcenter.CallController.Companion.callController
import com.mogo.chat.callcenter.CallTypeManager.Companion.callTypeManager
import com.mogo.chat.callcenter.ConvertFactory.Companion.factory
import com.mogo.chat.callcenter.ICallMessage
import com.mogo.chat.callcenter.ICallTypeChangedListener
import com.mogo.chat.callcenter.IMType
import com.mogo.chat.callcenter.SocketClientFactory
import com.mogo.chat.constant.HttpConstants
import com.mogo.chat.constant.SOCKET_HAND_SHAKE
import com.mogo.chat.constant.SOCKET_HEART_BEAT
import com.mogo.chat.constant.TAG
import com.mogo.chat.model.bean.Message
import com.mogo.chat.model.bean.Sns
import com.mogo.chat.model.bean.SocketMsg
import com.mogo.chat.model.bean.toSns
import com.mogo.chat.model.control.ChatController
import com.mogo.chat.util.UserInfoHelper.currentRoomId
import com.mogo.chat.util.audio.AudioFocusUtil
import com.mogo.chat.util.log
import com.mogo.chat.util.sp.getRoomId
import com.mogo.chat.util.trackAppEnter
import com.mogo.chat.window.CallingWindowManager
import com.mogo.chat.window.IWindowCallActionListener
import com.mogo.cloud.passport.MoGoAiCloudClientConfig
import com.mogo.websocket.ISocketMsgCallBack
import com.mogo.websocket.ISocketMsgSetting
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
class IMService : Service(), ICallMessage, IWindowCallActionListener {
companion object {
fun launchService(context: Context) {
context.startService(Intent(context, IMService::class.java))
}
}
private lateinit var callingWindowManager: CallingWindowManager
override fun onBind(intent: Intent): IBinder? {
return null
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
return START_STICKY
}
override fun onCreate() {
super.onCreate()
log(TAG, "onCreate ---> ")
init()
}
private fun init() {
AudioFocusUtil.getInstance().init(this)
//开启长连服务
SocketClientFactory.socketClient.initSocketServer(HttpConstants.getSocketServer())
SocketClientFactory.socketClient.getMessageSettings(socketMsgSetting)
SocketClientFactory.socketClient.addISocketMsgCallBack(socketMsgCallBack)
SocketClientFactory.socketClient.startConnect()
//更新用户信息
ChatController.getUserInfo()
callController.addCallBack(this.javaClass.simpleName, this)
callingWindowManager = CallingWindowManager(this, this)
// 注册状态回调,用来处理音频焦点
callTypeManager.addCallTypeChangedListener(object : ICallTypeChangedListener {
override fun onCallTypeChanged(callType: IMType) {
log(TAG, "收到状态变化,处理音频焦点")
when (callType) {
IMType.INIT_CALL -> {
log(TAG, "回归初始化状态尝试释放焦点")
AudioFocusUtil.getInstance().releaseDuck()
}
IMType.READY_TO_CALL_RECEIVER, IMType.READY_TO_CALL_SENDER, IMType.READY_TO_MATCH_RECEIVER, IMType.MATCHING -> {
log(TAG, "准备接打电话,开始匹配需要抢占焦点")
AudioFocusUtil.getInstance().requireDuck()
}
else -> log(TAG, "不需要处理音频焦点")
}
}
})
}
private val socketMsgSetting: ISocketMsgSetting = object : ISocketMsgSetting {
override fun getHandShakeMsg(): String {
log(TAG, "getHandShakeMsg")
val socketMsg = SocketMsg(SOCKET_HAND_SHAKE, MoGoAiCloudClientConfig.getInstance().sn)
return Gson().toJson(socketMsg)
}
override fun getHeartBeatMsg(): String {
log(TAG, "getHeartBeatMsg")
val socketMsg = SocketMsg(SOCKET_HEART_BEAT, MoGoAiCloudClientConfig.getInstance().sn, getRoomId())
return Gson().toJson(socketMsg)
}
}
private val socketMsgCallBack: ISocketMsgCallBack = object : ISocketMsgCallBack {
override fun onConnectOpen() {
log(TAG, "onConnectOpen ---> ")
}
override fun handleError(e: Exception) {
log(TAG, "handleError ---> msg: ${e.message}")
SocketClientFactory.socketClient.stopHeartBeat()
}
override fun onConnectClose() {
log(TAG, "onConnectClose ---> stop web socket thread ,and ready to reconnect")
SocketClientFactory.socketClient.stop()
log(TAG, "onConnectClose ---> stop Heart Beat")
SocketClientFactory.socketClient.stopHeartBeat()
log(TAG, "ready to reconnect")
SocketClientFactory.socketClient.reConnect()
}
override fun handleMessage(message: String) {
GlobalScope.launch(Dispatchers.Main) {
log(TAG, "handleMessage ---> $message")
factory.parseLongConnMsgToCallProxy(message)
}
}
}
override fun initStatus() {
super.initStatus()
callingWindowManager.hideIncomingCall()
}
/**
* 浮窗点击接听电话
*/
override fun windowAnswerCall(userReceiver: Sns) {
trackAppEnter("7")
ChatServiceHandler.answer(userReceiver.sn, currentRoomId, {
log(TAG, "接听电话成功 ---> ")
callingWindowManager.hideIncomingCall()
ChatController.enterRoom(currentRoomId)
callController.callingReceiver()
}, {
log(TAG, "IMService answer call is error ,please try again")
})
}
/**
* 浮窗显示拒绝接听电话
*/
override fun windowRefuseCall(sns: Sns) {
ChatServiceHandler.refuseCall(sns.sn, {}, {})
callingWindowManager.hideIncomingCall()
}
/**
* 收到来电消息
* 1.如果当前已经进房,则拒绝
* 2.如果当前已经收到邀请信息,则拒绝
* 3.如果当前正在打电话中,则拒绝
*/
override fun receiverCalling(message: Message) {
super.receiverCalling(message)
if (callingWindowManager.isWindowShow) {
log(TAG, "已在通话进程中,准备拒绝他 ---> ")
ChatServiceHandler.refuseCall(message.snSender, {}, {}, message.roomId)
} else {
log(TAG, "有人打来电话,准备显示 ---> $currentRoomId")
callingWindowManager.showIncomingCall(message.toSns())
}
}
override fun refuseMatchToShowCalling(message: Message) {
super.refuseMatchToShowCalling(message)
callingWindowManager.showIncomingCall(message.toSns())
}
override fun onTrimMemory(level: Int) {
super.onTrimMemory(level)
log(TAG, "onTrimMemory ---> ")
}
override fun onDestroy() {
super.onDestroy()
log(TAG, "onDestroy ---> ")
ChatController.updateExitRoomStatus()
SocketClientFactory.socketClient.disConnect()
AudioFocusUtil.getInstance().unInit(applicationContext)
}
}

View File

@@ -0,0 +1,53 @@
package com.mogo.chat.service
import com.mogo.chat.model.bean.Info
import com.mogo.chat.model.bean.Sns
class InvokeDataProxy {
companion object {
fun convertSnsToInvokeMap(sns: Sns): Map<String, String> {
val map = hashMapOf<String, String>()
map["sn"] = sns.sn
map["userName"] = sns.nickName ?: ""
map["userHead"] = sns.headImgUrl ?: ""
map["carTypeName"] = sns.carInfo ?: ""
map["gender"] = sns.cardIdSex ?: ""
map["location"] = sns.cityName ?: ""
map["age"] = sns.cardIdAge ?: ""
map["lat"] = sns.lat.toString()
map["lon"] = sns.lon.toString()
return map
}
fun covertMapToSns(map: Map<String, String>): Sns {
val sns = Sns()
sns.sn = map["sn"].toString()
sns.nickName = map["userName"] ?: ""
sns.headImgUrl = map["userHead"] ?: ""
sns.carInfo = map["carTypeName"] ?: ""
sns.cardIdSex = map["gender"] ?: ""
sns.cityName = map["location"] ?: ""
sns.cardIdAge = map["age"] ?: ""
sns.lat = map["lat"]?.toDouble() ?: 0.0
sns.lon = map["lon"]?.toDouble() ?: 0.0
return sns
}
fun convertUserInfoToInvokeMap(info: Info): Map<String, String> {
val map = hashMapOf<String, String>()
map["sn"] = info.carAndUserInfoRedisVo.sn ?: ""
map["userName"] = info.carAndUserInfoRedisVo.userNickName ?: ""
map["userHead"] = info.carAndUserInfoRedisVo.headImgUrl ?: ""
map["carTypeName"] = info.carAndUserInfoRedisVo.lastBrandName ?: ""
map["gender"] = info.carAndUserInfoRedisVo.cardIdSex ?: ""
map["location"] = info.carAndUserInfoRedisVo.lastActiveCity ?: ""
map["age"] = info.carAndUserInfoRedisVo.cardIdAge ?: ""
map["lat"] = info.realTimeLocationVo.lat.toString()
map["lon"] = info.realTimeLocationVo.lon.toString()
return map
}
}
}

View File

@@ -0,0 +1,115 @@
package com.mogo.chat.util
import android.content.Context
import com.alibaba.android.arouter.launcher.ARouter
import com.mogo.chat.R
import com.mogo.chat.aspect.DebugLog
import com.mogo.chat.constant.CALL_TYPE_MATCHING
import com.mogo.chat.constant.CALL_TYPE_VIDEO
import com.mogo.chat.constant.CALL_TYPE_VOICE
import com.mogo.chat.util.sp.getRoomId
import com.mogo.chat.util.sp.getTalkTime
import com.mogo.chat.util.sp.recordCallTime
import com.mogo.commons.AbsMogoApplication
import com.mogo.eagle.core.data.constants.MogoServicePaths
import com.mogo.service.IMogoServiceApis
import com.mogo.service.analytics.IMogoAnalytics
const val TRACK_APP_ENTER = "appenterfront"
const val TRACK_PHONE_CALL_CLICK = "carchat_carphone_call_click"
const val TRACK_VIDEO_CALL_CLICK = "carchat_carphone_video_click"
const val TRACK_CANCEL_CALL_CLICK = "carchat_cancel_call_click"
const val TRACK_MATCH_SUCCESS = "carchat_carphonecall_match_success"
const val TRACK_MATCH_FAIL = "carchat_carphonecall_match_fail"
const val TRACK_MATCH_CANCEL = "carchat_carphonecall_match_cancel_click"
private var trackRouter: IMogoAnalytics? = null
//自定义埋点
@DebugLog
fun trackNormalEvent(
event: String,
data: MutableMap<String, Any>?) {
track(event,data)
}
private fun track(eventType: String, data: MutableMap<String, Any>? = hashMapOf()) {
if (trackRouter == null) {
val aRouter = ARouter.getInstance().build(MogoServicePaths.PATH_SERVICE_APIS).navigation()
if (aRouter is IMogoServiceApis) {
trackRouter = aRouter.analyticsApi
}
}
trackRouter!!.track(eventType, data)
}
@DebugLog
fun trackAppEnter(type: String, context: Context = AbsMogoApplication.getApp().applicationContext) {
trackNormalEvent(
TRACK_APP_ENTER,
mutableMapOf(
"from" to type,
"appname" to context.getString(R.string.app_name),
"appversion" to context.packageManager.getPackageInfo(
context.packageName,
0
).versionName
)
)
}
//todo
@DebugLog
fun trackCall(callType: Int, type: Int) {
//记录开始语音、直播时间
recordCallTime()
when (callType) {
CALL_TYPE_VOICE -> {
trackNormalEvent(
TRACK_PHONE_CALL_CLICK,
mutableMapOf("type" to type, "type_of_call" to "direct")
)
}
CALL_TYPE_MATCHING -> {
trackNormalEvent(
TRACK_PHONE_CALL_CLICK,
mutableMapOf("type" to type, "type_of_call" to "match")
)
}
CALL_TYPE_VIDEO -> {
trackNormalEvent(TRACK_VIDEO_CALL_CLICK, mutableMapOf("type" to type))
}
else -> {
trackNormalEvent(TRACK_PHONE_CALL_CLICK, mutableMapOf("type" to type))
}
}
}
//todo
@DebugLog
fun trackHangUp(callType: Int, source: String = "1") {
when (callType) {
CALL_TYPE_VOICE -> {
trackNormalEvent(
TRACK_CANCEL_CALL_CLICK,
mutableMapOf(
"roomId" to getRoomId(),
"carphone_call_duration" to getTalkTime(),
"type_of_call" to "direct",
"source" to source
)
)
}
CALL_TYPE_MATCHING -> {
trackNormalEvent(
TRACK_CANCEL_CALL_CLICK,
mutableMapOf(
"roomId" to getRoomId(),
"carphone_call_duration" to getTalkTime(),
"type_of_call" to "match",
"source" to source
)
)
}
}
}

View File

@@ -0,0 +1,61 @@
package com.mogo.chat.util
import com.mogo.chat.constant.TAG
import kotlinx.coroutines.*
import java.util.*
import kotlin.concurrent.timerTask
class CallTimer private constructor() {
companion object {
private const val TIMER_RATE = 1000.toLong()//每隔1秒刷新一次时间
val callTimer by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
CallTimer()
}
}
private var mStartTime: Long = 0
private var start: Boolean = false
private var timer: Timer? = null
private var job: Job? = null
private var timerRate: ((Long) -> Unit)? = null //暂时仅有一处使用时间记录,后续如果有多处再使用集合
private var onStop: (() -> Unit)? = null //暂时仅有一处使用时间记录,后续如果有多处再使用集合
fun registerTimerRate(timerRate: ((Long) -> Unit), onStop:() -> Unit) {
this.timerRate = timerRate
this.onStop = onStop
}
fun start() {
if (start) {
log(TAG, "start : $start")
return
}
start = true
job = GlobalScope.async(Dispatchers.Default) {
if (timer == null) {
timer = Timer()
}
mStartTime = System.currentTimeMillis()
timer?.schedule(timerTask {
val timeLong = System.currentTimeMillis() - mStartTime
log(TAG, "times : $timeLong")
timerRate?.invoke(timeLong)
}, 0, TIMER_RATE)
}
}
fun stop() {
if (start) {
onStop?.invoke()
job?.cancel()
timer?.cancel()
timer = null
mStartTime = 0
start = false
}
}
}

View File

@@ -0,0 +1,36 @@
package com.mogo.chat.util
const val SPACE_TIME = 1500
var lastClickTime = 0L
var lastViewClickTime = 0L
var viewId: Int = 0
fun isDoubleClick(view: Int): Boolean {
val time = System.currentTimeMillis()
val timeD = time - lastViewClickTime
if (timeD < SPACE_TIME && viewId == view) {
return true
}
lastViewClickTime = time
viewId = view
return false
}
fun isDoubleClick(): Boolean {
if (System.currentTimeMillis() - lastClickTime < SPACE_TIME) {
return true
}
lastClickTime = System.currentTimeMillis()
return false
}
fun isDoubleClickTime(view: Int, spaceTime: Int): Boolean {
val time = System.currentTimeMillis()
val timeD = time - lastClickTime
if (timeD < spaceTime && viewId == view) {
return true
}
lastClickTime = time
viewId = view
return false
}

View File

@@ -0,0 +1,10 @@
@file:JvmName("LogUtil")
package com.mogo.chat.util
import com.mogo.chat.aspect.DebugLog
@DebugLog
fun log(tag: String, msg: String) {
}

View File

@@ -0,0 +1,57 @@
package com.mogo.chat.util
import android.content.Context
import android.media.AudioAttributes
import android.media.AudioManager
import android.media.MediaPlayer
import android.os.Build
import com.mogo.chat.constant.TAG
object MediaController {
private var mp: MediaPlayer? = null
fun startPlay(context: Context, audioSources: Int, isLoop: Boolean = false, channel:Int = AudioManager.STREAM_MUSIC) {
if (mp == null) {
mp = MediaPlayer()
}
resetStatus()
val file = context.resources.openRawResourceFd(audioSources)
mp?.apply {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
setAudioStreamType(channel)
} else {
setAudioAttributes(AudioAttributes.Builder().setLegacyStreamType(channel).build())
}
setDataSource(file.fileDescriptor, file.startOffset, file.length)
file.close()
isLooping = isLoop
prepareAsync()
setOnPreparedListener { player ->
log(TAG,"real play 准备播放音频====")
player.start()
}
setOnCompletionListener { player ->
log(TAG,"播放完成====")
player.reset()
}
}
}
fun release() {
log(TAG,"release 释放音频播放====")
if (mp != null) {
resetStatus()
mp!!.release()
mp = null
}
}
private fun resetStatus() {
mp?.apply {
if (isPlaying) {
stop()
}
reset()
}
}
}

View File

@@ -0,0 +1,43 @@
package com.mogo.chat.util
import com.mogo.chat.constant.CALL_TYPE_DEFAULT
import com.mogo.chat.constant.TAG
import com.mogo.chat.model.bean.Sns
object UserInfoHelper {
var userInfo: Sns = Sns()
set(value) {
log(TAG, "个人信息更新:$value")
field = value
}
/**
* 是否是主动匹配
*/
var isInitiativeMatch = false
/**
* 仅为首次进入车聊聊,同步对方信息而创建的临时对象
*/
var tmpSenderInfo: Sns = Sns()
set(value) {
log(TAG, "UserInfoHelper Sender Info update ---> $value")
field = value
}
var currentRoomId = 0
set(value) {
log(TAG, "UserInfoHelper setRoomId ---> $value")
field = value
}
var currentCallType : Int = CALL_TYPE_DEFAULT
set(value) {
log(TAG, "UserInfoHelper setCallType ---> $value")
field = value
}
}

View File

@@ -0,0 +1,20 @@
package com.mogo.chat.util.audio
import com.mogo.chat.util.audio.ChangAnAudioFocusImpl.Companion.changAnAudioFocusImpl
import com.mogo.chat.util.audio.MogoAudioFocusImpl.Companion.mogoAudioFocusImpl
import com.mogo.commons.debug.DebugConfig.*
class AudioFocusImpl {
companion object {
fun getInstance(): IAudioFocus {
return if (getCarMachineType() == CAR_MACHINE_TYPE_SELF_INNOVATE
|| getCarMachineType() == CAR_MACHINE_TYPE_BYD) {
mogoAudioFocusImpl
} else {
changAnAudioFocusImpl
}
}
}
}

View File

@@ -0,0 +1,106 @@
package com.mogo.chat.util.audio;
import android.content.Context;
import android.media.AudioManager;
import com.mogo.chat.callcenter.CallTypeManager;
import com.mogo.chat.callcenter.IMType;
import static com.mogo.chat.util.LogUtil.log;
public class AudioFocusUtil {
private static final String TAG = "AudioFocusUtil";
private MyAudioFocusChangeListener audioListener;
private AudioManager audioManager;
private AudioFocusUtil() {
}
public static class Holder {
private Holder() {
}
private static final AudioFocusUtil instance = new AudioFocusUtil();
}
public static AudioFocusUtil getInstance() {
return Holder.instance;
}
public void init(Context context) {
audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
audioListener = new MyAudioFocusChangeListener();
AudioFocusImpl.Companion.getInstance().init(context);
}
public boolean sendGetFocusIntent(Context context) {
return AudioFocusImpl.Companion.getInstance().sendGetFocusIntent(context);
}
public void sendReleaseFocusIntent(Context context) {
AudioFocusImpl.Companion.getInstance().sendReleaseFocusIntent(context);
}
public boolean findMicFocus(Context context) {
return AudioFocusImpl.Companion.getInstance().findMicFocus(context);
}
private int getCurrentPriority(Context context) {
return AudioFocusImpl.Companion.getInstance().getCurrentPriority(context);
}
public void unInit(Context context) {
AudioFocusImpl.Companion.getInstance().unInit(context);
}
private volatile Boolean hasAudioFocus = false;
public void requireDuck() {
log(TAG, "requireDuck===" + hasAudioFocus);
if (!hasAudioFocus) {
hasAudioFocus = true;
try {
audioManager.requestAudioFocus(audioListener, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
} catch (Exception e) {
e.printStackTrace();
}
}
}
public void releaseDuck() {
log(TAG, "releaseDuck===" + hasAudioFocus);
if (hasAudioFocus) {
hasAudioFocus = false;
try {
audioManager.abandonAudioFocus(audioListener);
} catch (Exception e) {
e.printStackTrace();
}
}
}
private class MyAudioFocusChangeListener implements AudioManager.OnAudioFocusChangeListener {
@Override
public void onAudioFocusChange(int focusChange) {
log(TAG, "onAudioFocusChange: " + focusChange);
switch (focusChange) {
case AudioManager.AUDIOFOCUS_GAIN:
hasAudioFocus = true;
if (CallTypeManager.Companion.getCallTypeManager().getCallStatus() == IMType.INIT_CALL) {
AudioFocusUtil.getInstance().releaseDuck();
}
break;
case AudioManager.AUDIOFOCUS_LOSS:
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
hasAudioFocus = false;
break;
}
}
}
}

View File

@@ -0,0 +1,41 @@
package com.mogo.chat.util.audio
import android.content.Context
import com.fce.micro.MicManager
import com.fce.micro.callback.MicroDef
import com.mogo.chat.constant.TAG
import com.mogo.chat.util.log
class ChangAnAudioFocusImpl private constructor() : IAudioFocus {
companion object {
val changAnAudioFocusImpl by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
ChangAnAudioFocusImpl()
}
}
override fun init(context:Context) {
MicManager.getInstance().initMicParam(context, MicroDef.MIC_DEBUNK)
MicManager.getInstance().setMicReleaseBack {
//收到该回调表示有更高优先级应用使用mic停止录音释放mic
sendReleaseFocusIntent(context)
}
MicManager.getInstance().setMicObtainBack {
//APP收到此回调时开始录音
log(TAG,"ChangAnAudioFocusImpl MicObtainBack : 可以开始录音了")
}
}
override fun sendGetFocusIntent(context: Context) :Boolean{
return MicManager.getInstance().requestMicSrc()
}
override fun sendReleaseFocusIntent(context: Context) {
MicManager.getInstance().releaseMicSrc()
}
override fun unInit(context: Context) {
MicManager.getInstance().releaseMicSrc()
}
}

View File

@@ -0,0 +1,35 @@
package com.mogo.chat.util.audio
import android.content.Context
interface IAudioFocus {
fun init(context: Context)
/**
* 查询麦克风焦点
* 麦克风优先级
* true,代表当前麦克风焦点在你这
*/
fun findMicFocus(context: Context): Boolean {
return false
}
/**
* 查询当前的麦克风使用的优先级
*
* @return 麦克风优先级
*/
fun getCurrentPriority(context: Context): Int {
return 0
}
/**
* 申请麦克风焦点
*/
fun sendGetFocusIntent(context: Context):Boolean
fun sendReleaseFocusIntent(context: Context)
fun unInit(context: Context)
}

View File

@@ -0,0 +1,93 @@
package com.mogo.chat.util.audio
import android.content.Context
import com.mogo.chat.constant.TAG
import com.mogo.chat.util.log
import com.zhidao.auto.micstrategy.MicStrategyManager
import com.zhidao.auto.micstrategy.MicStrategyManager.MicFocusListener
import com.zhidao.auto.micstrategy.MicStrategyManagerImpl
class MogoAudioFocusImpl private constructor() : IAudioFocus {
private var listener: MyMicFocusListener? = null
companion object {
val mogoAudioFocusImpl by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
MogoAudioFocusImpl()
}
}
override fun init(context: Context) {
listener = MyMicFocusListener()
try {
if (listener == null) listener = MyMicFocusListener()
MicStrategyManagerImpl.getInstance(context).addMicFocusListener(listener)
} catch (e: Exception) {
e.printStackTrace()
}
}
override fun findMicFocus(context: Context): Boolean {
super.findMicFocus(context)
var can = false
try {
can = MicStrategyManagerImpl.getInstance(context).findMicFocus(MicStrategyManager.VOIP_PRIORITY)
} catch (e: java.lang.Exception) {
e.printStackTrace()
}
return can
}
override fun getCurrentPriority(context: Context): Int {
return try {
MicStrategyManagerImpl.getInstance(context).currentPriority
} catch (e: java.lang.Exception) {
e.printStackTrace()
0
}
}
override fun sendGetFocusIntent(context: Context):Boolean {
return try {
if (listener == null) listener = MyMicFocusListener()
MicStrategyManagerImpl.getInstance(context).requestMicFocus(MicStrategyManager.VOIP_PRIORITY, listener)
log(TAG, "sendGetFocusIntent: ")
true
} catch (e: java.lang.Exception) {
e.printStackTrace()
false
}
}
override fun sendReleaseFocusIntent(context: Context) {
try {
if (listener == null) listener = MyMicFocusListener()
MicStrategyManagerImpl.getInstance(context).abandonMicFocus()
log(TAG, "sendReleaseFocusIntent: ")
} catch (e: java.lang.Exception) {
e.printStackTrace()
}
}
override fun unInit(context: Context) {
try {
if (listener != null) MicStrategyManagerImpl.getInstance(context).removeMicFocusListener(listener)
} catch (e: java.lang.Exception) {
e.printStackTrace()
}
}
class MyMicFocusListener : MicFocusListener {
/**
* 麦克风焦点状态
*
* @param i
*/
override fun onMicFocuschanged(i: Int) {
//do nothing
}
}
}

View File

@@ -0,0 +1,19 @@
package com.mogo.chat.util.sp
import android.content.Context
import android.content.SharedPreferences
import androidx.core.content.edit
import com.mogo.commons.AbsMogoApplication
fun getSP(fileName: String): SharedPreferences {
return AbsMogoApplication.getApp().applicationContext.getSharedPreferences(fileName, Context.MODE_PRIVATE)
}
inline fun getCommitSP(fileName: String, action: SharedPreferences.Editor.() -> Unit) {
getSP(fileName).edit(commit = true, action = action)
}
inline fun getApplySp(fileName: String, action: SharedPreferences.Editor.() -> Unit) {
getSP(fileName).edit(commit = false, action = action)
}

View File

@@ -0,0 +1,187 @@
@file:JvmName("SharedPreferenceUtil")
package com.mogo.chat.util.sp
import com.mogo.chat.aspect.DebugLog
const val TEMPORARY_FILE_NAME = "temporary_im_data"
const val CONFIG_FILE_NAME = "configs_im_data"
const val FILE_NAME = "settings_im_data"
const val PARAM_ROOM_ID = "PARAM_ROOM_ID"
const val PARAM_FRIEND_LIST_CALL = "PARAM_FRIEND_LIST_CALL"
const val PARAM_NEW_FOCUS = "PARAM_NEW_FOCUS"
const val PARAM_VOICE_TYPE = "PARAM_VOICE_TYPE"
const val PARAM_CALL_TIME = "PARAM_CALL_TIME"
const val PARAM_INIT_TIP = "PARAM_INIT_TIP"
const val PARAM_CAR_ONLINE_STATUS = "PARAM_CAR_ONLINE_STATUS"
const val PARAM_GUIDE_SHOW_STATUS = "PARAM_GUIDE_SHOW_STATUS"
const val PARAM_CONFIG_EXPIRY_TIME = "PARAM_CONFIG_EXPIRY_TIME"
const val PARAM_CONFIG_COUNT_DOWN_TIME = "PARAM_CONFIG_COUNT_DOWN_TIME"
const val PARAM_CONFIG_VOICE_CONTENT = "PARAM_CONFIG_VOICE_CONTENT"
const val PARAM_CONFIG_IMAGE_IS_SAVED = "PARAM_CONFIG_IMAGE_IS_SAVED"
const val PARAM_CONFIG_TOPIC_GUIDE = "PARAM_CONFIG_TOPIC_GUIDE"
const val PARAM_CONFIG_FOCUS_NOTICE_TIMES = "PARAM_CONFIG_FOCUS_NOTICE_TIMES"
const val PARAM_CONFIG_OWN_NICK_NAME = "PARAM_CONFIG_OWN_NICK_NAME"
const val PARAM_CONFIG_OWN_HEAD_IMG = "PARAM_CONFIG_OWN_HEAD_IMG"
fun saveRoomId(roomId: Int) {
getCommitSP(TEMPORARY_FILE_NAME) {
putInt(PARAM_ROOM_ID, roomId)
}
}
fun hasRoomId(): Boolean {
return getSP(TEMPORARY_FILE_NAME).getInt(PARAM_ROOM_ID, 0) != 0
}
fun getRoomId(): Int {
return getSP(TEMPORARY_FILE_NAME).getInt(PARAM_ROOM_ID, 0)
}
fun setCallFromFriendList(call: Boolean) {
getCommitSP(TEMPORARY_FILE_NAME) {
putBoolean(PARAM_FRIEND_LIST_CALL, call)
}
}
fun hasCallFromFriendList(): Boolean {
return getSP(TEMPORARY_FILE_NAME).getBoolean(PARAM_FRIEND_LIST_CALL, false)
}
fun hasFocus(): Boolean {
return getSP(TEMPORARY_FILE_NAME).getBoolean(PARAM_NEW_FOCUS, false)
}
fun newFocus(focus: Boolean) {
getApplySp(TEMPORARY_FILE_NAME) {
putBoolean(PARAM_NEW_FOCUS, focus)
}
}
fun setVoiceType(voiceType: Int) {
getApplySp(FILE_NAME) {
putInt(PARAM_VOICE_TYPE, voiceType)
}
}
fun getVoiceType(): Int {
return getSP(FILE_NAME).getInt(PARAM_VOICE_TYPE, 0)
}
fun setCarOnLineStatus(onLine: Boolean) {
getApplySp(FILE_NAME) {
putBoolean(PARAM_CAR_ONLINE_STATUS, onLine)
}
}
/**
* 本地缓存的在线隐身状态
*/
fun getCarOnLineStatus(): Boolean {
return getSP(FILE_NAME).getBoolean(PARAM_CAR_ONLINE_STATUS, true)
}
fun initTip(initType: Int) {
getApplySp(FILE_NAME) {
putBoolean(PARAM_INIT_TIP + initType, true)
}
}
fun getInitStatus(initType: Int): Boolean {
return getSP(FILE_NAME).getBoolean(PARAM_INIT_TIP + initType, false)
}
fun recordCallTime() {
getApplySp(FILE_NAME) {
putLong(PARAM_CALL_TIME, System.currentTimeMillis())
}
}
fun getTalkTime(): Long {
val startCallTime = getSP(FILE_NAME).getLong(PARAM_CALL_TIME, 0)
return if (startCallTime.toString() == "0") {
0
} else {
System.currentTimeMillis() - startCallTime
}
}
fun guideHasShown() {
getApplySp(FILE_NAME) {
putBoolean(PARAM_GUIDE_SHOW_STATUS, true).apply()
}
}
fun getGuideShowStatus(): Boolean {
return getSP(FILE_NAME).getBoolean(PARAM_GUIDE_SHOW_STATUS, false)
}
/**********************************************配置Data*****************************************************/
fun setExpiryTime(expiryTime: Long) {
getApplySp(CONFIG_FILE_NAME) {
putLong(PARAM_CONFIG_EXPIRY_TIME, expiryTime)
}
}
fun isExpiryTime(requestTime: Long): Boolean {
return getSP(CONFIG_FILE_NAME).getLong(PARAM_CONFIG_EXPIRY_TIME, 0) != requestTime
}
fun setCountDownTime(countDownTime: Int) {
getApplySp(CONFIG_FILE_NAME) {
putInt(PARAM_CONFIG_COUNT_DOWN_TIME, countDownTime)
}
}
fun getCountDownTime(): Int {
return getSP(CONFIG_FILE_NAME).getInt(PARAM_CONFIG_COUNT_DOWN_TIME, 0)
}
fun setConfigVoiceContent(voiceContent: String) {
getApplySp(CONFIG_FILE_NAME) {
putString(PARAM_CONFIG_VOICE_CONTENT, voiceContent)
}
}
fun getConfigVoiceContent(): String {
return getSP(CONFIG_FILE_NAME).getString(PARAM_CONFIG_VOICE_CONTENT, "")!!
}
fun setImageSaveStatus(saveStatus: Boolean) {
getApplySp(CONFIG_FILE_NAME) {
putBoolean(PARAM_CONFIG_IMAGE_IS_SAVED, saveStatus)
}
}
fun getImageSaveStatus(): Boolean {
return getSP(CONFIG_FILE_NAME).getBoolean(PARAM_CONFIG_IMAGE_IS_SAVED, false)
}
fun setFocusNoticeTimes(times: Int) {
getApplySp(CONFIG_FILE_NAME) {
putInt(PARAM_CONFIG_FOCUS_NOTICE_TIMES, times)
}
}
fun getFocusNoticeTimes(): Int {
return getSP(CONFIG_FILE_NAME).getInt(PARAM_CONFIG_FOCUS_NOTICE_TIMES, 0)
}
@DebugLog
fun setTopicGuideContent(content: String) {
getApplySp(CONFIG_FILE_NAME) {
putString(PARAM_CONFIG_TOPIC_GUIDE, content)
}
}
fun getTopicGuideContent(): Array<String> {
val content = getSP(CONFIG_FILE_NAME).getString(PARAM_CONFIG_TOPIC_GUIDE, "")
return if (content.isNullOrEmpty()) emptyArray() else content.split("/").toTypedArray()
}

View File

@@ -0,0 +1,79 @@
package com.mogo.chat.voice
import com.alibaba.android.arouter.launcher.ARouter
import com.mogo.chat.constant.TAG
import com.mogo.chat.util.log
import com.mogo.commons.AbsMogoApplication
import com.mogo.commons.voice.AIAssist
import com.mogo.commons.voice.IMogoVoiceCmdCallBack
import com.mogo.eagle.core.data.constants.MogoServicePaths
import com.mogo.service.IMogoServiceApis
import com.mogo.service.intent.IMogoIntentManager
object IMVoiceClient {
private var intentRegister: IMogoIntentManager? = null
private const val REQUEST_VOICE_CALL = "主人,有新的小伙伴找你聊天啦,你要接听吗?你可以说“接听”开启通话。或者说“挂断”结束通话"
private const val REQUEST_CLOUD_VOICE_CALL = "您有来自管理平台的语音电话,请接听!"
private val requestCallYArray: Array<String> = arrayOf("接听")
private val requestCallNArray: Array<String> = arrayOf("挂断")
const val VOICE_INTENT_ANSWER_CALL = "com.ileja.phone.incoming.accept" //接听
const val VOICE_INTENT_REFUSE_CALL = "com.ileja.phone.incoming.reject" //挂断(包括拨出时挂断和来电挂断)
init {
log(TAG, "init")
val register =
ARouter.getInstance().build(MogoServicePaths.PATH_SERVICE_APIS).navigation()
if (register is IMogoServiceApis) {
intentRegister = register.intentManagerApi
}
}
private var onCmdAgree: ((Boolean) -> Unit)? = null
private var onSpeedFinish: (() -> Unit)? = null
fun registerIntentInComingCall(listener: IVoiceIntentListener) {
intentRegister?.registerIntentListener(VOICE_INTENT_ANSWER_CALL, listener)
intentRegister?.registerIntentListener(VOICE_INTENT_REFUSE_CALL, listener)
}
fun unRegisterIntentInComingCall(listener: IVoiceIntentListener) {
intentRegister?.unregisterIntentListener(VOICE_INTENT_ANSWER_CALL, listener)
intentRegister?.unregisterIntentListener(VOICE_INTENT_REFUSE_CALL, listener)
}
fun speakAndRegisterCall(onCmdAgree: ((Boolean) -> Unit), onSpeedFinish: (() -> Unit)) {
IMVoiceClient.onCmdAgree = onCmdAgree
IMVoiceClient.onSpeedFinish = onSpeedFinish
AIAssist.getInstance(AbsMogoApplication.getApp().applicationContext).speakQAndACmd(REQUEST_CLOUD_VOICE_CALL,
requestCallYArray, requestCallNArray, object : IMogoVoiceCmdCallBack {
override fun onCmdAction(speakText: String?) {
super.onCmdAction(speakText)
log(TAG, "onCmdAction ---> ")
IMVoiceClient.onCmdAgree?.invoke(true)
}
override fun onCmdCancel(speakText: String?) {
super.onCmdCancel(speakText)
log(TAG, "onCmdCancel ---> ")
IMVoiceClient.onCmdAgree?.invoke(false)
}
override fun onSpeakEnd(speakText: String?) {
super.onSpeakEnd(speakText)
log(TAG, "onSpeakEnd ---> ")
IMVoiceClient.onSpeedFinish?.invoke()
}
})
}
fun releaseSpeakAndRegisterCallback() {
onCmdAgree = null
onSpeedFinish = null
}
}

View File

@@ -0,0 +1,28 @@
package com.mogo.chat.voice
import android.content.Intent
import com.mogo.chat.constant.TAG
import com.mogo.chat.util.log
import com.mogo.chat.voice.IMVoiceClient.VOICE_INTENT_ANSWER_CALL
import com.mogo.chat.voice.IMVoiceClient.VOICE_INTENT_REFUSE_CALL
import com.mogo.service.intent.IMogoIntentListener
interface IVoiceIntentListener :IMogoIntentListener{
override fun onIntentReceived(intentStr: String?, intent: Intent?) {
log(TAG, "onIntentCommand intentStr:$intentStr")
when (intentStr) {
VOICE_INTENT_ANSWER_CALL -> {
onVoiceAnswerCall()
}
VOICE_INTENT_REFUSE_CALL -> {
onVoiceRefuseCall()
}
}
}
fun onVoiceAnswerCall()
fun onVoiceRefuseCall()
}

View File

@@ -0,0 +1,252 @@
package com.mogo.chat.window
import android.content.Context
import android.graphics.PixelFormat
import android.media.AudioManager
import android.os.Build
import android.os.Handler
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.view.WindowManager
import android.view.WindowManager.LayoutParams
import android.widget.ImageButton
import android.widget.ImageView
import android.widget.TextView
import com.mogo.chat.R
import com.mogo.chat.constant.TAG
import com.mogo.chat.model.bean.Sns
import com.mogo.chat.util.MediaController
import com.mogo.chat.util.log
import com.mogo.chat.voice.IMVoiceClient
import com.mogo.chat.voice.IVoiceIntentListener
import com.mogo.utils.WindowUtils
import com.mogo.utils.glide.GlideApp
/**
* 来电界面管理
*/
class CallingWindowManager(
val context: Context,
private val callActionListener: IWindowCallActionListener
) : IVoiceIntentListener {
companion object {
const val MSG_DIALING_TIME_OUT = 1001
const val DEFAULT_MAX_DIALING_TIME = 30 * 1000L
}
private var windowManager: WindowManager? = null
var isWindowShow = false
private var isShowBig = false
private var bodyView: View? = null
private var layoutParams: LayoutParams = LayoutParams()
private lateinit var userInfo: Sns
private lateinit var btnScale: ImageButton
private lateinit var ivHeadImg: ImageView
private lateinit var tvNickName: TextView
private lateinit var tvBigAnswer: TextView
private lateinit var tvBigCancel: TextView
private lateinit var ibSmallAnswer: ImageButton
private lateinit var ibSmallCancel: ImageButton
private val handler = Handler(context.mainLooper) {
log(TAG, "times up ,ready to refuse call ")
when (it.what) {
MSG_DIALING_TIME_OUT -> callActionListener.windowRefuseCall(userInfo)
}
true
}
fun showIncomingCall(userInfo: Sns) {
if (!isWindowShow) {
log(TAG, "显示来电浮窗======")
broadCastVoicePrompt()
handler.sendEmptyMessageDelayed(MSG_DIALING_TIME_OUT, DEFAULT_MAX_DIALING_TIME)
isWindowShow = true
this.userInfo = userInfo
windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
layoutParams.type = LayoutParams.TYPE_APPLICATION_OVERLAY
} else {
layoutParams.type = LayoutParams.TYPE_PHONE
}
layoutParams.flags = LayoutParams.FLAG_NOT_FOCUSABLE
layoutParams.gravity = Gravity.START or Gravity.TOP
layoutParams.format = PixelFormat.RGBA_8888
exchangeToBig()
}
}
private fun exchangeToBig() {
windowManager?.let {
if (bodyView != null) {
// 如果bodyView不为空需要先清除掉然后重新添加因为如果不为空则说明是从小界面切换回大界面而不是初始化展示大界面
it.removeView(bodyView)
}
bodyView =
LayoutInflater.from(context).inflate(R.layout.window_incomming_call_big, null)
layoutParams.width = LayoutParams.MATCH_PARENT
layoutParams.height = LayoutParams.MATCH_PARENT
layoutParams.x = 0
layoutParams.y = 0
initBigView()
it.addView(bodyView, layoutParams)
isShowBig = true
}
}
private fun initBigView() {
bodyView?.let {
btnScale = it.findViewById(R.id.btnScale)
ivHeadImg = it.findViewById(R.id.ivHead)
tvNickName = it.findViewById(R.id.tvUserName)
tvBigAnswer = it.findViewById(R.id.tvBigAnswer)
tvBigCancel = it.findViewById(R.id.tvBigCancel)
it.setOnClickListener(null)
GlideApp.with(context).load(userInfo.headImgUrl)
.placeholder(R.mipmap.icon_avator_big).circleCrop()
.into(ivHeadImg)
// tvNickName.text = userInfo.nickName
tvNickName.text = "云平台" //todo 需要产品后面更改逻辑,避免写死造成困扰
btnScale.setOnClickListener {
exchangeToSmall()
}
tvBigAnswer.setOnClickListener {
// 接听电话
clickDialingCall(true)
}
tvBigCancel.setOnClickListener {
// 拒绝接听电话
clickDialingCall(false)
}
}
}
private fun exchangeToSmall() {
windowManager?.let {
it.removeViewImmediate(bodyView)
bodyView =
LayoutInflater.from(context).inflate(R.layout.window_incomming_call_small, null)
val sWidth = WindowUtils.getScreenWidth(context)
val sHeight = WindowUtils.getScreenHeight(context)
val x =
sWidth - context.resources.getDimension(R.dimen.dp_580) - context.resources.getDimension(
R.dimen.dp_310
)
val y =
sHeight - context.resources.getDimension(R.dimen.dp_120) - context.resources.getDimension(
R.dimen.dp_60
) - WindowUtils.getStatusBarHeight(context)
layoutParams.width = context.resources.getDimension(R.dimen.dp_768).toInt()
layoutParams.height = context.resources.getDimension(R.dimen.dp_212).toInt()
layoutParams.gravity = Gravity.START or Gravity.TOP
layoutParams.x = x.toInt()
layoutParams.y = y.toInt()
initSmallView()
it.addView(bodyView, layoutParams)
isShowBig = false
}
}
private fun initSmallView() {
bodyView?.let {
tvNickName = it.findViewById(R.id.tvSmallUserName)
ibSmallAnswer = it.findViewById(R.id.ibSmallAnswer)
ibSmallCancel = it.findViewById(R.id.ibSmallCancel)
tvNickName.text = userInfo.nickName
it.setOnClickListener {
exchangeToBig()
}
ibSmallAnswer.setOnClickListener {
// 接听电话
callActionListener.windowAnswerCall(userInfo)
}
ibSmallCancel.setOnClickListener {
// 拒绝接听电话
callActionListener.windowRefuseCall(userInfo)
}
}
}
fun hideIncomingCall() {
log(TAG,"hideIncomingCall")
if (isWindowShow && windowManager != null) {
handler.removeMessages(MSG_DIALING_TIME_OUT)
log(TAG,"removeView : windowManager")
windowManager!!.removeView(bodyView)
windowManager = null
bodyView = null
isWindowShow = false
}
releaseAudioAndVoice()
}
/**
* 来电语音播报,现在只有语音来电,暂时不报别的
*/
private fun broadCastVoicePrompt() {
IMVoiceClient.speakAndRegisterCall({ agree ->
if (isWindowShow) {
log(TAG, "speakAndRegisterCall")
clickDialingCall(agree)
}
}, {
log(TAG, "playFinish")
playAudioCall()
registerIntentInComingCall()
})
}
private fun clickDialingCall(agree: Boolean) {
if (agree) {
callActionListener.windowAnswerCall(userInfo)
} else {
callActionListener.windowRefuseCall(userInfo)
}
releaseAudioAndVoice()
}
override fun onVoiceAnswerCall() {
callActionListener.windowAnswerCall(userInfo)
releaseAudioAndVoice()
}
override fun onVoiceRefuseCall() {
callActionListener.windowRefuseCall(userInfo)
releaseAudioAndVoice()
}
private fun releaseAudioAndVoice() {
stopAudioCall()
unRegisterVoice()
}
private fun playAudioCall() {
log(TAG, "播放来电铃声=====$isWindowShow")
if (isWindowShow) {
MediaController.startPlay(context, R.raw.call, true, AudioManager.STREAM_RING)
}
}
private fun stopAudioCall() {
log(TAG, "停止播放来电铃声======")
MediaController.release()
}
private fun registerIntentInComingCall() {
IMVoiceClient.registerIntentInComingCall(this)
}
private fun unRegisterVoice() {
IMVoiceClient.releaseSpeakAndRegisterCallback()
IMVoiceClient.unRegisterIntentInComingCall(this)
}
}

View File

@@ -0,0 +1,19 @@
package com.mogo.chat.window
import com.mogo.chat.model.bean.Sns
/**
* 浮窗通话状态通知
*/
interface IWindowCallActionListener {
/**
* 浮窗点击接听电话
*/
fun windowAnswerCall(userReceiver: Sns)
/**
* 浮窗显示拒绝接听电话
*/
fun windowRefuseCall(sns: Sns)
}