This commit is contained in:
parent
722ece723f
commit
a957afd5d4
|
|
@ -17,7 +17,7 @@ android {
|
|||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
||||
ndk {
|
||||
abiFilters.addAll(listOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64"))
|
||||
abiFilters.addAll(listOf("armeabi-v7a", "arm64-v8a"))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -45,6 +45,7 @@ android {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
implementation("com.jakewharton.timber:timber:5.0.1")
|
||||
implementation("io.github.justson:agentweb-core:v5.1.1-androidx")
|
||||
implementation("com.github.Justson:Downloader:v5.0.4-androidx")
|
||||
implementation("com.google.android.material:material:1.12.0")
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
|
||||
|
||||
<application
|
||||
android:name=".MyApplication"
|
||||
android:allowBackup="true"
|
||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||
android:fullBackupContent="@xml/backup_rules"
|
||||
|
|
|
|||
|
|
@ -86,8 +86,9 @@
|
|||
<p style="font-size: 12px; color: #666;">(适用于蓝牙/USB 小票机,传输原始 JSON)</p>
|
||||
|
||||
<button class="btn btn-orange" onclick="callUpdate()">3. 检查系统更新</button>
|
||||
<button class="btn" style="background: #64748b;" onclick="callAbout()">4. 关于应用</button>
|
||||
<button class="btn btn-red" onclick="callExit()">5. 退出应用</button>
|
||||
<button class="btn" style="background: #10B981;" onclick="callUploadLogs()">4. 上传系统日志</button>
|
||||
<button class="btn" style="background: #64748b;" onclick="callAbout()">5. 关于应用</button>
|
||||
<button class="btn btn-red" onclick="callExit()">6. 退出应用</button>
|
||||
|
||||
<!-- WebSocket 控制区 -->
|
||||
<div class="ws-card">
|
||||
|
|
@ -255,6 +256,15 @@
|
|||
}
|
||||
}
|
||||
|
||||
// 上传日志
|
||||
function callUploadLogs() {
|
||||
if (window.android && window.android.uploadLogs) {
|
||||
window.android.uploadLogs();
|
||||
} else {
|
||||
alert("上传日志接口不可用");
|
||||
}
|
||||
}
|
||||
|
||||
function callExit() {
|
||||
if (window.android && window.android.exitApp) {
|
||||
window.android.exitApp();
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
package com.example.kingway.ptt;
|
||||
|
||||
import android.util.Log;
|
||||
import timber.log.Timber;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**解析接收到的AT指令*/
|
||||
public class CallBackResolution {
|
||||
private static String TAG = "CallBackResolution";
|
||||
private static String TAG = "PTT_RX";
|
||||
private static String indexGroupId = "";//用户所在群组Id
|
||||
private static String indexGroupName = "";//用户所在群组名称
|
||||
private static List<GroupMemberInfoDto> memberInfoDtos = new ArrayList<>();
|
||||
|
|
@ -26,10 +26,10 @@ public class CallBackResolution {
|
|||
|
||||
String ngx = "\\u";
|
||||
try {
|
||||
Log.i(TAG, "lisHex-->" + lisHex.toString());
|
||||
Timber.tag(TAG).i("lisHex-->" + lisHex.toString());
|
||||
String firstHex = lisHex.get(0);
|
||||
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){
|
||||
case "82"://登录状态通知指令
|
||||
String loginId = lisHex.get(2) + "" + lisHex.get(3) + "" + lisHex.get(4) + "" + lisHex.get(5);
|
||||
|
|
@ -54,17 +54,17 @@ public class CallBackResolution {
|
|||
break;
|
||||
|
||||
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);
|
||||
break;
|
||||
|
||||
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);
|
||||
break;
|
||||
|
||||
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 groupId = lisHex.get(6) + "" + lisHex.get(7) + "" + lisHex.get(8) + "" + lisHex.get(9);
|
||||
int groupNo = Integer.valueOf((lisHex.get(4) + "" + lisHex.get(5)), 16);
|
||||
|
|
@ -87,9 +87,9 @@ public class CallBackResolution {
|
|||
} finally {
|
||||
String tempTxt = "";
|
||||
tempTxt = unicodeToString(strGroup);
|
||||
Log.i(TAG, " 组名称 " + tempTxt);
|
||||
Timber.tag(TAG).i(" 组名称 " + tempTxt);
|
||||
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();
|
||||
GroupInfoDto groupInfo = new GroupInfoDto(groupId, tempTxt, groupNo, groupCount);
|
||||
groupInfos.add(groupInfo);
|
||||
|
|
@ -197,7 +197,7 @@ public class CallBackResolution {
|
|||
for (int i = 0; i < stringsTalk.size(); i++) {
|
||||
tempTxt += unicode(stringsTalk.get(i));
|
||||
}
|
||||
Log.i(TAG, " 临时讲话 信息通知的指令 " + tempTxt);
|
||||
Timber.tag(TAG).i(" 临时讲话 信息通知的指令 " + tempTxt);
|
||||
CallBackUtil.callBackTempCallNotify(tempTxt);
|
||||
}
|
||||
break;
|
||||
|
|
@ -230,7 +230,7 @@ public class CallBackResolution {
|
|||
for (int i = 0; i < stringsJumpGroup.size(); i++) {
|
||||
userGroupName += unicode(stringsJumpGroup.get(i));
|
||||
}
|
||||
Log.i(TAG,"进入群组 " + userGroupName);
|
||||
Timber.tag(TAG).i("进入群组 " + userGroupName);
|
||||
indexGroupId = userGroupId;
|
||||
indexGroupName = userGroupName;
|
||||
CallBackUtil.callBackNotifyUpdateGroup(userGroupId, userGroupName);
|
||||
|
|
@ -257,7 +257,7 @@ public class CallBackResolution {
|
|||
String time = strLis[2];
|
||||
CallBackUtil.callBackNotifyLocationStatus(0, locationMemberId, latitude, longitude, time);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "获取定位Exception-->" + e.getMessage());
|
||||
Timber.tag(TAG).e(e, "获取定位Exception");
|
||||
}
|
||||
} else {
|
||||
CallBackUtil.callBackNotifyLocationStatus(1, "", 0.0, 0.0, "");
|
||||
|
|
@ -311,14 +311,14 @@ public class CallBackResolution {
|
|||
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 string = "";
|
||||
for (int i = 4; i < lisHex.size(); i++) {
|
||||
string += lisHex.get(i).toString();
|
||||
}
|
||||
String address = hexString2String(string);
|
||||
Timber.tag(TAG).i("address-->" + address);
|
||||
|
||||
String[] strLis = address.split(",");
|
||||
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());
|
||||
|
|
@ -358,7 +358,7 @@ public class CallBackResolution {
|
|||
break;
|
||||
}
|
||||
}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;
|
||||
|
||||
import timber.log.Timber;
|
||||
|
||||
/**
|
||||
* 指令发送类
|
||||
* */
|
||||
public class SendAtUtil {
|
||||
|
||||
private static void send(String cmd) {
|
||||
Timber.tag("PTT_TX").i("CMD: %s", cmd);
|
||||
MyApplication.getAppInstance().getpNative().pttNativeSendAtCmd(cmd);
|
||||
}
|
||||
|
||||
/**
|
||||
* 账号登录接口
|
||||
* account 账号
|
||||
|
|
@ -20,55 +27,55 @@ public class SendAtUtil {
|
|||
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");
|
||||
send(loginAt);
|
||||
send("00000000\r\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* 按下PTT开始呼叫
|
||||
* */
|
||||
public static void speakPlay(){
|
||||
MyApplication.getAppInstance().getpNative().pttNativeSendAtCmd("0B0000");
|
||||
send("0B0000");
|
||||
}
|
||||
|
||||
/** 松开PTT结束呼叫*/
|
||||
public static void speakRelease(){
|
||||
AudioRecordManager.getInstance().stopRecord();
|
||||
MyApplication.getAppInstance().getpNative().pttNativeSendAtCmd("0C0000");
|
||||
send("0C0000");
|
||||
}
|
||||
|
||||
/** 发送TCP包 登录成功后需要定时发送TCP包*/
|
||||
public static void sendTCP(){
|
||||
MyApplication.getAppInstance().getpNative().pttNativeSendAtCmd("570000\r\n");
|
||||
send("570000\r\n");
|
||||
}
|
||||
|
||||
/** 发送UDP包 登录后需要定时发送UDP包*/
|
||||
public static void sendUDP(){
|
||||
MyApplication.getAppInstance().getpNative().pttNativeSendAtCmd("580000\r\n");
|
||||
send("580000\r\n");
|
||||
}
|
||||
|
||||
/** 请求群组信息 */
|
||||
public static void getGroupInfo(){
|
||||
MyApplication.getAppInstance().getpNative().pttNativeSendAtCmd("0D0000");
|
||||
send("0D0000");
|
||||
}
|
||||
|
||||
/** 请求群组成员信息
|
||||
* groupId 十六进制群组Id
|
||||
* */
|
||||
public static void getGroupMemberInfo(String groupId){
|
||||
MyApplication.getAppInstance().getpNative().pttNativeSendAtCmd("0E0000" + groupId);
|
||||
send("0E0000" + groupId);
|
||||
}
|
||||
|
||||
/** 切换群组
|
||||
* groupId 十六进制群组Id
|
||||
* */
|
||||
public static void jumpGroup(String groupId){
|
||||
MyApplication.getAppInstance().getpNative().pttNativeSendAtCmd("090000" + groupId);
|
||||
send("090000" + groupId);
|
||||
}
|
||||
|
||||
/** 请求好友信息 */
|
||||
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.os.Handler
|
||||
import android.os.Looper
|
||||
import android.util.Log
|
||||
import android.webkit.JavascriptInterface
|
||||
import android.widget.Toast
|
||||
import android.print.PrintAttributes
|
||||
import android.print.PrintManager
|
||||
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) {
|
||||
|
||||
/**
|
||||
* 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)
|
||||
* 适用于打印精美的 A4 单据或页面
|
||||
|
|
@ -33,7 +67,7 @@ class AndroidInterface(private val activity: MainActivity) {
|
|||
|
||||
printManager.print(jobName, printAdapter, PrintAttributes.Builder().build())
|
||||
} catch (e: Exception) {
|
||||
Log.e("Print", "系统打印失败", e)
|
||||
Timber.tag("Print").e(e, "系统打印失败")
|
||||
Toast.makeText(activity, "打印服务启动失败", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
|
@ -47,17 +81,17 @@ class AndroidInterface(private val activity: MainActivity) {
|
|||
@JavascriptInterface
|
||||
fun printRawData(data: String) {
|
||||
try {
|
||||
Log.d("Print", "收到 H5 原始打印数据: $data")
|
||||
Timber.tag("Print").d("收到 H5 原始打印数据: $data")
|
||||
Handler(Looper.getMainLooper()).post {
|
||||
try {
|
||||
// 这里可以弹出一个选择框让用户选择打印机,或者根据配置自动连接
|
||||
Toast.makeText(activity, "H5指令打印: $data", Toast.LENGTH_SHORT).show()
|
||||
} catch (e: Exception) {
|
||||
Log.e("Print", "Toast显示失败", e)
|
||||
Timber.tag("Print").e(e, "Toast显示失败")
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e("Print", "指令解析失败", e)
|
||||
Timber.tag("Print").e(e, "指令解析失败")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -70,7 +104,7 @@ class AndroidInterface(private val activity: MainActivity) {
|
|||
try {
|
||||
UpdateManager(activity).checkUpdate()
|
||||
} catch (e: Exception) {
|
||||
Log.e("Update", "启动更新检查失败", e)
|
||||
Timber.tag("Update").e(e, "启动更新检查失败")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -84,7 +118,7 @@ class AndroidInterface(private val activity: MainActivity) {
|
|||
try {
|
||||
activity.showExitDialog()
|
||||
} catch (e: Exception) {
|
||||
Log.e("App", "显示退出弹窗失败", e)
|
||||
Timber.tag("App").e(e, "显示退出弹窗失败")
|
||||
activity.finish()
|
||||
}
|
||||
}
|
||||
|
|
@ -110,7 +144,7 @@ class AndroidInterface(private val activity: MainActivity) {
|
|||
fun getAboutInfo(): String {
|
||||
return try {
|
||||
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("versionName", packageInfo.versionName)
|
||||
json.put("model", android.os.Build.MODEL)
|
||||
|
|
@ -134,7 +168,7 @@ class AndroidInterface(private val activity: MainActivity) {
|
|||
val list = updatedMsgs.split("|---|").take(5000)
|
||||
prefs.edit().putString("history", list.joinToString("|---|")).apply()
|
||||
} 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()
|
||||
}
|
||||
} 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.findViewById<android.view.View>(R.id.loading_layout)?.visibility = android.view.View.GONE
|
||||
} 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.os.Bundle
|
||||
import android.util.Log
|
||||
import timber.log.Timber
|
||||
import android.widget.*
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
|
|
@ -68,7 +68,8 @@ class LoginActivity : AppCompatActivity() {
|
|||
* 执行真实接口登录
|
||||
*/
|
||||
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 body = MultipartBody.Builder()
|
||||
|
|
@ -123,7 +124,7 @@ class LoginActivity : AppCompatActivity() {
|
|||
Toast.makeText(this@LoginActivity, msg, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e("Login", "解析响应失败", e)
|
||||
Timber.tag("Login").e(e, "解析响应失败")
|
||||
Toast.makeText(this@LoginActivity, "服务器响应异常", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,12 +3,20 @@ package com.stand.standapp
|
|||
import android.os.Bundle
|
||||
import android.view.KeyEvent
|
||||
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.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import timber.log.Timber
|
||||
import com.just.agentweb.AgentWeb
|
||||
import com.just.agentweb.WebChromeClient
|
||||
import com.just.agentweb.WebViewClient
|
||||
import com.stand.standapp.R
|
||||
import com.stand.standapp.AppConfig
|
||||
import com.stand.standapp.AndroidInterface
|
||||
import com.stand.standapp.UpdateManager
|
||||
|
||||
class MainActivity : AppCompatActivity() {
|
||||
|
||||
|
|
@ -38,11 +46,31 @@ class MainActivity : AppCompatActivity() {
|
|||
// 从配置中获取服务器地址
|
||||
val serverUrl = AppConfig.getServerUrl(this)
|
||||
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)
|
||||
.setAgentWebParent(container, LinearLayout.LayoutParams(-1, -1))
|
||||
.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))
|
||||
.createAgentWeb()
|
||||
.ready()
|
||||
|
|
@ -66,7 +94,7 @@ class MainActivity : AppCompatActivity() {
|
|||
settings.domStorageEnabled = true
|
||||
settings.javaScriptEnabled = true
|
||||
} 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")
|
||||
|
|
@ -75,8 +103,7 @@ class MainActivity : AppCompatActivity() {
|
|||
UpdateManager(this).checkUpdate()
|
||||
|
||||
} catch (e: Exception) {
|
||||
Log.e("MainActivity", "Fatal Crash: ${e.message}")
|
||||
e.printStackTrace()
|
||||
Timber.tag("MainActivity").e(e, "Fatal Crash")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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.Looper
|
||||
import android.provider.MediaStore
|
||||
import android.util.Log
|
||||
import timber.log.Timber
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
|
|
@ -63,7 +63,7 @@ class UpdateManager(private val context: Context) {
|
|||
|
||||
client.newCall(request).enqueue(object : Callback {
|
||||
override fun onFailure(call: Call, e: IOException) {
|
||||
Log.e("UpdateManager", "检查更新失败: ${e.message}")
|
||||
Timber.tag("UpdateManager").e(e, "检查更新失败")
|
||||
}
|
||||
|
||||
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() }
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e("UpdateManager", "解析或处理更新结果失败", e)
|
||||
Timber.tag("UpdateManager").e(e, "解析或处理更新结果失败")
|
||||
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")
|
||||
context.startActivity(intent)
|
||||
} catch (e: Exception) {
|
||||
Log.e("UpdateManager", "安装失败", e)
|
||||
Timber.tag("UpdateManager").e(e, "安装失败")
|
||||
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>
|
||||
<string name="app_name">StandAPP</string>
|
||||
<string name="app_name">消防值守台</string>
|
||||
</resources>
|
||||
Loading…
Reference in New Issue