This commit is contained in:
parent
722ece723f
commit
a957afd5d4
|
|
@ -17,7 +17,7 @@ android {
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
|
||||||
ndk {
|
ndk {
|
||||||
abiFilters.addAll(listOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64"))
|
abiFilters.addAll(listOf("armeabi-v7a", "arm64-v8a"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -45,6 +45,7 @@ android {
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
implementation("com.jakewharton.timber:timber:5.0.1")
|
||||||
implementation("io.github.justson:agentweb-core:v5.1.1-androidx")
|
implementation("io.github.justson:agentweb-core:v5.1.1-androidx")
|
||||||
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")
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@
|
||||||
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
|
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
|
android:name=".MyApplication"
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||||
android:fullBackupContent="@xml/backup_rules"
|
android:fullBackupContent="@xml/backup_rules"
|
||||||
|
|
|
||||||
|
|
@ -86,8 +86,9 @@
|
||||||
<p style="font-size: 12px; color: #666;">(适用于蓝牙/USB 小票机,传输原始 JSON)</p>
|
<p style="font-size: 12px; color: #666;">(适用于蓝牙/USB 小票机,传输原始 JSON)</p>
|
||||||
|
|
||||||
<button class="btn btn-orange" onclick="callUpdate()">3. 检查系统更新</button>
|
<button class="btn btn-orange" onclick="callUpdate()">3. 检查系统更新</button>
|
||||||
<button class="btn" style="background: #64748b;" onclick="callAbout()">4. 关于应用</button>
|
<button class="btn" style="background: #10B981;" onclick="callUploadLogs()">4. 上传系统日志</button>
|
||||||
<button class="btn btn-red" onclick="callExit()">5. 退出应用</button>
|
<button class="btn" style="background: #64748b;" onclick="callAbout()">5. 关于应用</button>
|
||||||
|
<button class="btn btn-red" onclick="callExit()">6. 退出应用</button>
|
||||||
|
|
||||||
<!-- WebSocket 控制区 -->
|
<!-- WebSocket 控制区 -->
|
||||||
<div class="ws-card">
|
<div class="ws-card">
|
||||||
|
|
@ -255,6 +256,15 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 上传日志
|
||||||
|
function callUploadLogs() {
|
||||||
|
if (window.android && window.android.uploadLogs) {
|
||||||
|
window.android.uploadLogs();
|
||||||
|
} else {
|
||||||
|
alert("上传日志接口不可用");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function callExit() {
|
function callExit() {
|
||||||
if (window.android && window.android.exitApp) {
|
if (window.android && window.android.exitApp) {
|
||||||
window.android.exitApp();
|
window.android.exitApp();
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
package com.example.kingway.ptt;
|
package com.example.kingway.ptt;
|
||||||
|
|
||||||
import android.util.Log;
|
import timber.log.Timber;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**解析接收到的AT指令*/
|
/**解析接收到的AT指令*/
|
||||||
public class CallBackResolution {
|
public class CallBackResolution {
|
||||||
private static String TAG = "CallBackResolution";
|
private static String TAG = "PTT_RX";
|
||||||
private static String indexGroupId = "";//用户所在群组Id
|
private static String indexGroupId = "";//用户所在群组Id
|
||||||
private static String indexGroupName = "";//用户所在群组名称
|
private static String indexGroupName = "";//用户所在群组名称
|
||||||
private static List<GroupMemberInfoDto> memberInfoDtos = new ArrayList<>();
|
private static List<GroupMemberInfoDto> memberInfoDtos = new ArrayList<>();
|
||||||
|
|
@ -26,10 +26,10 @@ public class CallBackResolution {
|
||||||
|
|
||||||
String ngx = "\\u";
|
String ngx = "\\u";
|
||||||
try {
|
try {
|
||||||
Log.i(TAG, "lisHex-->" + lisHex.toString());
|
Timber.tag(TAG).i("lisHex-->" + lisHex.toString());
|
||||||
String firstHex = lisHex.get(0);
|
String firstHex = lisHex.get(0);
|
||||||
String errorNum = lisHex.get(1);
|
String errorNum = lisHex.get(1);
|
||||||
Log.d(TAG, "at_OEM_AT_cb firstHex: " + firstHex + ", errorNum: " + errorNum);
|
Timber.tag(TAG).d("at_OEM_AT_cb firstHex: " + firstHex + ", errorNum: " + errorNum);
|
||||||
switch (firstHex){
|
switch (firstHex){
|
||||||
case "82"://登录状态通知指令
|
case "82"://登录状态通知指令
|
||||||
String loginId = lisHex.get(2) + "" + lisHex.get(3) + "" + lisHex.get(4) + "" + lisHex.get(5);
|
String loginId = lisHex.get(2) + "" + lisHex.get(3) + "" + lisHex.get(4) + "" + lisHex.get(5);
|
||||||
|
|
@ -54,17 +54,17 @@ public class CallBackResolution {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "0b"://发起讲话返回状态
|
case "0b"://发起讲话返回状态
|
||||||
Log.d(TAG, "at_OEM_AT_cb ptt speak...");
|
Timber.tag(TAG).d("at_OEM_AT_cb ptt speak...");
|
||||||
CallBackUtil.callBackSpeakPlaySuccess(errorNum.equals("00")? true : false);
|
CallBackUtil.callBackSpeakPlaySuccess(errorNum.equals("00")? true : false);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "0c"://结束讲话返回状态
|
case "0c"://结束讲话返回状态
|
||||||
Log.d(TAG, "at_OEM_AT_cb speak end...");
|
Timber.tag(TAG).d("at_OEM_AT_cb speak end...");
|
||||||
CallBackUtil.callBackSpeakReleaseSuccess(errorNum.equals("00")? true : false);
|
CallBackUtil.callBackSpeakReleaseSuccess(errorNum.equals("00")? true : false);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "80"://返回群组信息
|
case "80"://返回群组信息
|
||||||
Log.d(TAG, "at_OEM_AT_cb get group info: " + lisHex);
|
Timber.tag(TAG).d("at_OEM_AT_cb get group info: " + lisHex);
|
||||||
String strGroup = "";
|
String strGroup = "";
|
||||||
String groupId = lisHex.get(6) + "" + lisHex.get(7) + "" + lisHex.get(8) + "" + lisHex.get(9);
|
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 groupNo = Integer.valueOf((lisHex.get(4) + "" + lisHex.get(5)), 16);
|
||||||
|
|
@ -87,9 +87,9 @@ public class CallBackResolution {
|
||||||
} finally {
|
} finally {
|
||||||
String tempTxt = "";
|
String tempTxt = "";
|
||||||
tempTxt = unicodeToString(strGroup);
|
tempTxt = unicodeToString(strGroup);
|
||||||
Log.i(TAG, " 组名称 " + tempTxt);
|
Timber.tag(TAG).i(" 组名称 " + tempTxt);
|
||||||
CallBackUtil.callBackGroupInfo(groupId, tempTxt, groupNo, groupCount);
|
CallBackUtil.callBackGroupInfo(groupId, tempTxt, groupNo, groupCount);
|
||||||
Log.d(TAG, "at_OEM_AT_cb group info groupId: " + groupId + ", groupNo: " + groupNo+", groupCount: " +groupCount);
|
Timber.tag(TAG).d("at_OEM_AT_cb group info groupId: " + groupId + ", groupNo: " + groupNo+", groupCount: " +groupCount);
|
||||||
if (1 == groupNo) groupInfos.clear();
|
if (1 == groupNo) groupInfos.clear();
|
||||||
GroupInfoDto groupInfo = new GroupInfoDto(groupId, tempTxt, groupNo, groupCount);
|
GroupInfoDto groupInfo = new GroupInfoDto(groupId, tempTxt, groupNo, groupCount);
|
||||||
groupInfos.add(groupInfo);
|
groupInfos.add(groupInfo);
|
||||||
|
|
@ -197,7 +197,7 @@ public class CallBackResolution {
|
||||||
for (int i = 0; i < stringsTalk.size(); i++) {
|
for (int i = 0; i < stringsTalk.size(); i++) {
|
||||||
tempTxt += unicode(stringsTalk.get(i));
|
tempTxt += unicode(stringsTalk.get(i));
|
||||||
}
|
}
|
||||||
Log.i(TAG, " 临时讲话 信息通知的指令 " + tempTxt);
|
Timber.tag(TAG).i(" 临时讲话 信息通知的指令 " + tempTxt);
|
||||||
CallBackUtil.callBackTempCallNotify(tempTxt);
|
CallBackUtil.callBackTempCallNotify(tempTxt);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
@ -230,7 +230,7 @@ public class CallBackResolution {
|
||||||
for (int i = 0; i < stringsJumpGroup.size(); i++) {
|
for (int i = 0; i < stringsJumpGroup.size(); i++) {
|
||||||
userGroupName += unicode(stringsJumpGroup.get(i));
|
userGroupName += unicode(stringsJumpGroup.get(i));
|
||||||
}
|
}
|
||||||
Log.i(TAG,"进入群组 " + userGroupName);
|
Timber.tag(TAG).i("进入群组 " + userGroupName);
|
||||||
indexGroupId = userGroupId;
|
indexGroupId = userGroupId;
|
||||||
indexGroupName = userGroupName;
|
indexGroupName = userGroupName;
|
||||||
CallBackUtil.callBackNotifyUpdateGroup(userGroupId, userGroupName);
|
CallBackUtil.callBackNotifyUpdateGroup(userGroupId, userGroupName);
|
||||||
|
|
@ -257,7 +257,7 @@ public class CallBackResolution {
|
||||||
String time = strLis[2];
|
String time = strLis[2];
|
||||||
CallBackUtil.callBackNotifyLocationStatus(0, locationMemberId, latitude, longitude, time);
|
CallBackUtil.callBackNotifyLocationStatus(0, locationMemberId, latitude, longitude, time);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.e(TAG, "获取定位Exception-->" + e.getMessage());
|
Timber.tag(TAG).e(e, "获取定位Exception");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
CallBackUtil.callBackNotifyLocationStatus(1, "", 0.0, 0.0, "");
|
CallBackUtil.callBackNotifyLocationStatus(1, "", 0.0, 0.0, "");
|
||||||
|
|
@ -316,7 +316,7 @@ public class CallBackResolution {
|
||||||
string += lisHex.get(i).toString();
|
string += lisHex.get(i).toString();
|
||||||
}
|
}
|
||||||
String address = hexString2String(string);
|
String address = hexString2String(string);
|
||||||
Log.i(TAG, "address-->" + address);
|
Timber.tag(TAG).i("address-->" + address);
|
||||||
|
|
||||||
String[] strLis = address.split(",");
|
String[] strLis = address.split(",");
|
||||||
latitude = Double.parseDouble(strLis[0]);
|
latitude = Double.parseDouble(strLis[0]);
|
||||||
|
|
@ -358,7 +358,7 @@ public class CallBackResolution {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}catch (Exception e){
|
}catch (Exception e){
|
||||||
Log.e(TAG,"at_OEM_AT_cb Exception " + e.getMessage());
|
Timber.tag(TAG).e(e, "at_OEM_AT_cb Exception");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,17 @@
|
||||||
package com.example.kingway.ptt;
|
package com.example.kingway.ptt;
|
||||||
|
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 指令发送类
|
* 指令发送类
|
||||||
* */
|
* */
|
||||||
public class SendAtUtil {
|
public class SendAtUtil {
|
||||||
|
|
||||||
|
private static void send(String cmd) {
|
||||||
|
Timber.tag("PTT_TX").i("CMD: %s", cmd);
|
||||||
|
MyApplication.getAppInstance().getpNative().pttNativeSendAtCmd(cmd);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 账号登录接口
|
* 账号登录接口
|
||||||
* account 账号
|
* account 账号
|
||||||
|
|
@ -20,55 +27,55 @@ public class SendAtUtil {
|
||||||
String locationHex = str2HexStr("gps=" + location + ";");
|
String locationHex = str2HexStr("gps=" + location + ";");
|
||||||
String tempCallHex = str2HexStr("inv=" + tempCall + ";");
|
String tempCallHex = str2HexStr("inv=" + tempCall + ";");
|
||||||
String loginAt = "010000" + ipHex + "" + accountHex + "" + pwdHex + locationHex + tempCallHex + "\r\n";
|
String loginAt = "010000" + ipHex + "" + accountHex + "" + pwdHex + locationHex + tempCallHex + "\r\n";
|
||||||
MyApplication.getAppInstance().getpNative().pttNativeSendAtCmd(loginAt);
|
send(loginAt);
|
||||||
MyApplication.getAppInstance().getpNative().pttNativeSendAtCmd("00000000\r\n");
|
send("00000000\r\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 按下PTT开始呼叫
|
* 按下PTT开始呼叫
|
||||||
* */
|
* */
|
||||||
public static void speakPlay(){
|
public static void speakPlay(){
|
||||||
MyApplication.getAppInstance().getpNative().pttNativeSendAtCmd("0B0000");
|
send("0B0000");
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 松开PTT结束呼叫*/
|
/** 松开PTT结束呼叫*/
|
||||||
public static void speakRelease(){
|
public static void speakRelease(){
|
||||||
AudioRecordManager.getInstance().stopRecord();
|
AudioRecordManager.getInstance().stopRecord();
|
||||||
MyApplication.getAppInstance().getpNative().pttNativeSendAtCmd("0C0000");
|
send("0C0000");
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 发送TCP包 登录成功后需要定时发送TCP包*/
|
/** 发送TCP包 登录成功后需要定时发送TCP包*/
|
||||||
public static void sendTCP(){
|
public static void sendTCP(){
|
||||||
MyApplication.getAppInstance().getpNative().pttNativeSendAtCmd("570000\r\n");
|
send("570000\r\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 发送UDP包 登录后需要定时发送UDP包*/
|
/** 发送UDP包 登录后需要定时发送UDP包*/
|
||||||
public static void sendUDP(){
|
public static void sendUDP(){
|
||||||
MyApplication.getAppInstance().getpNative().pttNativeSendAtCmd("580000\r\n");
|
send("580000\r\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 请求群组信息 */
|
/** 请求群组信息 */
|
||||||
public static void getGroupInfo(){
|
public static void getGroupInfo(){
|
||||||
MyApplication.getAppInstance().getpNative().pttNativeSendAtCmd("0D0000");
|
send("0D0000");
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 请求群组成员信息
|
/** 请求群组成员信息
|
||||||
* groupId 十六进制群组Id
|
* groupId 十六进制群组Id
|
||||||
* */
|
* */
|
||||||
public static void getGroupMemberInfo(String groupId){
|
public static void getGroupMemberInfo(String groupId){
|
||||||
MyApplication.getAppInstance().getpNative().pttNativeSendAtCmd("0E0000" + groupId);
|
send("0E0000" + groupId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 切换群组
|
/** 切换群组
|
||||||
* groupId 十六进制群组Id
|
* groupId 十六进制群组Id
|
||||||
* */
|
* */
|
||||||
public static void jumpGroup(String groupId){
|
public static void jumpGroup(String groupId){
|
||||||
MyApplication.getAppInstance().getpNative().pttNativeSendAtCmd("090000" + groupId);
|
send("090000" + groupId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 请求好友信息 */
|
/** 请求好友信息 */
|
||||||
public static void getFriendInfo(){
|
public static void getFriendInfo(){
|
||||||
MyApplication.getAppInstance().getpNative().pttNativeSendAtCmd("0E000000000000\r\n");
|
send("0E000000000000\r\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,15 +3,49 @@ package com.stand.standapp
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import android.util.Log
|
|
||||||
import android.webkit.JavascriptInterface
|
import android.webkit.JavascriptInterface
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import android.print.PrintAttributes
|
import android.print.PrintAttributes
|
||||||
import android.print.PrintManager
|
import android.print.PrintManager
|
||||||
import com.stand.standapp.R
|
import com.stand.standapp.R
|
||||||
|
import com.stand.standapp.utils.LogManager
|
||||||
|
import timber.log.Timber
|
||||||
|
import org.json.JSONObject
|
||||||
|
|
||||||
class AndroidInterface(private val activity: MainActivity) {
|
class AndroidInterface(private val activity: MainActivity) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JS 调用:压缩并导出日志
|
||||||
|
* 返回压缩后的文件路径
|
||||||
|
*/
|
||||||
|
@JavascriptInterface
|
||||||
|
fun exportLogs(): String {
|
||||||
|
val zipPath = LogManager.zipLogs(activity)
|
||||||
|
if (zipPath != null) {
|
||||||
|
Handler(Looper.getMainLooper()).post {
|
||||||
|
Toast.makeText(activity, "日志已打包: $zipPath", Toast.LENGTH_LONG).show()
|
||||||
|
}
|
||||||
|
return zipPath
|
||||||
|
} else {
|
||||||
|
Handler(Looper.getMainLooper()).post {
|
||||||
|
Toast.makeText(activity, "暂无日志或打包失败", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JS 调用:直接上传日志到后台
|
||||||
|
*/
|
||||||
|
@JavascriptInterface
|
||||||
|
fun uploadLogs() {
|
||||||
|
LogManager.uploadLogs(activity) { success, message ->
|
||||||
|
Handler(Looper.getMainLooper()).post {
|
||||||
|
Toast.makeText(activity, if (success) "日志上传成功" else "上传失败: $message", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* JS 调用:标准系统打印 (PDF/网络打印机/AirPrint)
|
* JS 调用:标准系统打印 (PDF/网络打印机/AirPrint)
|
||||||
* 适用于打印精美的 A4 单据或页面
|
* 适用于打印精美的 A4 单据或页面
|
||||||
|
|
@ -33,7 +67,7 @@ class AndroidInterface(private val activity: MainActivity) {
|
||||||
|
|
||||||
printManager.print(jobName, printAdapter, PrintAttributes.Builder().build())
|
printManager.print(jobName, printAdapter, PrintAttributes.Builder().build())
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e("Print", "系统打印失败", e)
|
Timber.tag("Print").e(e, "系统打印失败")
|
||||||
Toast.makeText(activity, "打印服务启动失败", Toast.LENGTH_SHORT).show()
|
Toast.makeText(activity, "打印服务启动失败", Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -47,17 +81,17 @@ class AndroidInterface(private val activity: MainActivity) {
|
||||||
@JavascriptInterface
|
@JavascriptInterface
|
||||||
fun printRawData(data: String) {
|
fun printRawData(data: String) {
|
||||||
try {
|
try {
|
||||||
Log.d("Print", "收到 H5 原始打印数据: $data")
|
Timber.tag("Print").d("收到 H5 原始打印数据: $data")
|
||||||
Handler(Looper.getMainLooper()).post {
|
Handler(Looper.getMainLooper()).post {
|
||||||
try {
|
try {
|
||||||
// 这里可以弹出一个选择框让用户选择打印机,或者根据配置自动连接
|
// 这里可以弹出一个选择框让用户选择打印机,或者根据配置自动连接
|
||||||
Toast.makeText(activity, "H5指令打印: $data", Toast.LENGTH_SHORT).show()
|
Toast.makeText(activity, "H5指令打印: $data", Toast.LENGTH_SHORT).show()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e("Print", "Toast显示失败", e)
|
Timber.tag("Print").e(e, "Toast显示失败")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e("Print", "指令解析失败", e)
|
Timber.tag("Print").e(e, "指令解析失败")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -70,7 +104,7 @@ class AndroidInterface(private val activity: MainActivity) {
|
||||||
try {
|
try {
|
||||||
UpdateManager(activity).checkUpdate()
|
UpdateManager(activity).checkUpdate()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e("Update", "启动更新检查失败", e)
|
Timber.tag("Update").e(e, "启动更新检查失败")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -84,7 +118,7 @@ class AndroidInterface(private val activity: MainActivity) {
|
||||||
try {
|
try {
|
||||||
activity.showExitDialog()
|
activity.showExitDialog()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e("App", "显示退出弹窗失败", e)
|
Timber.tag("App").e(e, "显示退出弹窗失败")
|
||||||
activity.finish()
|
activity.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -110,7 +144,7 @@ class AndroidInterface(private val activity: MainActivity) {
|
||||||
fun getAboutInfo(): String {
|
fun getAboutInfo(): String {
|
||||||
return try {
|
return try {
|
||||||
val packageInfo = activity.packageManager.getPackageInfo(activity.packageName, 0)
|
val packageInfo = activity.packageManager.getPackageInfo(activity.packageName, 0)
|
||||||
val json = org.json.JSONObject()
|
val json = JSONObject()
|
||||||
json.put("appName", activity.getString(R.string.app_name))
|
json.put("appName", activity.getString(R.string.app_name))
|
||||||
json.put("versionName", packageInfo.versionName)
|
json.put("versionName", packageInfo.versionName)
|
||||||
json.put("model", android.os.Build.MODEL)
|
json.put("model", android.os.Build.MODEL)
|
||||||
|
|
@ -134,7 +168,7 @@ class AndroidInterface(private val activity: MainActivity) {
|
||||||
val list = updatedMsgs.split("|---|").take(5000)
|
val list = updatedMsgs.split("|---|").take(5000)
|
||||||
prefs.edit().putString("history", list.joinToString("|---|")).apply()
|
prefs.edit().putString("history", list.joinToString("|---|")).apply()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e("Storage", "消息保存异常", e)
|
Timber.tag("Storage").e(e, "消息保存异常")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -162,7 +196,7 @@ class AndroidInterface(private val activity: MainActivity) {
|
||||||
Toast.makeText(activity, "记录已清空", Toast.LENGTH_SHORT).show()
|
Toast.makeText(activity, "记录已清空", Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e("Storage", "清空记录异常", e)
|
Timber.tag("Storage").e(e, "清空记录异常")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -176,7 +210,7 @@ class AndroidInterface(private val activity: MainActivity) {
|
||||||
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)
|
Timber.tag("UI").e(e, "隐藏加载布局失败")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ package com.stand.standapp
|
||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.Log
|
import timber.log.Timber
|
||||||
import android.widget.*
|
import android.widget.*
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
|
@ -68,7 +68,8 @@ class LoginActivity : AppCompatActivity() {
|
||||||
* 执行真实接口登录
|
* 执行真实接口登录
|
||||||
*/
|
*/
|
||||||
private fun performLogin(username: String, password: String, remember: Boolean) {
|
private fun performLogin(username: String, password: String, remember: Boolean) {
|
||||||
val loginUrl = "https://template.gyouzhe.com/api/auth/login"
|
val serverUrl = AppConfig.getServerUrl(this)
|
||||||
|
val loginUrl = "$serverUrl/api/auth/login"
|
||||||
val bearerToken = "eyJraWQiOiJzLXYxIiwidHlwIjoiSldUIiwiYWxnIjoiSFMyNTYifQ.eyJzdWIiOiJhZG1pbiIsInR5cCI6ImFjY2VzcyIsImV4cCI6MTc3NjQwMTU2MSwianRpIjoiMjM0NjdiZGMtNTY0Zi00NmY0LWJhZTgtNzJhZmQ1MzhmYmZhIiwiZmdwIjoiY2FhNzYzOTI4YWRhYWZiYjgzMjE3NjExYmVjYTc3ZjMiLCJraWQiOiJzLXYxIn0.4Zf1eiKcUjGNoFylEzmZ2no1kJY-44CQ8QWmMA_UxvY"
|
val bearerToken = "eyJraWQiOiJzLXYxIiwidHlwIjoiSldUIiwiYWxnIjoiSFMyNTYifQ.eyJzdWIiOiJhZG1pbiIsInR5cCI6ImFjY2VzcyIsImV4cCI6MTc3NjQwMTU2MSwianRpIjoiMjM0NjdiZGMtNTY0Zi00NmY0LWJhZTgtNzJhZmQ1MzhmYmZhIiwiZmdwIjoiY2FhNzYzOTI4YWRhYWZiYjgzMjE3NjExYmVjYTc3ZjMiLCJraWQiOiJzLXYxIn0.4Zf1eiKcUjGNoFylEzmZ2no1kJY-44CQ8QWmMA_UxvY"
|
||||||
|
|
||||||
val body = MultipartBody.Builder()
|
val body = MultipartBody.Builder()
|
||||||
|
|
@ -123,7 +124,7 @@ class LoginActivity : AppCompatActivity() {
|
||||||
Toast.makeText(this@LoginActivity, msg, Toast.LENGTH_SHORT).show()
|
Toast.makeText(this@LoginActivity, msg, Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e("Login", "解析响应失败", e)
|
Timber.tag("Login").e(e, "解析响应失败")
|
||||||
Toast.makeText(this@LoginActivity, "服务器响应异常", Toast.LENGTH_SHORT).show()
|
Toast.makeText(this@LoginActivity, "服务器响应异常", Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,20 @@ package com.stand.standapp
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.KeyEvent
|
import android.view.KeyEvent
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import android.webkit.ConsoleMessage
|
||||||
|
import android.webkit.WebResourceError
|
||||||
|
import android.webkit.WebResourceRequest
|
||||||
|
import android.webkit.WebView
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
import android.util.Log
|
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import timber.log.Timber
|
||||||
import com.just.agentweb.AgentWeb
|
import com.just.agentweb.AgentWeb
|
||||||
|
import com.just.agentweb.WebChromeClient
|
||||||
|
import com.just.agentweb.WebViewClient
|
||||||
import com.stand.standapp.R
|
import com.stand.standapp.R
|
||||||
|
import com.stand.standapp.AppConfig
|
||||||
|
import com.stand.standapp.AndroidInterface
|
||||||
|
import com.stand.standapp.UpdateManager
|
||||||
|
|
||||||
class MainActivity : AppCompatActivity() {
|
class MainActivity : AppCompatActivity() {
|
||||||
|
|
||||||
|
|
@ -38,11 +46,31 @@ class MainActivity : AppCompatActivity() {
|
||||||
// 从配置中获取服务器地址
|
// 从配置中获取服务器地址
|
||||||
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()}"
|
||||||
Log.d("MainActivity", "Loading URL: $finalUrl")
|
Timber.tag("MainActivity").d("Loading URL: $finalUrl")
|
||||||
|
|
||||||
mAgentWeb = AgentWeb.with(this)
|
mAgentWeb = AgentWeb.with(this)
|
||||||
.setAgentWebParent(container, LinearLayout.LayoutParams(-1, -1))
|
.setAgentWebParent(container, LinearLayout.LayoutParams(-1, -1))
|
||||||
.useDefaultIndicator()
|
.useDefaultIndicator()
|
||||||
|
.setWebChromeClient(object : WebChromeClient() {
|
||||||
|
override fun onConsoleMessage(consoleMessage: ConsoleMessage?): Boolean {
|
||||||
|
Timber.tag("WebConsole").d("${consoleMessage?.messageLevel()}: ${consoleMessage?.message()} -- From line ${consoleMessage?.lineNumber()} of ${consoleMessage?.sourceId()}")
|
||||||
|
return super.onConsoleMessage(consoleMessage)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.setWebViewClient(object : WebViewClient() {
|
||||||
|
override fun onPageFinished(view: WebView?, url: String?) {
|
||||||
|
super.onPageFinished(view, url)
|
||||||
|
cancelTimeoutTimer()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onReceivedError(view: WebView?, request: WebResourceRequest?, error: WebResourceError?) {
|
||||||
|
Timber.tag("WebError").e("URL: ${request?.url}, Error: ${error?.description} (Code: ${error?.errorCode})")
|
||||||
|
if (request?.isForMainFrame == true) {
|
||||||
|
showErrorPage()
|
||||||
|
}
|
||||||
|
super.onReceivedError(view, request, error)
|
||||||
|
}
|
||||||
|
})
|
||||||
.addJavascriptInterface("android", AndroidInterface(this))
|
.addJavascriptInterface("android", AndroidInterface(this))
|
||||||
.createAgentWeb()
|
.createAgentWeb()
|
||||||
.ready()
|
.ready()
|
||||||
|
|
@ -66,7 +94,7 @@ class MainActivity : AppCompatActivity() {
|
||||||
settings.domStorageEnabled = true
|
settings.domStorageEnabled = true
|
||||||
settings.javaScriptEnabled = true
|
settings.javaScriptEnabled = true
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e("MainActivity", "WebView settings error: ${e.message}")
|
Timber.tag("MainActivity").e(e, "WebView settings error")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// .go("http://152.32.186.199/local/print_demo.html")
|
// .go("http://152.32.186.199/local/print_demo.html")
|
||||||
|
|
@ -75,8 +103,7 @@ class MainActivity : AppCompatActivity() {
|
||||||
UpdateManager(this).checkUpdate()
|
UpdateManager(this).checkUpdate()
|
||||||
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e("MainActivity", "Fatal Crash: ${e.message}")
|
Timber.tag("MainActivity").e(e, "Fatal Crash")
|
||||||
e.printStackTrace()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
package com.stand.standapp
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import com.stand.standapp.utils.LogManager
|
||||||
|
import com.example.kingway.ptt.MyApplication as PTTMyApplication
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
|
class MyApplication : Application() {
|
||||||
|
|
||||||
|
override fun onCreate() {
|
||||||
|
super.onCreate()
|
||||||
|
// Initialize Timber and File Logging
|
||||||
|
LogManager.init(this)
|
||||||
|
|
||||||
|
// Initialize PTT Module
|
||||||
|
PTTMyApplication.init(this)
|
||||||
|
|
||||||
|
Timber.tag("MyApplication").i("Application onCreate - Logging system and PTT started")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -10,7 +10,7 @@ import android.os.Environment
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import android.provider.MediaStore
|
import android.provider.MediaStore
|
||||||
import android.util.Log
|
import timber.log.Timber
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
|
@ -63,7 +63,7 @@ class UpdateManager(private val context: Context) {
|
||||||
|
|
||||||
client.newCall(request).enqueue(object : Callback {
|
client.newCall(request).enqueue(object : Callback {
|
||||||
override fun onFailure(call: Call, e: IOException) {
|
override fun onFailure(call: Call, e: IOException) {
|
||||||
Log.e("UpdateManager", "检查更新失败: ${e.message}")
|
Timber.tag("UpdateManager").e(e, "检查更新失败")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResponse(call: Call, response: Response) {
|
override fun onResponse(call: Call, response: Response) {
|
||||||
|
|
@ -104,7 +104,7 @@ class UpdateManager(private val context: Context) {
|
||||||
handler.post { Toast.makeText(context, "更新服务提示: $msg", Toast.LENGTH_SHORT).show() }
|
handler.post { Toast.makeText(context, "更新服务提示: $msg", Toast.LENGTH_SHORT).show() }
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e("UpdateManager", "解析或处理更新结果失败", e)
|
Timber.tag("UpdateManager").e(e, "解析或处理更新结果失败")
|
||||||
handler.post { Toast.makeText(context, "更新信息解析失败", Toast.LENGTH_SHORT).show() }
|
handler.post { Toast.makeText(context, "更新信息解析失败", Toast.LENGTH_SHORT).show() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -266,7 +266,7 @@ class UpdateManager(private val context: Context) {
|
||||||
intent.setDataAndType(installUri, "application/vnd.android.package-archive")
|
intent.setDataAndType(installUri, "application/vnd.android.package-archive")
|
||||||
context.startActivity(intent)
|
context.startActivity(intent)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e("UpdateManager", "安装失败", e)
|
Timber.tag("UpdateManager").e(e, "安装失败")
|
||||||
Toast.makeText(context, "无法自动调起安装,请在系统下载目录中手动安装", Toast.LENGTH_LONG).show()
|
Toast.makeText(context, "无法自动调起安装,请在系统下载目录中手动安装", Toast.LENGTH_LONG).show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,135 @@
|
||||||
|
package com.stand.standapp.utils
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import com.stand.standapp.AppConfig
|
||||||
|
import okhttp3.*
|
||||||
|
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||||
|
import okhttp3.RequestBody.Companion.asRequestBody
|
||||||
|
import timber.log.Timber
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileInputStream
|
||||||
|
import java.io.FileOutputStream
|
||||||
|
import java.io.FileWriter
|
||||||
|
import java.io.IOException
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.*
|
||||||
|
import java.util.concurrent.Executors
|
||||||
|
import java.util.zip.ZipEntry
|
||||||
|
import java.util.zip.ZipOutputStream
|
||||||
|
|
||||||
|
object LogManager {
|
||||||
|
private val executor = Executors.newSingleThreadExecutor()
|
||||||
|
private const val MAX_DAYS = 15
|
||||||
|
private val client = OkHttpClient()
|
||||||
|
|
||||||
|
fun init(context: Context) {
|
||||||
|
Timber.plant(Timber.DebugTree())
|
||||||
|
val logDir = File(context.getExternalFilesDir(null), "logs")
|
||||||
|
if (!logDir.exists()) logDir.mkdirs()
|
||||||
|
cleanOldLogs(logDir)
|
||||||
|
Timber.plant(FileLoggingTree(logDir))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun cleanOldLogs(logDir: File) {
|
||||||
|
executor.execute {
|
||||||
|
try {
|
||||||
|
val calendar = Calendar.getInstance()
|
||||||
|
calendar.add(Calendar.DAY_OF_YEAR, -MAX_DAYS)
|
||||||
|
val thresholdDate = calendar.time
|
||||||
|
logDir.listFiles()?.forEach { file ->
|
||||||
|
if (file.isFile && file.name.endsWith(".log")) {
|
||||||
|
if (Date(file.lastModified()).before(thresholdDate)) file.delete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun zipLogs(context: Context): String? {
|
||||||
|
val logDir = File(context.getExternalFilesDir(null), "logs")
|
||||||
|
if (!logDir.exists() || logDir.listFiles().isNullOrEmpty()) return null
|
||||||
|
val zipFile = File(context.getExternalFilesDir(null), "logs_${System.currentTimeMillis()}.zip")
|
||||||
|
return try {
|
||||||
|
ZipOutputStream(FileOutputStream(zipFile)).use { zos ->
|
||||||
|
logDir.listFiles()?.forEach { file ->
|
||||||
|
if (file.isFile && file.name.endsWith(".log")) {
|
||||||
|
zos.putNextEntry(ZipEntry(file.name))
|
||||||
|
FileInputStream(file).use { it.copyTo(zos) }
|
||||||
|
zos.closeEntry()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
zipFile.absolutePath
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Timber.e(e, "Zip logs failed")
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传日志到服务器
|
||||||
|
*/
|
||||||
|
fun uploadLogs(context: Context, callback: (Boolean, String) -> Unit) {
|
||||||
|
executor.execute {
|
||||||
|
val zipPath = zipLogs(context)
|
||||||
|
if (zipPath == null) {
|
||||||
|
callback(false, "打包日志失败")
|
||||||
|
return@execute
|
||||||
|
}
|
||||||
|
|
||||||
|
val file = File(zipPath)
|
||||||
|
val serverUrl = AppConfig.getServerUrl(context)
|
||||||
|
val uploadUrl = "$serverUrl/api/attachment/upload"
|
||||||
|
|
||||||
|
// 使用固定的 Token (根据用户提供)
|
||||||
|
val bearerToken = "eyJraWQiOiJzLXYxIiwidHlwIjoiSldUIiwiYWxnIjoiSFMyNTYifQ.eyJzdWIiOiJhZG1pbiIsInR5cCI6ImFjY2VzcyIsImV4cCI6MTc3NjQ5MDczNSwianRpIjoiMjYzNjljYzQtYzJiMy00YTg0LWFiYTMtZDU4NmNiZDRmMmM0IiwiZmdwIjoiY2FhNzYzOTI4YWRhYWZiYjgzMjE3NjExYmVjYTc3ZjMiLCJraWQiOiJzLXYxIn0.AyuzNAe-R_GOBC65STH5qpHkmmUngX8rMBPI4CdIIhk"
|
||||||
|
|
||||||
|
val requestBody = MultipartBody.Builder()
|
||||||
|
.setType(MultipartBody.FORM)
|
||||||
|
.addFormDataPart("file", file.name, file.asRequestBody("application/zip".toMediaTypeOrNull()))
|
||||||
|
.build()
|
||||||
|
|
||||||
|
val request = Request.Builder()
|
||||||
|
.url(uploadUrl)
|
||||||
|
.header("Authorization", "Bearer $bearerToken")
|
||||||
|
.post(requestBody)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
try {
|
||||||
|
client.newCall(request).execute().use { response ->
|
||||||
|
if (response.isSuccessful) {
|
||||||
|
callback(true, "上传成功")
|
||||||
|
// 上传成功后删除临时 zip 文件
|
||||||
|
file.delete()
|
||||||
|
} else {
|
||||||
|
callback(false, "服务器返回错误: ${response.code}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: IOException) {
|
||||||
|
callback(false, "网络连接失败: ${e.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class FileLoggingTree(private val logDir: File) : Timber.Tree() {
|
||||||
|
private val dateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.getDefault())
|
||||||
|
private val fileNameFormat = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
|
||||||
|
|
||||||
|
override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
|
||||||
|
executor.execute {
|
||||||
|
val logFile = File(logDir, "${fileNameFormat.format(Date())}.log")
|
||||||
|
val priorityStr = when (priority) {
|
||||||
|
2 -> "V"
|
||||||
|
3 -> "D"
|
||||||
|
4 -> "I"
|
||||||
|
5 -> "W"
|
||||||
|
6 -> "E"
|
||||||
|
7 -> "A"
|
||||||
|
else -> "U"
|
||||||
|
}
|
||||||
|
val entry = "${dateFormat.format(Date())} $priorityStr/[$tag]: $message\n${t?.stackTraceToString() ?: ""}\n"
|
||||||
|
try { FileWriter(logFile, true).use { it.write(entry) } } catch (e: Exception) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">StandAPP</string>
|
<string name="app_name">消防值守台</string>
|
||||||
</resources>
|
</resources>
|
||||||
Loading…
Reference in New Issue