修复问题
This commit is contained in:
parent
55841872ba
commit
cd0b247438
|
|
@ -13,16 +13,17 @@
|
||||||
.externalNativeBuild
|
.externalNativeBuild
|
||||||
.cxx
|
.cxx
|
||||||
local.properties
|
local.properties
|
||||||
|
/app/release/baselineProfiles/0/app-release.dm
|
||||||
|
/app/release/baselineProfiles/1/app-release.dm
|
||||||
|
/app/release/app-release.apk
|
||||||
|
/app/release/output-metadata.json
|
||||||
/startopencode.bat
|
/startopencode.bat
|
||||||
/.idea/
|
/.idea/AndroidProjectSystem.xml
|
||||||
/第三方/ptt/
|
/.idea/compiler.xml
|
||||||
/第三方/安卓demo和说明/POC SOS扩展指令手册V1.0.docx
|
/.idea/deploymentTargetSelector.xml
|
||||||
/第三方/安卓demo和说明/POC 多用户单呼扩展.docx
|
/.idea/encodings.xml
|
||||||
/第三方/安卓demo和说明/POC应用扩展指令手册V4.0(TTS功能只需看第7部分).pdf
|
/.idea/gradle.xml
|
||||||
/第三方/安卓demo和说明/ptt.rar
|
/.idea/markdown.xml
|
||||||
.idea/
|
/.idea/misc.xml
|
||||||
.idea/
|
/.idea/runConfigurations.xml
|
||||||
/第三方/安卓demo和说明/POC_Manual.txt
|
/.idea/vcs.xml
|
||||||
/第三方/安卓demo和说明/POC_SingleCall.md
|
|
||||||
/第三方/安卓demo和说明/POC_SOS.md
|
|
||||||
/app/release/
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="DeviceTable">
|
||||||
|
<option name="columnSorters">
|
||||||
|
<list>
|
||||||
|
<ColumnSorterState>
|
||||||
|
<option name="column" value="Name" />
|
||||||
|
<option name="order" value="ASCENDING" />
|
||||||
|
</ColumnSorterState>
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
84
AGENTS.md
84
AGENTS.md
|
|
@ -1,84 +0,0 @@
|
||||||
# StandAPP 智能调度终端 - 开发指南 (AGENTS.md)
|
|
||||||
|
|
||||||
本文档为 StandAPP 项目的开发规范与技术参考,旨在帮助开发者快速理解项目架构、构建流程及核心业务逻辑。
|
|
||||||
|
|
||||||
## 1. 项目概况
|
|
||||||
StandAPP 是一款基于 Kotlin 和 Jetpack Compose 开发的 Android 应用。集成了 `AgentWeb`(网页交互)、打印服务、自动更新以及核心的 **PTT (Push-to-Talk) 对讲模块**。
|
|
||||||
|
|
||||||
- **主包名:** `com.stand.standapp`
|
|
||||||
- **PTT 包名:** `com.example.kingway.ptt` (严禁修改,与 Native 层硬绑定)
|
|
||||||
- **技术栈:** Kotlin, Jetpack Compose, Gradle (Kotlin DSL), OkHttp, AgentWeb, NDK (C++).
|
|
||||||
- **SDK 版本:** Compile SDK 36, Target SDK 34, Min SDK 24.
|
|
||||||
|
|
||||||
## 2. 构建、检查与测试
|
|
||||||
|
|
||||||
### 构建命令
|
|
||||||
- **编译 Debug APK:** `./gradlew assembleDebug`
|
|
||||||
- **清理项目:** `./gradlew clean`
|
|
||||||
- **编译 Release APK:** `./gradlew assembleRelease`
|
|
||||||
|
|
||||||
### 检查与测试
|
|
||||||
- **运行 Lint 检查:** `./gradlew lint`
|
|
||||||
- **运行单元测试:** `./gradlew test`
|
|
||||||
- **运行单项测试:** `./gradlew :app:testDebugUnitTest --tests "包名.类名"`
|
|
||||||
|
|
||||||
## 3. 代码风格与规范
|
|
||||||
|
|
||||||
- **缩进:** 4 个空格。
|
|
||||||
- **命名:** 类名使用 `PascalCase`;函数和变量使用 `camelCase`;常量使用 `SCREAMING_SNAKE_CASE`。
|
|
||||||
- **Compose:** 优先使用 Material 3 组件;Composable 函数首字母大写;Modifier 必须作为第一个可选参数。
|
|
||||||
- **错误处理:** 必须使用 `try-catch` 包裹 IO、网络、JSON 解析等易错操作,并使用 `Log.e` 记录异常。
|
|
||||||
|
|
||||||
## 4. PTT (对讲) 核心业务逻辑解析
|
|
||||||
|
|
||||||
PTT 模块是本项目的通讯核心,依赖底层 `libptt.so` 原生引擎。其业务流程高度依赖 **十六进制 AT 指令** 交互。
|
|
||||||
|
|
||||||
### 4.1 核心组件与关键方法
|
|
||||||
|
|
||||||
#### **A. 指令发送:`SendAtUtil` (指令工厂)**
|
|
||||||
封装了所有发往 Native 引擎的控制指令。
|
|
||||||
- `toLogin(account, pwd, ip, location, tempCall)`: **登录接口**。将账号、密码、服务器 IP、GPS 开启标志及单呼标志拼接为 `010000` 开头的十六进制指令。
|
|
||||||
- `speakPlay()`: **开始对讲**。发送 `0B0000` 指令,触发底层 POC 引擎进入发送模式并准备处理语音数据。
|
|
||||||
- `speakRelease()`: **停止对讲**。发送 `0C0000` 指令通知引擎结束通话,并同步停止本地录音线程。
|
|
||||||
- `sendTCP()` / `sendUDP()`: **心跳维持**。登录成功后,必须每 **40秒** 调用一次。`570000` 为 TCP 心跳,`580000` 为 UDP 心跳。
|
|
||||||
- `getGroupInfo()`: **查询群组**。发送 `0D0000` 指令。
|
|
||||||
- `getGroupMemberInfo(groupId)`: **查询成员**。发送 `0E0000` + 群组 ID。
|
|
||||||
- `jumpGroup(groupId)`: **切换群组**。发送 `090000` + 群组 ID,用于即时切换监听的群组。
|
|
||||||
|
|
||||||
#### **B. 指令解析:`CallBackResolution` (响应处理器)**
|
|
||||||
解析 Native 引擎通过 JNI 异步返回的十六进制数据。
|
|
||||||
- `at_OEM_AT_cb(lisHex)`: **解析入口**。识别首字节指令码:
|
|
||||||
- `82`: 登录/离线状态变更。
|
|
||||||
- `80`: 接收群组详细信息(ID、名称、成员数)。
|
|
||||||
- `81`: 接收群成员在线状态及视频能力。
|
|
||||||
- `83`: 语音发起者信息(当前谁在说话)。
|
|
||||||
- `8b`: 播音状态变更(引擎开始或停止播放对方声音)。
|
|
||||||
- `8d`: 经纬度数据通知。
|
|
||||||
- `at_Play_TTS_cb(sData)`: **TTS 状态解析**。解析中文状态描述。当识别到“已登录”时,需触发心跳定时器。
|
|
||||||
- `isLoginConflict(state)`: **冲突检测**。逻辑:1 分钟内若状态在“离线”和“登录中”切换超过 5 次,则判断为登录冲突。
|
|
||||||
|
|
||||||
#### **C. 录音管理:`AudioRecordManager` (音频采集)**
|
|
||||||
- `startRecord()`: 启动录音循环。设置优先级为 `THREAD_PRIORITY_URGENT_AUDIO`。
|
|
||||||
- `stopRecord()`: 释放 `AudioRecord` 资源。
|
|
||||||
- **技术参数**: 采样率 8000Hz, 16bit, 单声道。每读取 320 字节 PCM 原始数据即调用 `pttNativeSendRecordCmd` 推送至 Native 层。
|
|
||||||
|
|
||||||
#### **D. JNI 桥接:`pttNative` (底层交互)**
|
|
||||||
- `OEM_Play_cb(byte[] bytes)`: **播放回调**。底层接收到的音频流会实时回调此方法,数据需直接写入 `AudioTrack` 实例进行播放。
|
|
||||||
- `pttNativePocTaskStart()`: **引擎初始化**。
|
|
||||||
|
|
||||||
### 4.2 业务技术流程
|
|
||||||
1. **初始化**: 调用 `MyApplication.init(context)` -> 加载 SO 库 -> 启动 Native 任务。
|
|
||||||
2. **认证**: 调用 `toLogin` -> 异步等待 `82` 回调或 TTS “已登录”提示。
|
|
||||||
3. **在线维持**: 登录成功后启动周期为 40s 的任务发送 TCP/UDP 心跳。
|
|
||||||
4. **对讲过程**:
|
|
||||||
- **按下 PTT**: `speakPlay` -> 收到成功反馈 -> `AudioRecordManager` 持续采音并推送。
|
|
||||||
- **松开 PTT**: `speakRelease` -> 停止录音 -> 发送结束指令。
|
|
||||||
5. **UI 更新**: 引擎回调均在 **子线程**。所有 UI 操作(Toast、Dialog、TextView 更新)必须切回主线程。
|
|
||||||
|
|
||||||
## 5. 开发约束与安全
|
|
||||||
1. **包名与类名**: `com.example.kingway.ptt.pttNative` 严禁改名或移动,否则会导致 JNI 方法找不到实现导致崩溃。
|
|
||||||
2. **权限管理**: 必须先通过 `XXPermissions` 获取 `RECORD_AUDIO` 权限后再执行对讲。
|
|
||||||
3. **线程规范**: 回调处理严禁阻塞。耗时操作必须异步执行。
|
|
||||||
|
|
||||||
---
|
|
||||||
*本文档由 AI 助手维护,旨在确保业务逻辑的一致性。*
|
|
||||||
175
PTT_API_DOC.md
175
PTT_API_DOC.md
|
|
@ -1,175 +0,0 @@
|
||||||
# PTT 平台外部接口详细文档
|
|
||||||
|
|
||||||
**基础路径**: `/api/external/ptt`
|
|
||||||
**说明**: 该控制器是与第三方 PTT 语音平台对接的核心入口,涵盖了频道管理、成员状态、录音存取及语音识别等全量功能。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. 频道与会话管理
|
|
||||||
|
|
||||||
### 三、创建频道
|
|
||||||
* **接口**: `POST /group`
|
|
||||||
* **描述**: 在平台创建一个新的普通频道,支持邀请账号列表或用户 UID 列表。
|
|
||||||
* **参数**:
|
|
||||||
* `gname` (Query, 必填): 群组/频道名称。
|
|
||||||
* `accounts` (Body, 可选): 参与成员账号列表 (`List<String>`)。
|
|
||||||
* `uids` (Query, 可选): 参与成员 UID 列表 (`List<Integer>`)。
|
|
||||||
* **返回**: `Result<PttModels.OpData>`
|
|
||||||
|
|
||||||
### 四、删除频道
|
|
||||||
* **接口**: `DELETE /group/{gid}`
|
|
||||||
* **描述**: 删除指定的普通频道。
|
|
||||||
* **参数**:
|
|
||||||
* `gid` (Path, 必填): 频道 ID。
|
|
||||||
* **返回**: `Result<Boolean>`
|
|
||||||
|
|
||||||
### 五、修改频道
|
|
||||||
* **接口**: `PUT /group/{gid}`
|
|
||||||
* **描述**: 修改频道的名称信息。
|
|
||||||
* **参数**:
|
|
||||||
* `gid` (Path, 必填): 频道 ID。
|
|
||||||
* `newName` (Query, 必填): 新的频道名称。
|
|
||||||
* **返回**: `Result<Boolean>`
|
|
||||||
|
|
||||||
### 六、拉成员入频道
|
|
||||||
* **接口**: `POST /group/{gid}/members`
|
|
||||||
* **描述**: 将指定的账号列表加入到特定的频道中。
|
|
||||||
* **参数**:
|
|
||||||
* `gid` (Path, 必填): 频道 ID。
|
|
||||||
* `accounts` (Body, 必填): 待加入的成员账号列表 (`List<String>`)。
|
|
||||||
* **返回**: `Result<Boolean>`
|
|
||||||
|
|
||||||
### 七、成员踢出频道
|
|
||||||
* **接口**: `DELETE /group/{gid}/members`
|
|
||||||
* **描述**: 将指定的账号列表从频道中移除。
|
|
||||||
* **参数**:
|
|
||||||
* `gid` (Path, 必填): 频道 ID。
|
|
||||||
* `accounts` (Body, 必填): 待移除的成员账号列表 (`List<String>`)。
|
|
||||||
* **返回**: `Result<Boolean>`
|
|
||||||
|
|
||||||
### 二十、创建临时会话
|
|
||||||
* **接口**: `POST /temp-group`
|
|
||||||
* **描述**: 发起一个新的临时群组通话。
|
|
||||||
* **参数**:
|
|
||||||
* `gname` (Query, 必填): 群组名称。
|
|
||||||
* `accounts` (Body, 必填): 参与成员账号列表 (`List<String>`)。
|
|
||||||
* `creatorid` (Query, 必填): 创建者 UID。
|
|
||||||
* **返回**: `Result<PttModels.OpData>`
|
|
||||||
|
|
||||||
### 二十一、删除临时会话
|
|
||||||
* **接口**: `DELETE /temp-group/{gid}`
|
|
||||||
* **描述**: 注销/结束一个临时群组。
|
|
||||||
* **参数**:
|
|
||||||
* `gid` (Path, 必填): 临时群组 ID。
|
|
||||||
* **返回**: `Result<Boolean>`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. 列表与成员查询
|
|
||||||
|
|
||||||
### 八、频道成员列表
|
|
||||||
* **接口**: `GET /group/{gid}/members`
|
|
||||||
* **描述**: 查询指定普通频道内的成员列表,支持搜索和分页。
|
|
||||||
* **参数**:
|
|
||||||
* `gid` (Path, 必填): 频道 ID。
|
|
||||||
* `page`, `limit` (Query, 可选): 分页参数。
|
|
||||||
* `search` (Query, 可选): 搜索关键词(用户名/账号)。
|
|
||||||
* **返回**: `Result<PttResult<List<PttModels.Member>>>`
|
|
||||||
|
|
||||||
### 九、频道列表查询
|
|
||||||
* **接口**: `GET /groups`
|
|
||||||
* **描述**: 获取系统内所有的普通频道列表。
|
|
||||||
* **参数**: `page`, `limit` (Query, 可选)。
|
|
||||||
* **返回**: `Result<PttResult<List<PttModels.Group>>>`
|
|
||||||
|
|
||||||
### 十、临时会话列表查询
|
|
||||||
* **接口**: `GET /temp-groups`
|
|
||||||
* **描述**: 获取所有的临时群组列表。
|
|
||||||
* **参数**: `page`, `limit` (Query, 可选)。
|
|
||||||
* **返回**: `Result<PttResult<List<PttModels.Group>>>`
|
|
||||||
|
|
||||||
### 十一、成员可见频道列表查询
|
|
||||||
* **接口**: `GET /user/groups`
|
|
||||||
* **描述**: 查询指定成员有权限查看的频道。
|
|
||||||
* **参数**: `account`, `uid`, `search` (Query, 可选)。
|
|
||||||
* **返回**: `Result<List<PttModels.Group>>`
|
|
||||||
|
|
||||||
### 二十九、查询某个临时群组下的成员
|
|
||||||
* **接口**: `GET /temp-group/{tempgid}/members`
|
|
||||||
* **描述**: 获取指定临时群组内的所有成员详情。
|
|
||||||
* **参数**: `tempgid` (Path, 必填)。
|
|
||||||
* **返回**: `Result<List<PttModels.Member>>`
|
|
||||||
|
|
||||||
### 三十一、分页查询群组成员
|
|
||||||
* **接口**: `GET /group/{gid}/members-paged`
|
|
||||||
* **描述**: 按在线状态分页查询群组成员。
|
|
||||||
* **参数**:
|
|
||||||
* `gid` (Path, 必填)。
|
|
||||||
* `condition` (Query, 可选): 在线状态筛选 (1:在线, 2:离线, 3:在线不在组)。
|
|
||||||
* `page`, `limit` (Query, 可选)。
|
|
||||||
* **返回**: `Result<PttResult<List<PttModels.Member>>>`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. 录音记录与处理 (核心功能)
|
|
||||||
|
|
||||||
### 十二、查询频道内成员ptt录音
|
|
||||||
* **接口**: `POST /group/{gid}/records`
|
|
||||||
* **描述**: 获取普通频道内指定成员在特定时间段的 PTT 录音记录。
|
|
||||||
* **参数**:
|
|
||||||
* `gid` (Path, 必填)。
|
|
||||||
* `uids` (Body, 可选): 筛选的成员 UID 列表。
|
|
||||||
* `start`, `end` (Query, 可选): 时间范围 (yyyy-MM-dd HH:mm:ss)。
|
|
||||||
* `page`, `limit` (Query, 可选)。
|
|
||||||
* **返回**: `Result<PttResult<List<PttModels.Record>>>`
|
|
||||||
|
|
||||||
### 十三、查询临时会话下的录音
|
|
||||||
* **接口**: `POST /temp-group/{tempgid}/records`
|
|
||||||
* **描述**: 获取临时会话下特定时间段的 PTT 录音记录。
|
|
||||||
* **参数**: 同上,针对临时群组 ID。
|
|
||||||
* **返回**: `Result<PttResult<List<PttModels.Record>>>`
|
|
||||||
|
|
||||||
### 获取录音转文字 (新增集成)
|
|
||||||
* **接口**: `GET /record/text`
|
|
||||||
* **描述**: 根据录音地址获取并转为文字。
|
|
||||||
* **特性**:
|
|
||||||
1. 内部自动处理音频下载与本地缓存。
|
|
||||||
2. 已转义过的记录将直接从数据库返回,避免重复调用 ASR。
|
|
||||||
* **参数**: `path` (Query, 必填): 录音文件远程相对路径。
|
|
||||||
* **返回**: `Result<String>` (识别后的文本内容)。
|
|
||||||
|
|
||||||
### 播放录音 (流式处理)
|
|
||||||
* **接口**: `GET /record/play`
|
|
||||||
* **描述**: 在线播放录音文件,首次播放会自动缓存到服务器本地。
|
|
||||||
* **特性**:
|
|
||||||
1. **流式输出**: 直接写入 Response 输出流,无内存占用压力。
|
|
||||||
2. **拖动支持**: 支持 HTTP Range 请求,可在浏览器进度条中任意拖动。
|
|
||||||
3. **优先播放**: 默认 `inline` 模式,谷歌浏览器直接调用内置播放器。
|
|
||||||
* **参数**: `path` (Query, 必填): 录音文件远程相对路径。
|
|
||||||
* **返回**: 二进制音频流 (`audio/wav`)。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. 系统工具
|
|
||||||
|
|
||||||
### 手动执行平台登录
|
|
||||||
* **接口**: `POST /login`
|
|
||||||
* **描述**: 刷新 PTT 平台的会话 Token 并重新登录。用于故障恢复或手动同步。
|
|
||||||
* **返回**: `Result<String>`
|
|
||||||
|
|
||||||
### 二、退出登录
|
|
||||||
* **接口**: `POST /logout`
|
|
||||||
* **描述**: 注销当前 PTT 会话。
|
|
||||||
* **返回**: `Result<Boolean>`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 响应规范
|
|
||||||
除 `playRecord` 这种流式接口外,所有接口均遵循统一响应体:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"code": 200, // 状态码 (200: 成功)
|
|
||||||
"msg": "操作成功", // 提示信息
|
|
||||||
"data": { ... } // 业务载荷
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
@ -1,5 +1 @@
|
||||||
/build
|
/build
|
||||||
/release/baselineProfiles/0/app-release.dm
|
|
||||||
/release/baselineProfiles/1/app-release.dm
|
|
||||||
/release/app-release-V1.0.3.apk
|
|
||||||
/release/output-metadata.json
|
|
||||||
|
|
@ -6,7 +6,7 @@ plugins {
|
||||||
android {
|
android {
|
||||||
namespace = "com.stand.standapp"
|
namespace = "com.stand.standapp"
|
||||||
compileSdk = 36
|
compileSdk = 36
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId = "com.stand.standapp"
|
applicationId = "com.stand.standapp"
|
||||||
minSdk = 24
|
minSdk = 24
|
||||||
|
|
@ -15,9 +15,8 @@ android {
|
||||||
versionName = "1.0.5"
|
versionName = "1.0.5"
|
||||||
|
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
|
||||||
ndk {
|
ndk {
|
||||||
abiFilters.addAll(listOf("armeabi-v7a", "arm64-v8a"))
|
abiFilters += listOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -49,7 +48,6 @@ dependencies {
|
||||||
implementation("com.github.Justson:Downloader:v5.0.4-androidx")
|
implementation("com.github.Justson:Downloader:v5.0.4-androidx")
|
||||||
implementation("com.google.android.material:material:1.12.0")
|
implementation("com.google.android.material:material:1.12.0")
|
||||||
implementation("com.squareup.okhttp3:okhttp:4.12.0")
|
implementation("com.squareup.okhttp3:okhttp:4.12.0")
|
||||||
implementation("com.github.getActivity:XXPermissions:18.2")
|
|
||||||
implementation(libs.androidx.appcompat)
|
implementation(libs.androidx.appcompat)
|
||||||
implementation(libs.androidx.core.ktx)
|
implementation(libs.androidx.core.ktx)
|
||||||
implementation(libs.androidx.lifecycle.runtime.ktx)
|
implementation(libs.androidx.lifecycle.runtime.ktx)
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@
|
||||||
<uses-permission android:name="android.permission.VIBRATE" />
|
<uses-permission android:name="android.permission.VIBRATE" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
||||||
|
|
@ -23,7 +22,6 @@
|
||||||
android:usesCleartextTraffic="true"
|
android:usesCleartextTraffic="true"
|
||||||
android:networkSecurityConfig="@xml/network_security_config"
|
android:networkSecurityConfig="@xml/network_security_config"
|
||||||
android:theme="@style/Theme.StandAPP">
|
android:theme="@style/Theme.StandAPP">
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".SplashActivity"
|
android:name=".SplashActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
|
|
@ -40,18 +38,6 @@
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:theme="@style/Theme.StandAPP" />
|
android:theme="@style/Theme.StandAPP" />
|
||||||
|
|
||||||
<activity
|
|
||||||
android:name=".MainActivity"
|
|
||||||
android:exported="false"
|
|
||||||
android:label="@string/app_name"
|
|
||||||
android:theme="@style/Theme.StandAPP" />
|
|
||||||
|
|
||||||
<activity
|
|
||||||
android:name="com.example.kingway.ptt.pttActivity"
|
|
||||||
android:exported="false"
|
|
||||||
android:label="PTT示例"
|
|
||||||
android:theme="@style/Theme.AppCompat.Light.NoActionBar" />
|
|
||||||
|
|
||||||
<provider
|
<provider
|
||||||
android:name="androidx.core.content.FileProvider"
|
android:name="androidx.core.content.FileProvider"
|
||||||
android:authorities="${applicationId}.fileprovider"
|
android:authorities="${applicationId}.fileprovider"
|
||||||
|
|
|
||||||
|
|
@ -94,7 +94,7 @@
|
||||||
<strong>WebSocket 实时推送</strong>
|
<strong>WebSocket 实时推送</strong>
|
||||||
<span id="ws-status-tag" class="ws-status ws-disconnected">未连接</span>
|
<span id="ws-status-tag" class="ws-status ws-disconnected">未连接</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="margin-top: 10px; font-size: 13px;">
|
<div style="margin-top: 10px; font-size: 13px;">
|
||||||
ID: <span id="ws-client-id" style="color: #6366f1;">-</span>
|
ID: <span id="ws-client-id" style="color: #6366f1;">-</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -102,7 +102,7 @@
|
||||||
<!-- WS 地址配置 -->
|
<!-- WS 地址配置 -->
|
||||||
<div class="ws-input-group">
|
<div class="ws-input-group">
|
||||||
<label style="font-size: 12px; color: #64748b;">WebSocket 连接地址:</label>
|
<label style="font-size: 12px; color: #64748b;">WebSocket 连接地址:</label>
|
||||||
<input type="text" id="ws-url-input" class="ws-input"
|
<input type="text" id="ws-url-input" class="ws-input"
|
||||||
value="wss://template.gyouzhe.com/api/ws/{clientId}?Authorization=qiangfengsuiyuehen">
|
value="wss://template.gyouzhe.com/api/ws/{clientId}?Authorization=qiangfengsuiyuehen">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -384,7 +384,7 @@
|
||||||
item.className = 'log-item';
|
item.className = 'log-item';
|
||||||
item.innerHTML = `<span class="log-time">[${time}]</span><span class="log-msg">${msg}</span>`;
|
item.innerHTML = `<span class="log-time">[${time}]</span><span class="log-msg">${msg}</span>`;
|
||||||
logList.insertBefore(item, logList.firstChild);
|
logList.insertBefore(item, logList.firstChild);
|
||||||
|
|
||||||
// 限制 DOM 节点数量,避免页面卡顿(虽然存储 5000 条,但显示建议限制在 200 条)
|
// 限制 DOM 节点数量,避免页面卡顿(虽然存储 5000 条,但显示建议限制在 200 条)
|
||||||
if (logList.childNodes.length > 200) {
|
if (logList.childNodes.length > 200) {
|
||||||
logList.removeChild(logList.lastChild);
|
logList.removeChild(logList.lastChild);
|
||||||
|
|
@ -412,13 +412,13 @@
|
||||||
ws.onclose = null; // 清除之前的重连逻辑
|
ws.onclose = null; // 清除之前的重连逻辑
|
||||||
ws.close();
|
ws.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
const rawUrl = document.getElementById('ws-url-input').value;
|
const rawUrl = document.getElementById('ws-url-input').value;
|
||||||
const finalUrl = rawUrl.replace("{clientId}", clientId);
|
const finalUrl = rawUrl.replace("{clientId}", clientId);
|
||||||
|
|
||||||
addLog("正在尝试连接: " + finalUrl, false);
|
addLog("正在尝试连接: " + finalUrl, false);
|
||||||
isManualClose = false; // 启动连接时重置标记
|
isManualClose = false; // 启动连接时重置标记
|
||||||
|
|
||||||
try {
|
try {
|
||||||
ws = new WebSocket(finalUrl);
|
ws = new WebSocket(finalUrl);
|
||||||
|
|
||||||
|
|
@ -441,7 +441,7 @@
|
||||||
updateWSStatus(false);
|
updateWSStatus(false);
|
||||||
addLog("连接关闭", false);
|
addLog("连接关闭", false);
|
||||||
stopHeartbeat();
|
stopHeartbeat();
|
||||||
|
|
||||||
// 只有非手动断开(如网络异常)时才自动重连
|
// 只有非手动断开(如网络异常)时才自动重连
|
||||||
if (!isManualClose) {
|
if (!isManualClose) {
|
||||||
addLog("异常断开,5秒后自动重连...", false);
|
addLog("异常断开,5秒后自动重连...", false);
|
||||||
|
|
|
||||||
|
|
@ -1,177 +0,0 @@
|
||||||
package com.example.kingway.ptt;
|
|
||||||
|
|
||||||
import android.media.AudioFormat;
|
|
||||||
import android.media.AudioRecord;
|
|
||||||
import android.media.MediaRecorder;
|
|
||||||
import android.util.Log;
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
@SuppressWarnings("ResultOfMethodCallIgnored")
|
|
||||||
public class AudioRecordManager {
|
|
||||||
private final String TAG = "AudioRecordManager";
|
|
||||||
private static final int FREQUENCY = 8000;
|
|
||||||
private AudioRecord mRecorder;
|
|
||||||
private Thread recordThread;
|
|
||||||
private boolean isStart = false;
|
|
||||||
private static AudioRecordManager mInstance;
|
|
||||||
private int bufferSize = 320;
|
|
||||||
|
|
||||||
|
|
||||||
private AudioRecordManager() {
|
|
||||||
initAudio();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void initAudio() {
|
|
||||||
int audioSource = MediaRecorder.AudioSource.MIC;
|
|
||||||
mRecorder = new AudioRecord(audioSource,
|
|
||||||
FREQUENCY,
|
|
||||||
AudioFormat.CHANNEL_IN_MONO,
|
|
||||||
AudioFormat.ENCODING_PCM_16BIT,
|
|
||||||
bufferSize * 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setRecording(){
|
|
||||||
if (mRecorder == null) initAudio();
|
|
||||||
if(mRecorder.getRecordingState() != 3) mRecorder.startRecording();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void stopAudio(){
|
|
||||||
if (mRecorder != null){
|
|
||||||
mRecorder.stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getState(){
|
|
||||||
if (mRecorder == null) initAudio();
|
|
||||||
return mRecorder.getRecordingState();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void destroyAudio(){
|
|
||||||
if (mRecorder != null){
|
|
||||||
mRecorder.stop();
|
|
||||||
mRecorder.release();
|
|
||||||
mRecorder = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取单例引用
|
|
||||||
*/
|
|
||||||
public static AudioRecordManager getInstance() {
|
|
||||||
if (mInstance == null) {
|
|
||||||
synchronized (AudioRecordManager.class) {
|
|
||||||
if (mInstance == null) {
|
|
||||||
mInstance = new AudioRecordManager();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return mInstance;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 销毁线程方法
|
|
||||||
*/
|
|
||||||
private void destroyThread() {
|
|
||||||
try {
|
|
||||||
if (null != recordThread && Thread.State.RUNNABLE == recordThread.getState()) {
|
|
||||||
try {
|
|
||||||
recordThread.interrupt();
|
|
||||||
} catch (Exception e) {
|
|
||||||
recordThread = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
recordThread = null;
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
} finally {
|
|
||||||
recordThread = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 启动录音线程
|
|
||||||
*/
|
|
||||||
private void startThread() {
|
|
||||||
destroyThread();
|
|
||||||
isStart = true;
|
|
||||||
if (mRecorder == null) {
|
|
||||||
initAudio();
|
|
||||||
}
|
|
||||||
if (recordThread == null) {
|
|
||||||
recordThread = new Thread(recordRunnable);
|
|
||||||
recordThread.start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 录音线程
|
|
||||||
*/
|
|
||||||
private Runnable recordRunnable = new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
try {
|
|
||||||
android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_AUDIO);
|
|
||||||
int bytesRecord;
|
|
||||||
byte[] tempBuffer = new byte[bufferSize];
|
|
||||||
if (mRecorder.getState() != AudioRecord.STATE_INITIALIZED) {
|
|
||||||
stopRecord();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
mRecorder.startRecording();
|
|
||||||
while (isStart) {
|
|
||||||
if (null != mRecorder) {
|
|
||||||
bytesRecord = mRecorder.read(tempBuffer, 0, bufferSize);
|
|
||||||
if (bytesRecord == AudioRecord.ERROR_INVALID_OPERATION
|
|
||||||
|| bytesRecord == AudioRecord.ERROR_BAD_VALUE) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (bytesRecord != 0 && bytesRecord != -1) {
|
|
||||||
Log.i(TAG, "tempBuffer->" + Arrays.toString(tempBuffer));
|
|
||||||
MyApplication.getAppInstance().getpNative().pttNativeSendRecordCmd(tempBuffer);
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 启动录音
|
|
||||||
*/
|
|
||||||
public void startRecord() {
|
|
||||||
try {
|
|
||||||
stopRecord();
|
|
||||||
startThread();
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 停止录音
|
|
||||||
*/
|
|
||||||
public void stopRecord() {
|
|
||||||
try {
|
|
||||||
isStart = false;
|
|
||||||
destroyThread();
|
|
||||||
if (mRecorder != null) {
|
|
||||||
try {
|
|
||||||
if (mRecorder.getState() == AudioRecord.STATE_INITIALIZED) {
|
|
||||||
mRecorder.stop();
|
|
||||||
}
|
|
||||||
mRecorder.release();
|
|
||||||
mRecorder = null;
|
|
||||||
} catch (Exception e) { }
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,470 +0,0 @@
|
||||||
package com.example.kingway.ptt;
|
|
||||||
|
|
||||||
import android.util.Log;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**解析接收到的AT指令*/
|
|
||||||
public class CallBackResolution {
|
|
||||||
private static String TAG = "CallBackResolution";
|
|
||||||
private static String indexGroupId = "";//用户所在群组Id
|
|
||||||
private static String indexGroupName = "";//用户所在群组名称
|
|
||||||
private static List<GroupMemberInfoDto> memberInfoDtos = new ArrayList<>();
|
|
||||||
private static int speckType = 1;
|
|
||||||
private static String talkId = "";
|
|
||||||
private static String talkName = "";
|
|
||||||
private static ArrayList<GroupInfoDto> groupInfos = new ArrayList<>();
|
|
||||||
private static String oldLoginSate = "00";
|
|
||||||
private static int logNum = 0;
|
|
||||||
private static long offlineTime = 0;
|
|
||||||
private static boolean loginState = false;
|
|
||||||
|
|
||||||
public static void at_OEM_AT_cb(List<String> lisHex){
|
|
||||||
if (lisHex.size() == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
String ngx = "\\u";
|
|
||||||
try {
|
|
||||||
Log.i(TAG, "lisHex-->" + lisHex.toString());
|
|
||||||
String firstHex = lisHex.get(0);
|
|
||||||
String errorNum = lisHex.get(1);
|
|
||||||
Log.d(TAG, "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);
|
|
||||||
CallBackUtil.callBackLoinState(errorNum, loginId);
|
|
||||||
loginState = isLoginConflict(errorNum);
|
|
||||||
CallBackUtil.callBackLoginConflict(loginState);
|
|
||||||
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"://发起讲话返回状态
|
|
||||||
Log.d(TAG, "at_OEM_AT_cb ptt speak...");
|
|
||||||
CallBackUtil.callBackSpeakPlaySuccess(errorNum.equals("00")? true : false);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "0c"://结束讲话返回状态
|
|
||||||
Log.d(TAG, "at_OEM_AT_cb speak end...");
|
|
||||||
CallBackUtil.callBackSpeakReleaseSuccess(errorNum.equals("00")? true : false);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "80"://返回群组信息
|
|
||||||
Log.d(TAG, "at_OEM_AT_cb get group info: " + lisHex);
|
|
||||||
String strGroup = "";
|
|
||||||
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 += ngx + lisHex.get(i + numB) + "" + lisHex.get(i + numB - 1);
|
|
||||||
} else {
|
|
||||||
strGroup += ngx + lisHex.get(i + numB + 1) + "" + lisHex.get(i + numB);
|
|
||||||
numB = numB + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
} finally {
|
|
||||||
String tempTxt = "";
|
|
||||||
tempTxt = unicodeToString(strGroup);
|
|
||||||
Log.i(TAG, " 组名称 " + tempTxt);
|
|
||||||
CallBackUtil.callBackGroupInfo(groupId, tempTxt, groupNo, groupCount);
|
|
||||||
Log.d(TAG, "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.add(groupInfo);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "81"://返回群成员信息
|
|
||||||
String groupMemberName = "";
|
|
||||||
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 += ngx + lisHex.get(i + numB) + "" + lisHex.get(i + numB - 1);
|
|
||||||
} else {
|
|
||||||
groupMemberName += ngx + lisHex.get(i + numB + 1) + "" + lisHex.get(i + numB);
|
|
||||||
numB = numB + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
} finally {
|
|
||||||
String tempTxt = unicodeToString(groupMemberName);
|
|
||||||
GroupMemberInfoDto infoDto = new GroupMemberInfoDto(memberId, tempTxt, status, memberNo, haveVideo);
|
|
||||||
if (memberNo == 1) memberInfoDtos.clear();
|
|
||||||
memberInfoDtos.add(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<String> 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) {
|
|
||||||
e.printStackTrace();
|
|
||||||
} finally {
|
|
||||||
String tempTxt = "";
|
|
||||||
for (int i = 0; i < stringsSpker.size(); i++) {
|
|
||||||
tempTxt += unicode(stringsSpker.get(i));
|
|
||||||
}
|
|
||||||
talkName = tempTxt;
|
|
||||||
}
|
|
||||||
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<String> 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) {
|
|
||||||
e.printStackTrace();
|
|
||||||
} finally {
|
|
||||||
String tempTxt = "";
|
|
||||||
for (int i = 0; i < stringsTalk.size(); i++) {
|
|
||||||
tempTxt += unicode(stringsTalk.get(i));
|
|
||||||
}
|
|
||||||
Log.i(TAG, " 临时讲话 信息通知的指令 " + tempTxt);
|
|
||||||
CallBackUtil.callBackTempCallNotify(tempTxt);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "86"://用户进入群组信息通知
|
|
||||||
String userGroupId = lisHex.get(2) + "" + lisHex.get(3) + "" + lisHex.get(4) + "" + lisHex.get(5);
|
|
||||||
List<String> 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) {
|
|
||||||
e.printStackTrace();
|
|
||||||
} finally {
|
|
||||||
String userGroupName = "";
|
|
||||||
for (int i = 0; i < stringsJumpGroup.size(); i++) {
|
|
||||||
userGroupName += unicode(stringsJumpGroup.get(i));
|
|
||||||
}
|
|
||||||
Log.i(TAG,"进入群组 " + 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) {
|
|
||||||
Log.e(TAG, "获取定位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<String> 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);
|
|
||||||
Log.i(TAG, "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){
|
|
||||||
Log.e(TAG,"at_OEM_AT_cb Exception " + e.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isLoginConflict(String state) {
|
|
||||||
if (oldLoginSate.equals("00") && state.equals("01")) {
|
|
||||||
logNum++;
|
|
||||||
oldLoginSate = state;
|
|
||||||
} else if (oldLoginSate.equals("01") && state.equals("00")) {
|
|
||||||
logNum++;
|
|
||||||
oldLoginSate = state;
|
|
||||||
}
|
|
||||||
if (1 == logNum) {
|
|
||||||
offlineTime = System.currentTimeMillis();
|
|
||||||
}
|
|
||||||
if (logNum > 4) {
|
|
||||||
if (System.currentTimeMillis() - offlineTime >= 60 * 1000) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void at_Play_TTS_cb(String sData){
|
|
||||||
try {
|
|
||||||
String[] txt = sData.split("\"");
|
|
||||||
String data = txt[1];
|
|
||||||
String ngx = "\\u";
|
|
||||||
List<String> 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) {
|
|
||||||
e.printStackTrace();
|
|
||||||
} finally {
|
|
||||||
String tempTxt = "";
|
|
||||||
for (int k = 0; k < strings.size(); k++) {
|
|
||||||
tempTxt += unicode(strings.get(k));
|
|
||||||
}
|
|
||||||
if (tempTxt.contains("已登录")) {
|
|
||||||
String showName = tempTxt.substring(3, tempTxt.length());
|
|
||||||
CallBackUtil.callBackLogin(true, showName);
|
|
||||||
java.util.concurrent.ScheduledExecutorService executor = java.util.concurrent.Executors.newScheduledThreadPool(1);
|
|
||||||
Runnable task = () -> {
|
|
||||||
SendAtUtil.sendUDP();
|
|
||||||
SendAtUtil.sendTCP();
|
|
||||||
};
|
|
||||||
executor.scheduleAtFixedRate(task, 0, 40, java.util.concurrent.TimeUnit.SECONDS);
|
|
||||||
} else if (tempTxt.contains("账号或密码错误")) {
|
|
||||||
CallBackUtil.callBackLogin(false, "");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static String unicode(String str) {
|
|
||||||
java.util.regex.Pattern pattern = java.util.regex.Pattern.compile("(\\\\u(\\p{XDigit}{4}))");
|
|
||||||
java.util.regex.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);
|
|
||||||
String s1 = s.substring(2, 4) + "00";
|
|
||||||
String s2 = s.substring(4);
|
|
||||||
int n = Integer.valueOf(s1, 16) + Integer.valueOf(s2, 16);
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,263 +0,0 @@
|
||||||
package com.example.kingway.ptt;
|
|
||||||
|
|
||||||
import android.os.Handler;
|
|
||||||
import android.util.Log;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**指令返回类
|
|
||||||
* 说明:操作UI请在主线程进行
|
|
||||||
* */
|
|
||||||
public class CallBackUtil {
|
|
||||||
private static String TAG = "TYT_CallBackUtil";
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 返回登录
|
|
||||||
* loginState 是否登录成功
|
|
||||||
* name 用户名
|
|
||||||
*/
|
|
||||||
public static void callBackLogin(boolean loginState, String name) {
|
|
||||||
Log.i(TAG, "callBackLogin loginState " + loginState + " name " + name);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 登录状态通知
|
|
||||||
* state
|
|
||||||
* 0 : 离线
|
|
||||||
* 1 : 登陆中
|
|
||||||
* 2 : 登陆成功
|
|
||||||
* 3 : 注销中
|
|
||||||
* 说明:第一次登录 通知状态 先01再02,
|
|
||||||
* 离线再上线 通知状态 先00再01
|
|
||||||
* <p>
|
|
||||||
* loginId 用户ID,登录成功id有效
|
|
||||||
*/
|
|
||||||
public static void callBackLoinState(String state, String loginId) {
|
|
||||||
Log.d(TAG, "callBackLoinState state: " + state + ", loginId: " + loginId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 判断账号登录是否冲突
|
|
||||||
* true:登录冲突
|
|
||||||
* false:登录不冲突
|
|
||||||
* */
|
|
||||||
public static void callBackLoginConflict(boolean loginConflict) {
|
|
||||||
Log.d(TAG, "callBackLoginConflict: " + loginConflict);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 登录账号有视频功能通知
|
|
||||||
*/
|
|
||||||
public static boolean callBackHaveVideo() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 查询群组返回
|
|
||||||
* state true-成功, false-失败
|
|
||||||
*/
|
|
||||||
public static void callBackOnGroupSuccess(boolean state) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 查询群组成员返回
|
|
||||||
* state true-成功, false-失败
|
|
||||||
*/
|
|
||||||
public static void callBackOnMemberSuccess(boolean state, List<GroupMemberInfoDto> dtos) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 应用发起讲话返回
|
|
||||||
* state true-成功, false-失败
|
|
||||||
*/
|
|
||||||
public static void callBackSpeakPlaySuccess(boolean state) {
|
|
||||||
Log.d(TAG, "callBackSpeakPlaySuccess state: " + state);
|
|
||||||
if (state) AudioRecordManager.getInstance().startRecord();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 应用结束讲话返回
|
|
||||||
* state true-成功, false-失败
|
|
||||||
*/
|
|
||||||
public static void callBackSpeakReleaseSuccess(boolean state) {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 返回群组信息
|
|
||||||
* groupId 群组ID
|
|
||||||
* groupName 群组名称
|
|
||||||
* groupNo 表示此组为第几个组
|
|
||||||
* groupCount 表示此组用户成员数
|
|
||||||
*/
|
|
||||||
public static void callBackGroupInfo(String groupId, String groupName, int groupNo, int groupCount) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 返回群组集合信息
|
|
||||||
* GroupInfoDto 群组信息
|
|
||||||
* */
|
|
||||||
public static void callBackGroupInfo (ArrayList < GroupInfoDto > groupInfoDtos) {
|
|
||||||
for (GroupInfoDto groupInfoDto : groupInfoDtos) {
|
|
||||||
Log.d(TAG, "callBackGroupInfo: " + groupInfoDto.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 讲话用户信息通知
|
|
||||||
* speckType 讲话状态: 0 : 表示自己无法讲话; 1 : 表示自己可以中断讲话人的讲话,可以进行强插讲话
|
|
||||||
* talkId 讲话用户ID
|
|
||||||
* talkName 讲话用户名字
|
|
||||||
* */
|
|
||||||
public static void callBackNotifySpker ( int speckType, String groupId, String
|
|
||||||
groupName, String talkId, String talkName){
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 对方讲话通知
|
|
||||||
* playStatus 1表示开始讲话, 0 表示结束讲话
|
|
||||||
* speckType 自己讲话状态: 0 : 表示自己无法讲话; 1 : 表示自己可以中断讲话人的讲话,可以进行强插讲话
|
|
||||||
* groupId 所在群组Id
|
|
||||||
* groupName 所在群组名称
|
|
||||||
* talkId 讲话用户Id
|
|
||||||
* talkName 讲话用户名称
|
|
||||||
* */
|
|
||||||
public static void callBackNotifyPlayStatus ( int playStatus, int speckType, String
|
|
||||||
groupId, String groupName, String talkId, String talkName){
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*临时呼叫信息通知
|
|
||||||
* tempCallNotify:
|
|
||||||
* 临时呼叫xxx:表示被单呼,呼叫名:xxx
|
|
||||||
* 退出临时呼叫:表示退出单呼
|
|
||||||
* 呼叫成功:表示单呼成功
|
|
||||||
* 呼叫失败:表示单呼失败
|
|
||||||
* */
|
|
||||||
public static void callBackTempCallNotify (String tempCallNotify){
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 用户进入群组信息通知
|
|
||||||
* userGroupId 用户所在的群组ID
|
|
||||||
* userGroupName 用户所在的群组名称
|
|
||||||
* */
|
|
||||||
public static void callBackNotifyUpdateGroup (String userGroupId, String userGroupName){
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 用户定位信息通知
|
|
||||||
* status 0:获取成功, 1:获取失败
|
|
||||||
* userId 用户ID
|
|
||||||
* latitude 纬度
|
|
||||||
* longitude 经度
|
|
||||||
* time 时间
|
|
||||||
*/
|
|
||||||
public static void callBackNotifyLocationStatus ( int status, String userId,double latitude,
|
|
||||||
double longitude, String time){
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 用户上报定位信息通知
|
|
||||||
* status true:成功, false:失败
|
|
||||||
* */
|
|
||||||
public static void callBackNotifyUploadLocation ( boolean status){
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 空中写账号信息通知
|
|
||||||
* codeInfo ip=106.15.8.60;id=Android3;gps=1;inv=1
|
|
||||||
* ip=106.15.8.60 用户ip:106.15.8.60
|
|
||||||
* id=Android3 用户账号:Android3
|
|
||||||
* gps=1:1勾选定位,0不勾选定位
|
|
||||||
* inv=1:1勾选单呼,0不勾选单呼
|
|
||||||
* */
|
|
||||||
public static void callBackNotifyCodeInfo (String codeInfo){
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 空中写密码信息通知
|
|
||||||
* pwdInfo age=123456;pwd=111111
|
|
||||||
* age=123456 经销商密码:123456
|
|
||||||
* pwd=111111 账号密码:111111
|
|
||||||
* */
|
|
||||||
public static void callBackNotifyPwdInfo (String pwdInfo){
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 进入写码模式通知
|
|
||||||
* status true:成功,false:失败
|
|
||||||
* */
|
|
||||||
public static void callBackNotifyCodeOpenStatus ( boolean status){
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 退出写码模式通知
|
|
||||||
* status true:成功,false:失败
|
|
||||||
* */
|
|
||||||
public static void callBackNotifyCodeExiteStatus ( boolean status){
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 上传经销商密码通知
|
|
||||||
* status true:成功,false:失败
|
|
||||||
* */
|
|
||||||
public static void callBackNotifyUploadAgentPwd ( boolean status){
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 上报本机告警信号通知
|
|
||||||
* status true:成功,false:失败
|
|
||||||
* */
|
|
||||||
public static void callBackRequestReportAlarm ( boolean status){
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 停止本机告警信号通知
|
|
||||||
* status true:成功,false:失败
|
|
||||||
* */
|
|
||||||
public static void callBackRequestStopAlarm ( boolean status){
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 收到群成员警告通知
|
|
||||||
* memberName 群成员名称
|
|
||||||
* latitude 纬度
|
|
||||||
* longitude 经度
|
|
||||||
* */
|
|
||||||
public static void callBackEnotifyAlarm (String memberName,double latitude, double longitude){
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 收到群成员停止警告通知
|
|
||||||
* */
|
|
||||||
public static void callBackEnotifyStopAlarm () {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 上传NFC信息通知
|
|
||||||
* status true:成功,false:失败
|
|
||||||
* */
|
|
||||||
public static void callBackRequestNfc ( boolean status){
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,69 +0,0 @@
|
||||||
package com.example.kingway.ptt;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <pre>
|
|
||||||
* desc : 组
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
|
|
||||||
public class GroupInfoDto {
|
|
||||||
|
|
||||||
|
|
||||||
private int groupNo; //表示此组为第一个组
|
|
||||||
private int groupCount; //表示此组有 3 个用户成员
|
|
||||||
private String groupId;
|
|
||||||
private String groupName;
|
|
||||||
|
|
||||||
public GroupInfoDto() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public GroupInfoDto(String groupId, String groupName, int groupNo, int groupCount) {
|
|
||||||
this.groupId = groupId;
|
|
||||||
this.groupName = groupName;
|
|
||||||
this.groupNo = groupNo;
|
|
||||||
this.groupCount = groupCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getGroupNo() {
|
|
||||||
return groupNo;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setGroupNo(int groupNo) {
|
|
||||||
this.groupNo = groupNo;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getGroupCount() {
|
|
||||||
return groupCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setGroupCount(int groupCount) {
|
|
||||||
this.groupCount = groupCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getGroupId() {
|
|
||||||
return groupId == null ? "" : groupId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setGroupId(String groupId) {
|
|
||||||
this.groupId = groupId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getGroupName() {
|
|
||||||
return groupName == null ? "" : groupName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setGroupName(String groupName) {
|
|
||||||
this.groupName = groupName;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "GroupInfoDto{" +
|
|
||||||
"groupNo=" + groupNo +
|
|
||||||
", groupCount=" + groupCount +
|
|
||||||
", groupId='" + groupId + '\'' +
|
|
||||||
", groupName='" + groupName + '\'' +
|
|
||||||
'}';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,80 +0,0 @@
|
||||||
package com.example.kingway.ptt;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <pre>
|
|
||||||
* desc : 组 成员
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
|
|
||||||
public class GroupMemberInfoDto implements Serializable {
|
|
||||||
private static final long serialVersionUID = 1L;
|
|
||||||
|
|
||||||
private int memberNo; //表示此成员为第几个用户
|
|
||||||
private String memberId;
|
|
||||||
private String gmemberName;
|
|
||||||
private String status;
|
|
||||||
private boolean haveVideo;//此成员是否有视频功能
|
|
||||||
|
|
||||||
public GroupMemberInfoDto(){}
|
|
||||||
|
|
||||||
public GroupMemberInfoDto(String memberId, String gmemberName, String status, int memberNo, boolean haveVideo) {
|
|
||||||
this.memberId = memberId;
|
|
||||||
this.gmemberName = gmemberName;
|
|
||||||
this.memberNo = memberNo;
|
|
||||||
this.status = status;
|
|
||||||
this.haveVideo = haveVideo;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getMemberNo() {
|
|
||||||
return memberNo;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setMemberNo(int memberNo) {
|
|
||||||
this.memberNo = memberNo;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getStatus() {
|
|
||||||
return status == null ? "" : status;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setStatus(String status) {
|
|
||||||
this.status = status;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getMemberId() {
|
|
||||||
return memberId == null ? "" : memberId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setMemberId(String memberId) {
|
|
||||||
this.memberId = memberId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getGmemberName() {
|
|
||||||
return gmemberName == null ? "" : gmemberName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setGmemberName(String gmemberName) {
|
|
||||||
this.gmemberName = gmemberName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isHaveVideo() {
|
|
||||||
return haveVideo;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setHaveVideo(boolean haveVideo) {
|
|
||||||
this.haveVideo = haveVideo;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "GroupMemberInfoDto{" +
|
|
||||||
"memberNo=" + memberNo +
|
|
||||||
", memberId='" + memberId + '\'' +
|
|
||||||
", gmemberName='" + gmemberName + '\'' +
|
|
||||||
", status='" + status + '\'' +
|
|
||||||
", haveVideo=" + haveVideo +
|
|
||||||
'}';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,45 +0,0 @@
|
||||||
package com.example.kingway.ptt;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
public class MyApplication {
|
|
||||||
private static Context mContext;
|
|
||||||
public static String pageName = "";
|
|
||||||
public static pttNative pNative;
|
|
||||||
private static MyApplication mAppInstance = new MyApplication();
|
|
||||||
|
|
||||||
public static void init(Context context) {
|
|
||||||
if (mContext != null) return;
|
|
||||||
mContext = context.getApplicationContext();
|
|
||||||
pageName = mContext.getPackageName();
|
|
||||||
initPTT();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static MyApplication getAppInstance() {
|
|
||||||
if (pNative == null && mContext != null) {
|
|
||||||
initPTT();
|
|
||||||
}
|
|
||||||
return mAppInstance;
|
|
||||||
}
|
|
||||||
|
|
||||||
public pttNative getpNative() {
|
|
||||||
return pNative;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void initPTT() {
|
|
||||||
if (pNative != null) return;
|
|
||||||
pNative = new pttNative();
|
|
||||||
|
|
||||||
pNative.pttNativeSetPackageName(pageName);
|
|
||||||
pNative.pttNativeSetAtCallback();
|
|
||||||
pNative.pttNativeSetPlayCallback();
|
|
||||||
pNative.pttNativeSetPalyStartCallback();
|
|
||||||
pNative.pttNativeSetPlayStopCallback();
|
|
||||||
pNative.pttNativeSetRecordStartCallback();
|
|
||||||
pNative.pttNativeSetRecordStopCallback();
|
|
||||||
pNative.pttNativeSetTTSCallback();
|
|
||||||
|
|
||||||
pNative.pttNativePocTaskStart();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,96 +0,0 @@
|
||||||
package com.example.kingway.ptt;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 指令发送类
|
|
||||||
* */
|
|
||||||
public class SendAtUtil {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 账号登录接口
|
|
||||||
* account 账号
|
|
||||||
* pwd 密码
|
|
||||||
* ip 服务器ip
|
|
||||||
* location 是否开启定位 1-开启,0关闭
|
|
||||||
* tempCall 是否开启单呼 1开启单呼,0关闭单呼
|
|
||||||
* */
|
|
||||||
public static void toLogin(String account, String pwd, String ip, int location, int tempCall){
|
|
||||||
String ipHex = str2HexStr("ip=" + ip + ";");
|
|
||||||
String accountHex = str2HexStr("id=" + account + ";");
|
|
||||||
String pwdHex = str2HexStr("pwd=" + pwd + ";");
|
|
||||||
String locationHex = str2HexStr("gps=" + location + ";");
|
|
||||||
String tempCallHex = str2HexStr("inv=" + tempCall + ";");
|
|
||||||
String loginAt = "010000" + ipHex + "" + accountHex + "" + pwdHex + locationHex + tempCallHex + "\r\n";
|
|
||||||
MyApplication.getAppInstance().getpNative().pttNativeSendAtCmd(loginAt);
|
|
||||||
MyApplication.getAppInstance().getpNative().pttNativeSendAtCmd("00000000\r\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 按下PTT开始呼叫
|
|
||||||
* */
|
|
||||||
public static void speakPlay(){
|
|
||||||
MyApplication.getAppInstance().getpNative().pttNativeSendAtCmd("0B0000");
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 松开PTT结束呼叫*/
|
|
||||||
public static void speakRelease(){
|
|
||||||
AudioRecordManager.getInstance().stopRecord();
|
|
||||||
MyApplication.getAppInstance().getpNative().pttNativeSendAtCmd("0C0000");
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 发送TCP包 登录成功后需要定时发送TCP包*/
|
|
||||||
public static void sendTCP(){
|
|
||||||
MyApplication.getAppInstance().getpNative().pttNativeSendAtCmd("570000\r\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 发送UDP包 登录后需要定时发送UDP包*/
|
|
||||||
public static void sendUDP(){
|
|
||||||
MyApplication.getAppInstance().getpNative().pttNativeSendAtCmd("580000\r\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 请求群组信息 */
|
|
||||||
public static void getGroupInfo(){
|
|
||||||
MyApplication.getAppInstance().getpNative().pttNativeSendAtCmd("0D0000");
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 请求群组成员信息
|
|
||||||
* groupId 十六进制群组Id
|
|
||||||
* */
|
|
||||||
public static void getGroupMemberInfo(String groupId){
|
|
||||||
MyApplication.getAppInstance().getpNative().pttNativeSendAtCmd("0E0000" + groupId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 切换群组
|
|
||||||
* groupId 十六进制群组Id
|
|
||||||
* */
|
|
||||||
public static void jumpGroup(String groupId){
|
|
||||||
MyApplication.getAppInstance().getpNative().pttNativeSendAtCmd("090000" + groupId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 请求好友信息 */
|
|
||||||
public static void getFriendInfo(){
|
|
||||||
MyApplication.getAppInstance().getpNative().pttNativeSendAtCmd("0E000000000000\r\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 字符串转换成十六进制字符串
|
|
||||||
*
|
|
||||||
* @param str 待转换的ASCII字符串
|
|
||||||
* @return String
|
|
||||||
*/
|
|
||||||
public static String str2HexStr(String str) {
|
|
||||||
|
|
||||||
char[] chars = "0123456789ABCDEF".toCharArray();
|
|
||||||
StringBuilder sb = new StringBuilder("");
|
|
||||||
byte[] bs = str.getBytes();
|
|
||||||
int bit;
|
|
||||||
|
|
||||||
for (int i = 0; i < bs.length; i++) {
|
|
||||||
bit = (bs[i] & 0x0f0) >> 4;
|
|
||||||
sb.append(chars[bit]);
|
|
||||||
bit = bs[i] & 0x0f;
|
|
||||||
sb.append(chars[bit]);
|
|
||||||
}
|
|
||||||
return sb.toString().trim();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,83 +0,0 @@
|
||||||
package com.example.kingway.ptt;
|
|
||||||
|
|
||||||
import android.graphics.Color;
|
|
||||||
|
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
|
||||||
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.nio.ByteOrder;
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
import android.view.MotionEvent;
|
|
||||||
import android.view.View;
|
|
||||||
import android.widget.Button;
|
|
||||||
|
|
||||||
import com.hjq.permissions.Permission;
|
|
||||||
import com.hjq.permissions.XXPermissions;
|
|
||||||
import com.stand.standapp.R;
|
|
||||||
|
|
||||||
public class pttActivity extends AppCompatActivity {
|
|
||||||
private Button pttBtn;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
setContentView(R.layout.activity_ptt);
|
|
||||||
pttBtn = (Button) findViewById(R.id.pttBtn);
|
|
||||||
|
|
||||||
// 初始化 PTT
|
|
||||||
MyApplication.init(this);
|
|
||||||
|
|
||||||
XXPermissions.with(this)
|
|
||||||
.permission(Permission.RECORD_AUDIO)
|
|
||||||
.request(null);
|
|
||||||
pttBtn.setOnTouchListener(new View.OnTouchListener() {
|
|
||||||
@Override
|
|
||||||
public boolean onTouch(View view, MotionEvent motionEvent) {
|
|
||||||
if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) {
|
|
||||||
pttBtn.setBackgroundColor(Color.RED);
|
|
||||||
SendAtUtil.speakPlay();
|
|
||||||
} else if (motionEvent.getAction() == MotionEvent.ACTION_UP) {
|
|
||||||
pttBtn.setBackgroundColor(Color.BLUE);
|
|
||||||
SendAtUtil.speakRelease();
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
findViewById(R.id.login).setOnClickListener(new View.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(View v) {
|
|
||||||
SendAtUtil.toLogin("test012", "123456", "106.15.8.60", 1, 1);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/** 获取群组 */
|
|
||||||
findViewById(R.id.button2).setOnClickListener(new View.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(View v) {
|
|
||||||
SendAtUtil.getGroupInfo();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/** 获取群成员 */
|
|
||||||
findViewById(R.id.button3).setOnClickListener(new View.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(View v) {
|
|
||||||
SendAtUtil.getGroupMemberInfo("0002b199");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/** 切换群组 */
|
|
||||||
findViewById(R.id.btnJumpGroup).setOnClickListener(new View.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(View v) {
|
|
||||||
SendAtUtil.jumpGroup("0002b199");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,208 +0,0 @@
|
||||||
package com.example.kingway.ptt;
|
|
||||||
|
|
||||||
import android.media.AudioAttributes;
|
|
||||||
import android.media.AudioFormat;
|
|
||||||
import android.media.AudioManager;
|
|
||||||
import android.media.AudioRecord;
|
|
||||||
import android.media.AudioTrack;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class pttNative {
|
|
||||||
private static String TAG = "pttNative";
|
|
||||||
static {
|
|
||||||
System.loadLibrary("ptt");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static char Char2ASCII(char c)
|
|
||||||
{
|
|
||||||
return (char) (c <= 9 ? (c+0x30) : (c+0x37));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static char Ascii2Char(char nib_h, char nib_l)
|
|
||||||
{
|
|
||||||
nib_h = (nib_h <= '9')?((char)(nib_h-0x30)):(((nib_h<='F')?((char)(nib_h-0x37)):((char)(nib_h-0x57))));
|
|
||||||
nib_l = (nib_l <= '9')?((char)(nib_l-0x30)):(((nib_l<='F')?((char)(nib_l-0x37)):((char)(nib_l-0x57))));
|
|
||||||
return (char)((nib_h<<4)+nib_l);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//Callback Function for Recv the message from POC Lib
|
|
||||||
public void OEM_AT_cb(String sData)
|
|
||||||
{
|
|
||||||
Log.e(TAG,"Poc2 AT Callback: " + sData);
|
|
||||||
int len, i;
|
|
||||||
char[] bytes = sData.toCharArray();
|
|
||||||
|
|
||||||
List<String> lisHex = new ArrayList<>();
|
|
||||||
if (bytes.length > 5 && bytes[0] == '+' && bytes[1] == 'P' && bytes[2] == 'O' && bytes[3] == 'C' && bytes[4] == ':') {
|
|
||||||
len = bytes.length - 5;
|
|
||||||
if (len >= 2) {
|
|
||||||
for (i = 0; i < len - 1; i = i + 2) {
|
|
||||||
String temp = Integer.toHexString(Ascii2Char(bytes[i + 5], bytes[i + 6]));
|
|
||||||
|
|
||||||
lisHex.add(temp.length() == 1 ? "0" + temp : temp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
CallBackResolution.at_OEM_AT_cb(lisHex);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OEM_Play_cb(byte[] bytes){
|
|
||||||
if (mAudioTrack == null) {
|
|
||||||
initAudioTrack();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mAudioTrack.getPlayState() != AudioTrack.PLAYSTATE_PLAYING){
|
|
||||||
mAudioTrack.play();
|
|
||||||
}
|
|
||||||
mAudioTrack.write(bytes, 0, bytes.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OEM_Play_Start_cb()
|
|
||||||
{
|
|
||||||
Log.e("Play Start!!!!\r\n","");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OEM_Play_Stop_cb()
|
|
||||||
{
|
|
||||||
Log.e("Play Stop!!!!\r\n","");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OEM_Record_Start_cb()
|
|
||||||
{
|
|
||||||
Log.e("Record Start!!!!\r\n","");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OEM_Record_Stop_cb()
|
|
||||||
{
|
|
||||||
Log.e("Record Stop!!!!\r\n","");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OEM_Play_TTS_cb(String sData)
|
|
||||||
{
|
|
||||||
Log.e(TAG , "Play TTS " + sData);
|
|
||||||
CallBackResolution.at_Play_TTS_cb(sData);
|
|
||||||
}
|
|
||||||
|
|
||||||
//Define the Function of the AT Command send
|
|
||||||
private native void OEM_AT_Send(String strAtCmd);
|
|
||||||
private native int OEM_Set_AT_CallBack(String cb);
|
|
||||||
|
|
||||||
private native void OEM_Push_Record_Data(byte[] data);
|
|
||||||
private native int OEM_Set_Play_CallBack(String cb);
|
|
||||||
|
|
||||||
private native int OEM_Set_Play_Start_Callback(String cb);
|
|
||||||
private native int OEM_Set_Play_Stop_Callback(String cb);
|
|
||||||
|
|
||||||
private native int OEM_Set_Record_Start_Callback(String cb);
|
|
||||||
private native int OEM_Set_Record_Stop_Callback(String cb);
|
|
||||||
|
|
||||||
private native int OEM_Set_TTS_Callback(String cb);
|
|
||||||
|
|
||||||
private native void OEM_Poc_Task_Start();
|
|
||||||
|
|
||||||
private native int OEM_Set_Package_Name(String name);
|
|
||||||
|
|
||||||
public void pttNativeSendRecordCmd(byte[] data) {
|
|
||||||
OEM_Push_Record_Data(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void pttNativeSendAtCmd(String strAtCmd)
|
|
||||||
{
|
|
||||||
Log.i(TAG,"strAtCmd " + strAtCmd);
|
|
||||||
OEM_AT_Send(strAtCmd);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void pttNativePocTaskStart()
|
|
||||||
{
|
|
||||||
OEM_Poc_Task_Start();
|
|
||||||
}
|
|
||||||
|
|
||||||
public int pttNativeSetAtCallback()
|
|
||||||
{
|
|
||||||
return OEM_Set_AT_CallBack("OEM_AT_cb");
|
|
||||||
}
|
|
||||||
|
|
||||||
public int pttNativeSetPlayCallback()
|
|
||||||
{
|
|
||||||
return OEM_Set_Play_CallBack("OEM_Play_cb");
|
|
||||||
}
|
|
||||||
|
|
||||||
public int pttNativeSetPalyStartCallback()
|
|
||||||
{
|
|
||||||
return OEM_Set_Play_Start_Callback("OEM_Play_Start_cb");
|
|
||||||
}
|
|
||||||
|
|
||||||
public int pttNativeSetPlayStopCallback()
|
|
||||||
{
|
|
||||||
return OEM_Set_Play_Stop_Callback("OEM_Play_Stop_cb");
|
|
||||||
}
|
|
||||||
|
|
||||||
public int pttNativeSetRecordStartCallback()
|
|
||||||
{
|
|
||||||
return OEM_Set_Record_Start_Callback("OEM_Record_Start_cb");
|
|
||||||
}
|
|
||||||
|
|
||||||
public int pttNativeSetRecordStopCallback()
|
|
||||||
{
|
|
||||||
return OEM_Set_Record_Stop_Callback("OEM_Record_Stop_cb");
|
|
||||||
}
|
|
||||||
|
|
||||||
public int pttNativeSetTTSCallback()
|
|
||||||
{
|
|
||||||
return OEM_Set_TTS_Callback("OEM_Play_TTS_cb");
|
|
||||||
}
|
|
||||||
|
|
||||||
public int pttNativeSetPackageName(String name)
|
|
||||||
{
|
|
||||||
return OEM_Set_Package_Name(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
public pttNative() {
|
|
||||||
initAudioTrack();
|
|
||||||
}
|
|
||||||
|
|
||||||
private AudioTrack mAudioTrack;
|
|
||||||
public void initAudioTrack() {
|
|
||||||
int mAudioMinBufSize = AudioTrack.getMinBufferSize(8000, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT);
|
|
||||||
|
|
||||||
AudioAttributes audioAttributes = new AudioAttributes.Builder()
|
|
||||||
.setUsage(AudioAttributes.USAGE_MEDIA)
|
|
||||||
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
|
|
||||||
.setLegacyStreamType(AudioManager.STREAM_MUSIC)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
AudioFormat audioFormat = new AudioFormat.Builder()
|
|
||||||
.setSampleRate(8000)
|
|
||||||
.setChannelMask(AudioFormat.CHANNEL_OUT_MONO)
|
|
||||||
.setEncoding(AudioFormat.ENCODING_PCM_16BIT)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
mAudioTrack = new AudioTrack(
|
|
||||||
audioAttributes,
|
|
||||||
audioFormat,
|
|
||||||
mAudioMinBufSize * 4,
|
|
||||||
AudioTrack.MODE_STREAM,
|
|
||||||
AudioManager.AUDIO_SESSION_ID_GENERATE
|
|
||||||
);
|
|
||||||
mAudioTrack.setVolume(1.0f);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public void destory() {
|
|
||||||
if (mAudioTrack != null) {
|
|
||||||
try {
|
|
||||||
if (mAudioTrack.getState() == AudioRecord.STATE_INITIALIZED) {
|
|
||||||
mAudioTrack.stop();
|
|
||||||
}
|
|
||||||
mAudioTrack.release();
|
|
||||||
mAudioTrack = null;
|
|
||||||
} catch (Exception e) {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -27,10 +27,10 @@ class AndroidInterface(private val activity: MainActivity) {
|
||||||
Toast.makeText(activity, "WebView 未就绪", Toast.LENGTH_SHORT).show()
|
Toast.makeText(activity, "WebView 未就绪", Toast.LENGTH_SHORT).show()
|
||||||
return@post
|
return@post
|
||||||
}
|
}
|
||||||
|
|
||||||
val printAdapter = webView.createPrintDocumentAdapter("Document")
|
val printAdapter = webView.createPrintDocumentAdapter("Document")
|
||||||
val jobName = activity.getString(R.string.app_name) + " Print Job"
|
val jobName = activity.getString(R.string.app_name) + " Print Job"
|
||||||
|
|
||||||
printManager.print(jobName, printAdapter, PrintAttributes.Builder().build())
|
printManager.print(jobName, printAdapter, PrintAttributes.Builder().build())
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e("Print", "系统打印失败", e)
|
Log.e("Print", "系统打印失败", e)
|
||||||
|
|
@ -173,7 +173,7 @@ class AndroidInterface(private val activity: MainActivity) {
|
||||||
fun onPageFinished() {
|
fun onPageFinished() {
|
||||||
Handler(Looper.getMainLooper()).post {
|
Handler(Looper.getMainLooper()).post {
|
||||||
try {
|
try {
|
||||||
// activity.cancelTimeoutTimer()
|
activity.cancelTimeoutTimer()
|
||||||
activity.findViewById<android.view.View>(R.id.loading_layout)?.visibility = android.view.View.GONE
|
activity.findViewById<android.view.View>(R.id.loading_layout)?.visibility = android.view.View.GONE
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e("UI", "隐藏加载布局失败", e)
|
Log.e("UI", "隐藏加载布局失败", e)
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ class LoginActivity : AppCompatActivity() {
|
||||||
val etPassword = findViewById<EditText>(R.id.et_password)
|
val etPassword = findViewById<EditText>(R.id.et_password)
|
||||||
val cbRemember = findViewById<CheckBox>(R.id.cb_remember)
|
val cbRemember = findViewById<CheckBox>(R.id.cb_remember)
|
||||||
val btnLogin = findViewById<Button>(R.id.btn_login)
|
val btnLogin = findViewById<Button>(R.id.btn_login)
|
||||||
val btnPttExample = findViewById<Button>(R.id.btn_ptt_example)
|
// val btnPttExample = findViewById<Button>(R.id.btn_ptt_example)
|
||||||
val ivSettings = findViewById<ImageView>(R.id.iv_settings)
|
val ivSettings = findViewById<ImageView>(R.id.iv_settings)
|
||||||
|
|
||||||
// 1. 加载保存的状态和凭据
|
// 1. 加载保存的状态和凭据
|
||||||
|
|
@ -53,15 +53,15 @@ class LoginActivity : AppCompatActivity() {
|
||||||
performLogin(username, password, cbRemember.isChecked)
|
performLogin(username, password, cbRemember.isChecked)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. PTT 示例按钮点击事件
|
// // 4. PTT 示例按钮点击事件
|
||||||
btnPttExample.setOnClickListener {
|
// btnPttExample.setOnClickListener {
|
||||||
try {
|
// try {
|
||||||
val intent = Intent(this, com.example.kingway.ptt.pttActivity::class.java)
|
// val intent = Intent(this, com.example.kingway.ptt.pttActivity::class.java)
|
||||||
startActivity(intent)
|
// startActivity(intent)
|
||||||
} catch (e: Exception) {
|
// } catch (e: Exception) {
|
||||||
Toast.makeText(this, "打开 PTT 示例失败: ${e.message}", Toast.LENGTH_SHORT).show()
|
// Toast.makeText(this, "打开 PTT 示例失败: ${e.message}", Toast.LENGTH_SHORT).show()
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -138,12 +138,12 @@ class LoginActivity : AppCompatActivity() {
|
||||||
val container = LinearLayout(this)
|
val container = LinearLayout(this)
|
||||||
container.orientation = LinearLayout.VERTICAL
|
container.orientation = LinearLayout.VERTICAL
|
||||||
container.setPadding(60, 20, 60, 0)
|
container.setPadding(60, 20, 60, 0)
|
||||||
|
|
||||||
val editText = EditText(this)
|
val editText = EditText(this)
|
||||||
editText.setText(AppConfig.getServerUrl(this))
|
editText.setText(AppConfig.getServerUrl(this))
|
||||||
editText.hint = "例如: http://192.168.1.100:8080"
|
editText.hint = "例如: http://192.168.1.100:8080"
|
||||||
container.addView(editText)
|
container.addView(editText)
|
||||||
|
|
||||||
AlertDialog.Builder(this)
|
AlertDialog.Builder(this)
|
||||||
.setTitle("服务器地址配置")
|
.setTitle("服务器地址配置")
|
||||||
.setView(container)
|
.setView(container)
|
||||||
|
|
@ -162,9 +162,9 @@ class LoginActivity : AppCompatActivity() {
|
||||||
AlertDialog.Builder(this)
|
AlertDialog.Builder(this)
|
||||||
.setTitle("提示")
|
.setTitle("提示")
|
||||||
.setMessage("确定要退出应用吗?")
|
.setMessage("确定要退出应用吗?")
|
||||||
.setPositiveButton("确定") { _, _ ->
|
.setPositiveButton("确定") { _, _ ->
|
||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION")
|
||||||
super.onBackPressed()
|
super.onBackPressed()
|
||||||
}
|
}
|
||||||
.setNegativeButton("取消", null)
|
.setNegativeButton("取消", null)
|
||||||
.show()
|
.show()
|
||||||
|
|
|
||||||
|
|
@ -13,12 +13,12 @@ import com.stand.standapp.R
|
||||||
class MainActivity : AppCompatActivity() {
|
class MainActivity : AppCompatActivity() {
|
||||||
|
|
||||||
private var mAgentWeb: AgentWeb? = null
|
private var mAgentWeb: AgentWeb? = null
|
||||||
// private val mHandler = android.os.Handler(android.os.Looper.getMainLooper())
|
private val mHandler = android.os.Handler(android.os.Looper.getMainLooper())
|
||||||
// private val TIMEOUT_MS = 30000L
|
private val TIMEOUT_MS = 30000L
|
||||||
//
|
|
||||||
// private val timeoutRunnable = Runnable {
|
private val timeoutRunnable = Runnable {
|
||||||
// showErrorPage()
|
showErrorPage()
|
||||||
// }
|
}
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
@ -29,16 +29,15 @@ class MainActivity : AppCompatActivity() {
|
||||||
val container = findViewById<LinearLayout>(R.id.container)
|
val container = findViewById<LinearLayout>(R.id.container)
|
||||||
if (container == null) return
|
if (container == null) return
|
||||||
|
|
||||||
// findViewById<android.widget.Button>(R.id.btn_retry)?.setOnClickListener {
|
findViewById<android.widget.Button>(R.id.btn_retry)?.setOnClickListener {
|
||||||
// hideErrorPage()
|
hideErrorPage()
|
||||||
// mAgentWeb?.webCreator?.webView?.reload()
|
mAgentWeb?.webCreator?.webView?.reload()
|
||||||
// startTimeoutTimer()
|
startTimeoutTimer()
|
||||||
// }
|
}
|
||||||
|
|
||||||
// 从配置中获取服务器地址
|
// 从配置中获取服务器地址
|
||||||
val serverUrl = AppConfig.getServerUrl(this)
|
val serverUrl = AppConfig.getServerUrl(this)
|
||||||
val finalUrl = "$serverUrl/example/print_demo.html?t=\${System.currentTimeMillis()}"
|
val finalUrl = "$serverUrl/example/print_demo.html?t=${System.currentTimeMillis()}"
|
||||||
// val finalUrl = "$serverUrl/example/print_demo.html?t=${System.currentTimeMillis()}"
|
|
||||||
Log.d("MainActivity", "Loading URL: $finalUrl")
|
Log.d("MainActivity", "Loading URL: $finalUrl")
|
||||||
|
|
||||||
mAgentWeb = AgentWeb.with(this)
|
mAgentWeb = AgentWeb.with(this)
|
||||||
|
|
@ -49,7 +48,7 @@ class MainActivity : AppCompatActivity() {
|
||||||
.ready()
|
.ready()
|
||||||
.go(finalUrl)
|
.go(finalUrl)
|
||||||
|
|
||||||
// startTimeoutTimer()
|
startTimeoutTimer()
|
||||||
|
|
||||||
// 配置 WebView 属性以支持 SSE 和跨域
|
// 配置 WebView 属性以支持 SSE 和跨域
|
||||||
|
|
||||||
|
|
@ -88,7 +87,7 @@ class MainActivity : AppCompatActivity() {
|
||||||
fun showExitDialog() {
|
fun showExitDialog() {
|
||||||
// 调用 JS 函数
|
// 调用 JS 函数
|
||||||
mAgentWeb?.jsAccessEntrace?.quickCallJs("addExitText")
|
mAgentWeb?.jsAccessEntrace?.quickCallJs("addExitText")
|
||||||
|
|
||||||
androidx.appcompat.app.AlertDialog.Builder(this)
|
androidx.appcompat.app.AlertDialog.Builder(this)
|
||||||
.setTitle("提示")
|
.setTitle("提示")
|
||||||
.setMessage("确定要退出应用吗?")
|
.setMessage("确定要退出应用吗?")
|
||||||
|
|
@ -120,26 +119,26 @@ class MainActivity : AppCompatActivity() {
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
mAgentWeb?.webLifeCycle?.onDestroy()
|
mAgentWeb?.webLifeCycle?.onDestroy()
|
||||||
// cancelTimeoutTimer()
|
cancelTimeoutTimer()
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
// private fun startTimeoutTimer() {
|
private fun startTimeoutTimer() {
|
||||||
// cancelTimeoutTimer()
|
cancelTimeoutTimer()
|
||||||
// mHandler.postDelayed(timeoutRunnable, TIMEOUT_MS)
|
mHandler.postDelayed(timeoutRunnable, TIMEOUT_MS)
|
||||||
// }
|
}
|
||||||
//
|
|
||||||
// fun cancelTimeoutTimer() {
|
fun cancelTimeoutTimer() {
|
||||||
// mHandler.removeCallbacks(timeoutRunnable)
|
mHandler.removeCallbacks(timeoutRunnable)
|
||||||
// }
|
}
|
||||||
//
|
|
||||||
// private fun showErrorPage() {
|
private fun showErrorPage() {
|
||||||
// findViewById<android.view.View>(R.id.loading_layout)?.visibility = android.view.View.GONE
|
findViewById<android.view.View>(R.id.loading_layout)?.visibility = android.view.View.GONE
|
||||||
// findViewById<android.view.View>(R.id.error_layout)?.visibility = android.view.View.VISIBLE
|
findViewById<android.view.View>(R.id.error_layout)?.visibility = android.view.View.VISIBLE
|
||||||
// }
|
}
|
||||||
//
|
|
||||||
// private fun hideErrorPage() {
|
private fun hideErrorPage() {
|
||||||
// findViewById<android.view.View>(R.id.error_layout)?.visibility = android.view.View.GONE
|
findViewById<android.view.View>(R.id.error_layout)?.visibility = android.view.View.GONE
|
||||||
// findViewById<android.view.View>(R.id.loading_layout)?.visibility = android.view.View.VISIBLE
|
findViewById<android.view.View>(R.id.loading_layout)?.visibility = android.view.View.VISIBLE
|
||||||
// }
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Binary file not shown.
Binary file not shown.
|
|
@ -129,15 +129,6 @@
|
||||||
android:textColor="#FFFFFF"
|
android:textColor="#FFFFFF"
|
||||||
android:textSize="18sp" />
|
android:textSize="18sp" />
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/btn_ptt_example"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="50dp"
|
|
||||||
android:background="@drawable/shape_button_login"
|
|
||||||
android:text="PTT 示例"
|
|
||||||
android:textColor="#FFFFFF"
|
|
||||||
android:textSize="16sp" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,89 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:id="@+id/container"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent">
|
||||||
android:orientation="vertical">
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/container"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical" />
|
||||||
|
|
||||||
|
<!-- 加载等待页 -->
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/loading_layout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="#FFFFFF"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:visibility="visible">
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:layout_width="60dp"
|
||||||
|
android:layout_height="60dp"
|
||||||
|
android:indeterminateTint="#004A8F" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="20dp"
|
||||||
|
android:text="消防值守台系统加载中..."
|
||||||
|
android:textColor="#333333"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="10dp"
|
||||||
|
android:text="正在初始化智能调度引擎..."
|
||||||
|
android:textColor="#999999"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- 异常提示页 (超时或网络错误) -->
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/error_layout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="#F8F9FA"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:visibility="gone">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="100dp"
|
||||||
|
android:layout_height="100dp"
|
||||||
|
android:src="@android:drawable/stat_notify_error"
|
||||||
|
android:tint="#D62828" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="24dp"
|
||||||
|
android:text="系统响应超时"
|
||||||
|
android:textColor="#333333"
|
||||||
|
android:textSize="20sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="280dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="12dp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="检测到网络环境不稳定或服务器未响应,请检查网络连接后重试。"
|
||||||
|
android:textColor="#666666"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btn_retry"
|
||||||
|
android:layout_width="160dp"
|
||||||
|
android:layout_height="50dp"
|
||||||
|
android:layout_marginTop="30dp"
|
||||||
|
android:backgroundTint="#004A8F"
|
||||||
|
android:text="重新加载"
|
||||||
|
android:textColor="#FFFFFF" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
|
|
||||||
Binary file not shown.
Loading…
Reference in New Issue