package com.example.kingway.ptt; import android.util.Log; import timber.log.Timber; import java.util.ArrayList; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; /**解析接收到的AT指令*/ public class CallBackResolution { private static String TAG = "PTT_RX"; private static String indexGroupId = "";//用户所在群组Id private static String indexGroupName = "";//用户所在群组名称 private static final int MAX_GROUP_MEMBERS = 500; private static final int MAX_GROUP_INFOS = 100; private static final Map memberInfoDtos = new LinkedHashMap<>(MAX_GROUP_MEMBERS); private static int speckType = 1; private static String talkId = ""; private static String talkName = ""; private static final Map groupInfos = new LinkedHashMap<>(MAX_GROUP_INFOS); private static String oldLoginSate = "00"; private static int logNum = 0; private static long offlineTime = 0; private static boolean loginState = false; private static String pttLoginStatus = "00"; // 00-未登录 01-登录中 02-已登录 private static String pttLoginId = ""; private static ScheduledExecutorService keepAliveExecutor = null; public static String getPttLoginStatus() { return pttLoginStatus; } public static String getPttLoginId() { return pttLoginId; } public static void resetPttLoginStatus() { pttLoginStatus = "00"; pttLoginId = ""; } public static String getCurrentGroupId() { return indexGroupId; } public static String getCurrentGroupName() { return indexGroupName; } public static void at_OEM_AT_cb(List lisHex){ if (lisHex.size() == 0) { return; } String ngx = "\\u"; try { Timber.tag(TAG).i( "lisHex-->" + lisHex.toString()); String firstHex = lisHex.get(0); String errorNum = lisHex.get(1); Timber.tag(TAG).d( "at_OEM_AT_cb firstHex: " + firstHex + ", errorNum: " + errorNum); switch (firstHex){ case "82"://登录状态通知指令 String loginId = lisHex.get(2) + "" + lisHex.get(3) + "" + lisHex.get(4) + "" + lisHex.get(5); pttLoginStatus = errorNum; pttLoginId = loginId; Timber.tag(TAG).d("PTT login status: "+pttLoginStatus+", id: "+pttLoginId); // 实时推送状态到前端 CallBackUtil.callBackLoinState(errorNum, loginId); loginState = isLoginConflict(errorNum); CallBackUtil.callBackLoginConflict(loginState); if (loginState) { Timber.tag(TAG).w("Login conflict detected, showing conflict dialog"); com.stand.standapp.MainActivity.executeJs("updatePttLoginStatus('" + pttLoginStatus + "','" + pttLoginId + "')"); com.stand.standapp.MainActivity.showPttConflictDialog(); } if ("00".equals(errorNum)) { com.stand.standapp.MainActivity.executeJs("updatePttLoginStatus('" + pttLoginStatus + "','" + pttLoginId + "')"); } if ("02".equals(errorNum)) { com.stand.standapp.MainActivity.onPttLoginSuccess(); com.stand.standapp.MainActivity.executeJs("updatePttLoginStatus('" + pttLoginStatus + "','" + pttLoginId + "')"); } break; case "a2"://账号有视频功能 CallBackUtil.callBackHaveVideo(); break; case "0d"://查询群组返回状态 if (errorNum.equals("00")){ CallBackUtil.callBackGroupInfo(groupInfos); } //CallBackUtil.callBackOnGroupSuccess(errorNum.equals("01")? false : true); break; case "0e"://查询群组成员返回状态 CallBackUtil.callBackOnMemberSuccess(errorNum.equals("01")? false : true, memberInfoDtos); break; case "0b"://发起讲话返回状态 Timber.tag(TAG).d( "at_OEM_AT_cb ptt speak..."); CallBackUtil.callBackSpeakPlaySuccess(errorNum.equals("00")? true : false); break; case "0c"://结束讲话返回状态 Timber.tag(TAG).d( "at_OEM_AT_cb speak end..."); CallBackUtil.callBackSpeakReleaseSuccess(errorNum.equals("00")? true : false); break; case "80"://返回群组信息 Timber.tag(TAG).d( "at_OEM_AT_cb get group info: " + lisHex); StringBuilder strGroup = new StringBuilder(); String groupId = lisHex.get(6) + "" + lisHex.get(7) + "" + lisHex.get(8) + "" + lisHex.get(9); int groupNo = Integer.valueOf((lisHex.get(4) + "" + lisHex.get(5)), 16); int groupCount = Integer.valueOf((lisHex.get(10) + "" + lisHex.get(11)), 16); try { int numB = 13; int size = lisHex.size() - 12; size = (size / 2) - 1; for (int i = 0; i < size; i++) { if (i == 0) { strGroup.append(ngx).append(lisHex.get(i + numB)).append(lisHex.get(i + numB - 1)); } else { strGroup.append(ngx).append(lisHex.get(i + numB + 1)).append(lisHex.get(i + numB)); numB = numB + 1; } } } catch (Exception e) { Timber.tag(TAG).e(e, "Decode hex failed"); } finally { String tempTxt = ""; tempTxt = unicodeToString(strGroup.toString()); Timber.tag(TAG).i( " 组名称 " + tempTxt); CallBackUtil.callBackGroupInfo(groupId, tempTxt, groupNo, groupCount); Timber.tag(TAG).d( "at_OEM_AT_cb group info groupId: " + groupId + ", groupNo: " + groupNo+", groupCount: " +groupCount); if (1 == groupNo) groupInfos.clear(); GroupInfoDto groupInfo = new GroupInfoDto(groupId, tempTxt, groupNo, groupCount); groupInfos.put(groupId, groupInfo); } break; case "81"://返回群成员信息 StringBuilder groupMemberName = new StringBuilder(); String status = lisHex.get(1); boolean haveVideo = lisHex.get(10).equals("01") ? true : false; String memberId = lisHex.get(6) + "" + lisHex.get(7) + "" + lisHex.get(8) + "" + lisHex.get(9); int memberNo = Integer.valueOf((lisHex.get(4) + "" + lisHex.get(5)), 16); try { int numB = 12; int size = lisHex.size() - 11; size = (size / 2) - 1; for (int i = 0; i < size; i++) { if (i == 0) { groupMemberName.append(ngx).append(lisHex.get(i + numB)).append(lisHex.get(i + numB - 1)); } else { groupMemberName.append(ngx).append(lisHex.get(i + numB + 1)).append(lisHex.get(i + numB)); numB = numB + 1; } } } catch (Exception e) { Timber.tag(TAG).e(e, "Decode hex failed"); } finally { String tempTxt = unicodeToString(groupMemberName.toString()); GroupMemberInfoDto infoDto = new GroupMemberInfoDto(memberId, tempTxt, status, memberNo, haveVideo); if (memberNo == 1) memberInfoDtos.clear(); memberInfoDtos.put(memberId, infoDto); } break; case "83"://讲话用户信息通知 if (lisHex.get(1).equals("00")) { speckType = 0; } else { speckType = 1; } talkId = lisHex.get(2) + "" + lisHex.get(3) + "" + lisHex.get(4) + "" + lisHex.get(5); List stringsSpker = new ArrayList<>(); try { int numA = 6; int numB = 7; int size = lisHex.size() - 6; size = (size / 2) - 1; for (int i = 0; i < size; i++) { if (i == 0) { stringsSpker.add(ngx + lisHex.get(i + numB) + "" + lisHex.get(i + numA)); } else { int start = i + numB; int end = i + numB + 1; stringsSpker.add(ngx + lisHex.get(end) + "" + lisHex.get(start)); numB = numB + 1; } } } catch (Exception e) { Timber.tag(TAG).e(e, "Decode hex failed"); } finally { String tempTxt = ""; for (int i = 0; i < stringsSpker.size(); i++) { tempTxt += unicode(stringsSpker.get(i)); } talkName = tempTxt; CallBackUtil.callBackNotifySpker(speckType, indexGroupId, indexGroupName, talkId, talkName); } break; case "8b"://通知音频播放的状态 int notifyPlayStatus = Integer.parseInt(lisHex.get(2)); if (notifyPlayStatus == 0){ talkId = ""; talkName = ""; } CallBackUtil.callBackNotifyPlayStatus(notifyPlayStatus, speckType, indexGroupId, indexGroupName, talkId, talkName); break; case "84"://提示信息通知 List stringsTalk = new ArrayList<>(); try { int numA = 2; int numB = 3; int size = lisHex.size() - 2; size = (size / 2) - 1; for (int i = 0; i < size; i++) { if (i == 0) { stringsTalk.add(ngx + lisHex.get(i + numB) + "" + lisHex.get(i + numA)); } else { int start = i + numB; int end = i + numB + 1; stringsTalk.add(ngx + lisHex.get(end) + "" + lisHex.get(start)); numB = numB + 1; } } } catch (Exception e) { Timber.tag(TAG).e(e, "Decode hex failed"); } finally { String tempTxt = ""; for (int i = 0; i < stringsTalk.size(); i++) { tempTxt += unicode(stringsTalk.get(i)); } int code=-1; if(tempTxt.equals("退出临时呼叫")){ code=0; } else if (tempTxt.startsWith("临时呼叫临时群组")) { code=1; } else if (tempTxt.equals("呼出失败")||tempTxt.equals("呼叫失败")) { code=2; } else if (tempTxt.equals("呼叫成功")||tempTxt.equals("呼出成功")) { code=3; }else{ code=-1; } Timber.tag(TAG).i( " 临时讲话 信息通知的指令 " + tempTxt + " code=" + code); CallBackUtil.callBackTempCallNotify(tempTxt); // 处理临时呼叫页面逻辑 if (code == 1) { // 临时呼叫临时群组 → 打开临时呼叫页面 try { android.content.Intent intent = new android.content.Intent( com.stand.standapp.MyApplication.getAppInstance(), com.stand.standapp.TempCallActivity.class ); // 从84消息中提取临时群名称,格式为"临时呼叫XXX" String tempGroupName = "临时会话"; if (tempTxt.startsWith("临时呼叫") && tempTxt.length() > "临时呼叫".length()) { tempGroupName = tempTxt.substring("临时呼叫".length()); } intent.putExtra("group_name", tempGroupName); intent.addFlags( android.content.Intent.FLAG_ACTIVITY_NEW_TASK | android.content.Intent.FLAG_ACTIVITY_SINGLE_TOP ); com.stand.standapp.MyApplication.getAppInstance().startActivity(intent); } catch (Exception e) { Timber.tag(TAG).e( "Failed to open TempCallActivity: " + e.getMessage()); } } else if (code == 0) { // 退出临时呼叫 → 关闭临时呼叫页面 if (com.stand.standapp.TempCallActivity.isOpen()) { com.stand.standapp.TempCallActivity.closeWithMessage("临时呼叫已结束"); } else if (com.stand.standapp.TempCallActivity.isMinimizedState()) { // 最小化状态下收到结束消息,清除状态并通知前端 com.stand.standapp.TempCallActivity.clearMinimizedState(); } } else if (code == 2) { // 呼叫失败 → 关闭临时呼叫页面 // if (com.stand.standapp.TempCallActivity.isOpen()) { // com.stand.standapp.TempCallActivity.closeWithMessage("呼叫失败"); // } else if (com.stand.standapp.TempCallActivity.isMinimizedState()) { // // 最小化状态下收到失败消息,清除状态并通知前端 // com.stand.standapp.TempCallActivity.clearMinimizedState(); // } } } break; case "86"://用户进入群组信息通知 String userGroupId = lisHex.get(2) + "" + lisHex.get(3) + "" + lisHex.get(4) + "" + lisHex.get(5); List stringsJumpGroup = new ArrayList<>(); try { int numA = 6; int numB = 7; int size = lisHex.size() - 6; size = (size / 2) - 1; for (int i = 0; i < size; i++) { if (i == 0) { stringsJumpGroup.add(ngx + lisHex.get(i + numB) + "" + lisHex.get(i + numA)); } else { int start = i + numB; int end = i + numB + 1; stringsJumpGroup.add(ngx + lisHex.get(end) + "" + lisHex.get(start)); numB = numB + 1; } } } catch (Exception e) { Timber.tag(TAG).e(e, "Decode hex failed"); } finally { String userGroupName = ""; for (int i = 0; i < stringsJumpGroup.size(); i++) { userGroupName += unicode(stringsJumpGroup.get(i)); } Timber.tag(TAG).i("进入群组 " + userGroupName); indexGroupId = userGroupId; indexGroupName = userGroupName; CallBackUtil.callBackNotifyUpdateGroup(userGroupId, userGroupName); } break; case "8d"://用户定位信息通知 if (lisHex.get(1).equals("00")) { String str = ""; String locationMemberId = lisHex.get(2) + "" + lisHex.get(3) + "" + lisHex.get(4) + "" + lisHex.get(5); for (int i = 0; i < lisHex.size() - 1; i++) { if (i > 5) { str = str + "" + lisHex.get(i).toString(); } } String address = hexString2String(str); address = address.substring(0,address.lastIndexOf(":")); try { String[] strLis = address.split(","); double latitude = Double.parseDouble(strLis[1]); double longitude = Double.parseDouble(strLis[0]); String time = strLis[2]; CallBackUtil.callBackNotifyLocationStatus(0, locationMemberId, latitude, longitude, time); } catch (Exception e) { Timber.tag(TAG).e( "获取定位Exception-->" + e.getMessage()); } } else { CallBackUtil.callBackNotifyLocationStatus(1, "", 0.0, 0.0, ""); } break; case "11"://上报位置信息 CallBackUtil.callBackNotifyUploadLocation(errorNum.equals("00")? true : false); break; case "02"://获取写码账号信息 String str = ""; for (int i = 4; i < lisHex.size() - 2; i++) { str = str + "" + lisHex.get(i); } String cmdTxt = hexString2String(str); CallBackUtil.callBackNotifyCodeInfo(cmdTxt); break; case "48"://获取写码密码信息 String strPwd = ""; for (int i = 4; i < lisHex.size() - 2; i++) { strPwd = strPwd + "" + lisHex.get(i); } String cmdPwdTxt = hexString2String(strPwd); CallBackUtil.callBackNotifyPwdInfo(cmdPwdTxt); break; case "45"://进入写码模式通知 CallBackUtil.callBackNotifyCodeOpenStatus(errorNum.equals("00")? true : false); break; case "52"://退出写码模式操作 CallBackUtil.callBackNotifyCodeExiteStatus(errorNum.equals("00")? true : false); break; case "47"://上传经销商密码 CallBackUtil.callBackNotifyUploadAgentPwd(errorNum.equals("00")? true : false); break; case "53"://上报本机告警信号 CallBackUtil.callBackRequestReportAlarm(errorNum.equals("00")? true : false); break; case "54"://停止本机告警信号 CallBackUtil.callBackRequestStopAlarm(errorNum.equals("00")? true : false); break; case "8e"://收到群成员告警信号 if (lisHex.get(1).equals("00")) { List strName = new ArrayList<>(); double latitude = 0.0, longitude = 0.0; try { String string = ""; for (int i = 4; i < lisHex.size(); i++) { string += lisHex.get(i).toString(); } String address = hexString2String(string); Timber.tag(TAG).i( "address-->" + address); String[] strLis = address.split(","); latitude = Double.parseDouble(strLis[0]); longitude = Double.parseDouble(strLis[1]); int size = lisHex.size() - (8 + strLis[0].length() + strLis[1].length()); size = size / 2; int numA = 4 + strLis[0].length() + strLis[1].length() + 3; int numB = 4 + strLis[0].length() + strLis[1].length() + 4; for (int i = 0; i < size; i++) { if (i == 0) { strName.add(ngx + lisHex.get(i + numB) + "" + lisHex.get(i + numA)); } else { int start = i + numB; int end = i + numB + 1; strName.add(ngx + lisHex.get(end) + "" + lisHex.get(start)); numB = numB + 1; } } } catch (Exception e) { } finally { String tempTxt = ""; for (int i = 0; i < strName.size(); i++) { if (strName.get(i).equals("\\u0000")) break; tempTxt += unicode(strName.get(i)); } CallBackUtil.callBackEnotifyAlarm(tempTxt, latitude, longitude); } } break; case "8f"://收到群成员停止警告信息 CallBackUtil.callBackEnotifyStopAlarm(); break; case "78"://上传NFC信息 CallBackUtil.callBackRequestNfc(errorNum.equals("00")? true : false); break; } }catch (Exception e){ Timber.tag(TAG).e("at_OEM_AT_cb Exception " + e.getMessage()); } } /** * 判断登录状态冲突, 1min内超过5次状态切换则视为登录冲突 * state * 0 : 离线 * 1 : 登陆中 * 2 : 登陆成功 * 3 : 注销中 * 说明:第一次登录 通知状态 先01再02, * 离线再上线 通知状态 先00再01 * tip:1min内会出现40次左右0-1切换 */ public static boolean isLoginConflict(String state) { Timber.tag(TAG).d( " isLoginConflict state: " + state + ",oldLoginSate: " + oldLoginSate + ", logNum: " + logNum); if (oldLoginSate.equals("00") && state.equals("01")) { Timber.tag(TAG).d( "isLoginConflict first login..."); logNum++; oldLoginSate = state; } else if (oldLoginSate.equals("01") && state.equals("00")) { Timber.tag(TAG).d( "isLoginConflict login state changed..."); logNum++; oldLoginSate = state; } if (1 == logNum) { offlineTime = System.currentTimeMillis(); } Timber.tag(TAG).d( "isLoginConflict logNum: " + logNum + ", offlineTime: " + offlineTime); if (System.currentTimeMillis() - offlineTime <= 60 * 1000) { if (logNum > 5) { Timber.tag(TAG).d("isLoginConflict login state changed: " + logNum + " in 1min"); return true; } } else { Timber.tag(TAG).d("isLoginConflict maybe network change, reset..."); logNum = 0; offlineTime = System.currentTimeMillis(); return false; } return false; } public static void at_Play_TTS_cb(String sData){ try { String[] txt = sData.split("\""); String data = txt[1]; Timber.tag(TAG).d( "at_Play_TTS_cb sData: " + sData); String ngx = "\\u"; List strings = new ArrayList<>(); try { String str = ""; for (int i = 0; i < data.length(); i++) { if (i == 0) { str = ngx + data.charAt(i); } else if (i % 4 == 0) { strings.add(str); str = ngx + data.charAt(i); } else if (i == data.length() - 1) { str = str + data.charAt(i); strings.add(str); } else { str = str + data.charAt(i); } } } catch (Exception e) { Timber.tag(TAG).e(e, "Decode hex failed"); } finally { StringBuilder sb = new StringBuilder(); for (int k = 0; k < strings.size(); k++) { sb.append(unicode(strings.get(k))); } String tempTxt = sb.toString(); Timber.tag(TAG).i("at_Play_TTS_cb " + tempTxt); if (tempTxt.contains("已登录")) { String showName = tempTxt.substring(3, tempTxt.length()); CallBackUtil.callBackLogin(true, showName); if (keepAliveExecutor != null && !keepAliveExecutor.isShutdown()) { keepAliveExecutor.shutdownNow(); } keepAliveExecutor = Executors.newSingleThreadScheduledExecutor(); Runnable task = () -> { Timber.tag(TAG).d( "callBackLogin send tcp and udp"); SendAtUtil.sendUDP(); SendAtUtil.sendTCP(); }; keepAliveExecutor.scheduleAtFixedRate(task, 0, 40, TimeUnit.SECONDS); } else if (tempTxt.contains("账号已更新")) { Timber.tag(TAG).w("TTS reports account updated: '%s'", tempTxt); } else if (tempTxt.contains("账号或密码错误")) { CallBackUtil.callBackLogin(false, ""); Timber.tag(TAG).e("Account/password error, showing dialog. TTS: '%s'", tempTxt); com.stand.standapp.MainActivity.showPttUpdateDialog("您的 PTT 账号信息账号或密码错误。\n请点击确认重启应用。","账号或密码错误"); }else if (tempTxt.contains("请配置账号")) { Timber.tag(TAG).w("TTS reports no account configured: '%s'", tempTxt); } } } catch (Exception e) { Timber.tag(TAG).e(e, "Decode hex failed"); } } public static String unicode(String str) { Pattern pattern = Pattern.compile("(\\\\u(\\p{XDigit}{4}))"); Matcher matcher = pattern.matcher(str); char ch; while (matcher.find()) { String group = matcher.group(2); ch = (char) Integer.parseInt(group, 16); String group1 = matcher.group(1); str = str.replace(group1, ch + ""); } return str; } public static String unicodeToString(String hex) { int t = hex.length() / 6; StringBuilder str = new StringBuilder(); for (int i = 0; i < t; i++) { String s = hex.substring(i * 6, (i + 1) * 6); // 高位需要补上00再转 String s1 = s.substring(2, 4) + "00"; // 低位直接转 String s2 = s.substring(4); // 将16进制的string转为int int n = Integer.valueOf(s1, 16) + Integer.valueOf(s2, 16); // 将int转换为字符 char[] chars = Character.toChars(n); str.append(new String(chars)); } return str.toString(); } public static String hexString2String(String src) { String temp = ""; for (int i = 0; i < src.length() / 2; i++) { temp = temp + (char) Integer.valueOf(src.substring(i * 2, i * 2 + 2), 16).byteValue(); } return temp; } }