将车聊聊工程代码加入到项目中,保证能跑起来了,后续做架构升级
Signed-off-by: 董宏宇 <martindhy@gmail.com>
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
}
|
||||
@@ -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() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.mogo.chat.callcenter
|
||||
|
||||
interface ICallTypeChangedListener {
|
||||
fun onCallTypeChanged(callType: IMType)
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.mogo.chat.common.gme
|
||||
|
||||
interface IGMEEventCallBack {
|
||||
fun gmeHangUp()
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.mogo.chat.constant
|
||||
|
||||
enum class RequestCode {
|
||||
|
||||
MATCH_ACTIVITY,
|
||||
PERSONAL_CENTER_ACTIVITY
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.mogo.chat.constant
|
||||
|
||||
enum class ResultCode {
|
||||
|
||||
MATCH_CANCEL,
|
||||
MATCH_ENTRY_ROOM,
|
||||
MATCH_FAIL,
|
||||
PERSONAL_CENTER_CALL
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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) }
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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) }
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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
|
||||
)
|
||||
@@ -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)"
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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)"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
package com.mogo.chat.model.bean
|
||||
|
||||
data class FocusStatus(val isFocus: Int)
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
@@ -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)
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.mogo.chat.model.bean
|
||||
|
||||
/**
|
||||
* 长连接初步建立时的初始化信息封装
|
||||
*/
|
||||
data class InitMessage(val nickName:String,val headImg:String,val localUserId:Int)
|
||||
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
)
|
||||
@@ -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
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.mogo.chat.model.bean
|
||||
|
||||
fun OnLineStatus.onLine(): Boolean {
|
||||
return carOnLine == 1 && onLine == 1
|
||||
}
|
||||
|
||||
/**
|
||||
* carOnLine:ACC ON状态 1:在线 2:不在线
|
||||
* onLine:是否隐身 1:在线 2:隐身
|
||||
*/
|
||||
data class OnLineStatus(val carOnLine: Int, val onLine: Int)
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.mogo.chat.model.bean
|
||||
|
||||
class Results<out T>(val t :T) {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.mogo.chat.model.bean
|
||||
|
||||
class RoomInfo {
|
||||
|
||||
var roomId:Int
|
||||
|
||||
constructor(roomId: Int) {
|
||||
this.roomId = roomId
|
||||
}
|
||||
}
|
||||
@@ -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)"
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.mogo.chat.model.bean
|
||||
|
||||
data class SplashConfigRequest(
|
||||
val dataSource: String,
|
||||
val serverType: String
|
||||
)
|
||||
@@ -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)
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.mogo.chat.model.bean
|
||||
|
||||
|
||||
data class TopicRequest(var topicGuide: TopicGuide)
|
||||
data class TopicGuide(var topicContent:ArrayList<String>)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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>
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
Reference in New Issue
Block a user