This commit is contained in:
844143714@qq,com 2026-04-30 00:50:59 +08:00
parent 1fb64b7770
commit e943361349
17 changed files with 358 additions and 178 deletions

View File

@ -17,7 +17,7 @@ android {
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
ndk {
abiFilters.addAll(listOf("armeabi-v7a","x86"))
abiFilters.addAll(listOf("armeabi-v7a"))
}
}

View File

@ -21,7 +21,7 @@
' setInterval(function() {',
' var code = prompt("bridge:_poll", "");',
' if (code) { try { eval(code); } catch(e) {} }',
' }, 500);',
' }, 1000);',
'})();'
].join('\n');

View File

@ -4,12 +4,15 @@ import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaRecorder;
import android.util.Log;
import com.stand.standapp.MyApplication;
import java.util.Arrays;
@SuppressWarnings("ResultOfMethodCallIgnored")
public class AudioRecordManager {
private final String TAG = "AudioRecordManager";
private static final int FREQUENCY = 8000;
private static final int FREQUENCY = 8000; //16K采集率
private AudioRecord mRecorder;
private Thread recordThread;
private boolean isStart = false;
@ -22,6 +25,7 @@ public class AudioRecordManager {
}
public void initAudio() {
//实例化一个AudioRecord类
int audioSource = MediaRecorder.AudioSource.MIC;
mRecorder = new AudioRecord(audioSource,
FREQUENCY,
@ -119,6 +123,7 @@ public class AudioRecordManager {
return;
}
mRecorder.startRecording();
int count = 0;
while (isStart) {
if (null != mRecorder) {
bytesRecord = mRecorder.read(tempBuffer, 0, bufferSize);
@ -147,6 +152,7 @@ public class AudioRecordManager {
public void startRecord() {
try {
stopRecord();
startThread();
} catch (Exception e) {
e.printStackTrace();
@ -158,7 +164,6 @@ public class AudioRecordManager {
*/
public void stopRecord() {
try {
isStart = false;
destroyThread();
if (mRecorder != null) {
try {
@ -171,6 +176,7 @@ public class AudioRecordManager {
}
} catch (Exception e) {
e.printStackTrace();
} finally {
}
}

View File

@ -1,8 +1,14 @@
package com.example.kingway.ptt;
import timber.log.Timber;
import android.util.Log;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**解析接收到的AT指令*/
public class CallBackResolution {
@ -41,16 +47,16 @@ public class CallBackResolution {
String ngx = "\\u";
try {
Timber.tag(TAG).i("lisHex-->" + lisHex.toString());
Log.i(TAG, "lisHex-->" + lisHex.toString());
String firstHex = lisHex.get(0);
String errorNum = lisHex.get(1);
Timber.tag(TAG).d("at_OEM_AT_cb firstHex: " + firstHex + ", errorNum: " + errorNum);
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);
pttLoginStatus = errorNum;
pttLoginId = loginId;
Timber.tag(TAG).i("PTT login status: %s, id: %s", pttLoginStatus, pttLoginId);
Log.d(TAG,"PTT login status: "+pttLoginStatus+", id: "+pttLoginId);
// 实时推送状态到前端
com.stand.standapp.MainActivity.executeJs("updatePttLoginStatus('" + pttLoginStatus + "','" + pttLoginId + "')");
CallBackUtil.callBackLoinState(errorNum, loginId);
@ -64,7 +70,6 @@ public class CallBackResolution {
}
break;
case "a2"://账号有视频功能
CallBackUtil.callBackHaveVideo();
break;
@ -81,17 +86,17 @@ public class CallBackResolution {
break;
case "0b"://发起讲话返回状态
Timber.tag(TAG).d("at_OEM_AT_cb ptt speak...");
Log.d(TAG, "at_OEM_AT_cb ptt speak...");
CallBackUtil.callBackSpeakPlaySuccess(errorNum.equals("00")? true : false);
break;
case "0c"://结束讲话返回状态
Timber.tag(TAG).d("at_OEM_AT_cb speak end...");
Log.d(TAG, "at_OEM_AT_cb speak end...");
CallBackUtil.callBackSpeakReleaseSuccess(errorNum.equals("00")? true : false);
break;
case "80"://返回群组信息
Timber.tag(TAG).d("at_OEM_AT_cb get group info: " + lisHex);
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);
@ -114,9 +119,9 @@ public class CallBackResolution {
} finally {
String tempTxt = "";
tempTxt = unicodeToString(strGroup);
Timber.tag(TAG).i(" 组名称 " + tempTxt);
Log.i(TAG, " 组名称 " + tempTxt);
CallBackUtil.callBackGroupInfo(groupId, tempTxt, groupNo, groupCount);
Timber.tag(TAG).d("at_OEM_AT_cb group info groupId: " + groupId + ", groupNo: " + groupNo+", groupCount: " +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);
@ -149,8 +154,6 @@ public class CallBackResolution {
GroupMemberInfoDto infoDto = new GroupMemberInfoDto(memberId, tempTxt, status, memberNo, haveVideo);
if (memberNo == 1) memberInfoDtos.clear();
memberInfoDtos.add(infoDto);
//输出群成员信息memberInfoDtos
Timber.tag(TAG).d("at_OEM_AT_cb group member info: " + infoDto);
}
break;
@ -226,7 +229,7 @@ public class CallBackResolution {
for (int i = 0; i < stringsTalk.size(); i++) {
tempTxt += unicode(stringsTalk.get(i));
}
Timber.tag(TAG).i(" 临时讲话 信息通知的指令 " + tempTxt);
Log.i(TAG, " 临时讲话 信息通知的指令 " + tempTxt);
CallBackUtil.callBackTempCallNotify(tempTxt);
}
break;
@ -259,7 +262,7 @@ public class CallBackResolution {
for (int i = 0; i < stringsJumpGroup.size(); i++) {
userGroupName += unicode(stringsJumpGroup.get(i));
}
Timber.tag(TAG).i("进入群组 " + userGroupName);
Log.i(TAG,"进入群组 " + userGroupName);
indexGroupId = userGroupId;
indexGroupName = userGroupName;
CallBackUtil.callBackNotifyUpdateGroup(userGroupId, userGroupName);
@ -286,7 +289,7 @@ public class CallBackResolution {
String time = strLis[2];
CallBackUtil.callBackNotifyLocationStatus(0, locationMemberId, latitude, longitude, time);
} catch (Exception e) {
Timber.tag(TAG).e(e, "获取定位Exception");
Log.e(TAG, "获取定位Exception-->" + e.getMessage());
}
} else {
CallBackUtil.callBackNotifyLocationStatus(1, "", 0.0, 0.0, "");
@ -340,14 +343,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);
Timber.tag(TAG).i("address-->" + address);
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(",");
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());
@ -387,24 +390,41 @@ public class CallBackResolution {
break;
}
}catch (Exception e){
Timber.tag(TAG).e(e, "at_OEM_AT_cb Exception");
Log.e(TAG,"at_OEM_AT_cb Exception " + e.getMessage());
}
}
/**
* 判断登录状态冲突, 1min内超过5次状态切换则视为登录冲突
* state
* 0 : 离线
* 1 : 登陆中
* 2 : 登陆成功
* 3 : 注销中
* 说明第一次登录 通知状态 先01再02
* 离线再上线 通知状态 先00再01
* tip1min内会出现40次左右0-1切换
*/
public static boolean isLoginConflict(String state) {
Log.d(TAG, " isLoginConflict state: " + state + ",oldLoginSate: " + oldLoginSate + ", logNum: " + logNum);
if (oldLoginSate.equals("00") && state.equals("01")) {
Log.d(TAG, "isLoginConflict first login...");
logNum++;
oldLoginSate = state;
} else if (oldLoginSate.equals("01") && state.equals("00")) {
Log.d(TAG, "isLoginConflict login state changed...");
logNum++;
oldLoginSate = state;
}
if (1 == logNum) {
offlineTime = System.currentTimeMillis();
}
Log.d(TAG, "isLoginConflict logNum: " + logNum + ", offlineTime: " + offlineTime);
if (logNum > 4) {
Log.d(TAG, "isLoginConflict current time: " + System.currentTimeMillis() + ", diffTime: " + (System.currentTimeMillis() - offlineTime));
if (System.currentTimeMillis() - offlineTime >= 60 * 1000) {
Log.d(TAG, "isLoginConflict login state changed: " + logNum + " in 1min");
return true;
}
}
@ -415,6 +435,8 @@ public class CallBackResolution {
try {
String[] txt = sData.split("\"");
String data = txt[1];
Log.d(TAG, "at_Play_TTS_cb sData: " + sData);
String ngx = "\\u";
List<String> strings = new ArrayList<>();
@ -443,18 +465,23 @@ public class CallBackResolution {
for (int k = 0; k < strings.size(); k++) {
tempTxt += unicode(strings.get(k));
}
Log.i(TAG,"at_Play_TTS_cb " + tempTxt);
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);
ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
Runnable task = () -> {
Log.d(TAG, "callBackLogin send tcp and udp");
SendAtUtil.sendUDP();
SendAtUtil.sendTCP();
};
executor.scheduleAtFixedRate(task, 0, 40, java.util.concurrent.TimeUnit.SECONDS);
executor.scheduleAtFixedRate(task, 0, 40, TimeUnit.SECONDS);
} else if (tempTxt.contains("账号已更新")) {
} else if (tempTxt.contains("账号或密码错误")) {
CallBackUtil.callBackLogin(false, "");
}else if (tempTxt.contains("请配置账号")) {
}
}
} catch (Exception e) {
e.printStackTrace();
@ -463,8 +490,9 @@ public class CallBackResolution {
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);
Pattern pattern = Pattern.compile("(\\\\u(\\p{XDigit}{4}))");
Matcher matcher = pattern.matcher(str);
char ch;
while (matcher.find()) {
String group = matcher.group(2);
@ -480,9 +508,13 @@ public class CallBackResolution {
StringBuilder str = new StringBuilder();
for (int i = 0; i < t; i++) {
String s = hex.substring(i * 6, (i + 1) * 6);
// 高位需要补上00再转
String s1 = s.substring(2, 4) + "00";
// 低位直接转
String s2 = s.substring(4);
// 将16进制的string转为int
int n = Integer.valueOf(s1, 16) + Integer.valueOf(s2, 16);
// 将int转换为字符
char[] chars = Character.toChars(n);
str.append(new String(chars));
}

View File

@ -1,7 +1,7 @@
package com.example.kingway.ptt;
import android.os.Handler;
import android.util.Log;
import java.util.ArrayList;
import java.util.List;

View File

@ -2,7 +2,11 @@ package com.example.kingway.ptt;
/**
* <pre>
* author : GaryLiang
* e-mail : 595184932@qq.com
* time : 2017/11/21 上午9:09
* desc :
* version: 1.0
* </pre>
*/

View File

@ -4,7 +4,11 @@ import java.io.Serializable;
/**
* <pre>
* author : GaryLiang
* e-mail : 595184932@qq.com
* time : 2017/11/21 上午9:09
* desc : 成员
* version: 1.0
* </pre>
*/

View File

@ -1,60 +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;
try {
pNative = new pttNative();
pNative.pttNativeSetPackageName(pageName);
pNative.pttNativeSetAtCallback();
pNative.pttNativeSetPlayCallback();
pNative.pttNativeSetPalyStartCallback();
pNative.pttNativeSetPlayStopCallback();
pNative.pttNativeSetRecordStartCallback();
pNative.pttNativeSetRecordStopCallback();
pNative.pttNativeSetTTSCallback();
pNative.pttNativePocTaskStart();
Log.i("PTT_INIT", "PTT init success");
} catch (Throwable e) {
Log.e("PTT_INIT", "Failed to init PTT: " + e.getMessage());
}
}
public static void destroy() {
if (pNative != null) {
pNative.destory();
pNative = null;
}
mContext = null;
mAppInstance = new MyApplication();
CallBackResolution.resetPttLoginStatus();
}
}

View File

@ -1,5 +1,7 @@
package com.example.kingway.ptt;
import com.stand.standapp.MyApplication;
import timber.log.Timber;
/**
@ -16,6 +18,13 @@ public class SendAtUtil {
sendRawCmd(cmd);
}
/**
* 取消登录接口 (清除服务端/本地登录状态)
*/
public static void toCancelLogin() {
send("050000\r\n");
}
/**
* 账号注销接口
*/

View File

@ -1,20 +1,13 @@
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 androidx.appcompat.app.AppCompatActivity;
import com.hjq.permissions.Permission;
import com.hjq.permissions.XXPermissions;
import com.stand.standapp.R;
@ -26,21 +19,22 @@ public class pttActivity extends AppCompatActivity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_ptt);
pttBtn = (Button) findViewById(R.id.pttBtn);
// 初始化 PTT
MyApplication.init(this);
pttBtn = (Button)findViewById(R.id.pttBtn);
XXPermissions.with(this)
.permission(Permission.RECORD_AUDIO)
.request(null);
pttBtn.setOnTouchListener(new View.OnTouchListener() {
pttBtn.setOnTouchListener(new View.OnTouchListener()
{
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) {
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) {
}
else if(motionEvent.getAction() == MotionEvent.ACTION_UP)
{
pttBtn.setBackgroundColor(Color.BLUE);
SendAtUtil.speakRelease();
}
@ -52,6 +46,14 @@ public class pttActivity extends AppCompatActivity {
findViewById(R.id.login).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
SendAtUtil.toLogin("gzzdweb01", "123456", "61.243.1.123", 1, 1);
}
});
findViewById(R.id.login1).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// SendAtUtil.toLogin("gzzdweb01", "123456", "61.243.1.123", 1, 1);
SendAtUtil.toLogin("test004", "123456", "220.154.131.231", 1, 1);
}
@ -80,5 +82,20 @@ public class pttActivity extends AppCompatActivity {
SendAtUtil.jumpGroup("00000001");
}
});
findViewById(R.id.btnCancel1).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
SendAtUtil.toCancelLogin();
}
});
/** 切换群组 */
findViewById(R.id.btnCancel2).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
SendAtUtil.toLogout();
}
});
}
}

View File

@ -37,7 +37,7 @@ public class pttNative {
public void OEM_AT_cb(String sData)
{
Log.e(TAG,"Poc2 AT Callback: " + sData);
int len, i;
int len, i, j=0;
char[] bytes = sData.toCharArray();
List<String> lisHex = new ArrayList<>();
@ -215,9 +215,9 @@ public class pttNative {
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)
.setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION) // 通信场景
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC) // 内容类型
.setLegacyStreamType(AudioManager.STREAM_MUSIC) // 设置通话流类型
.build();
AudioFormat audioFormat = new AudioFormat.Builder()
@ -233,7 +233,7 @@ public class pttNative {
AudioTrack.MODE_STREAM,
AudioManager.AUDIO_SESSION_ID_GENERATE
);
mAudioTrack.setVolume(1.0f);
mAudioTrack.setVolume(1.0f);// 设置音量
}

View File

@ -3,6 +3,7 @@ package com.stand.standapp
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.os.Process
import android.widget.*
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
@ -64,8 +65,30 @@ class LoginActivity : AppCompatActivity() {
// 4. 退出按钮
btnExit.setOnClickListener {
pttLogoutAndDestroy()
finish()
// fullCleanup()
// finishAffinity()
// android.os.Process.killProcess(android.os.Process.myPid())
try {
// com.example.kingway.ptt.MyApplication.init(this@LoginActivity)
// Thread.sleep(500)
// com.example.kingway.ptt.SendAtUtil.toCancelLogin()
// com.example.kingway.ptt.SendAtUtil.toLogout()
} catch (e: Exception) {
Timber.tag("PTT").e(e, "PTT init error")
}
Thread {
try {
Thread.sleep(500)
runOnUiThread {
Toast.makeText(this@LoginActivity, "PTT初始化中", Toast.LENGTH_SHORT).show()
}
Thread.sleep(3000)
com.example.kingway.ptt.SendAtUtil.toLogin("gzz22222", "1111", "61.243.1.123", 1, 1)
// com.example.kingway.ptt.SendAtUtil.toLogin("gzzdweb01", "123456", "61.243.1.123", 1, 1)
} catch (e: Exception) {
Timber.tag("PTT").e(e, "PTT login error")
}
}.start()
}
// 5. 彩蛋:点击底部版权 10 次进入开发者模式
@ -139,9 +162,9 @@ class LoginActivity : AppCompatActivity() {
Toast.makeText(this@LoginActivity, "登录成功", Toast.LENGTH_SHORT).show()
// 1. PTT 引擎初始化并异步登录 (避免阻塞 UI)
// 1. PTT 引擎强制重置后登录 (销毁旧 session确保干净状态)
try {
com.example.kingway.ptt.MyApplication.init(this@LoginActivity)
com.example.kingway.ptt.SendAtUtil.toCancelLogin()
com.example.kingway.ptt.SendAtUtil.toLogout()
} catch (e: Exception) {
Timber.tag("PTT").e(e, "PTT init error")
@ -152,7 +175,7 @@ class LoginActivity : AppCompatActivity() {
runOnUiThread {
Toast.makeText(this@LoginActivity, "PTT初始化中", Toast.LENGTH_SHORT).show()
}
Thread.sleep(1500)
Thread.sleep(3000)
com.example.kingway.ptt.SendAtUtil.toLogin("gzzdweb01", "123456", "61.243.1.123", 1, 1)
} catch (e: Exception) {
Timber.tag("PTT").e(e, "PTT login error")
@ -240,14 +263,24 @@ class LoginActivity : AppCompatActivity() {
.setTitle("提示")
.setMessage("确定要退出应用吗?")
.setPositiveButton("确定") { _, _ ->
pttLogoutAndDestroy()
finish()
fullCleanup()
finishAffinity()
Process.killProcess(Process.myPid())
}
.setNegativeButton("取消", null)
.show()
}
private fun fullCleanup() {
pttLogoutAndDestroy()
}
private fun pttLogoutAndDestroy() {
try {
com.example.kingway.ptt.SendAtUtil.toCancelLogin()
} catch (e: Exception) {
Timber.tag("LoginActivity").e(e, "PTT cancel failed")
}
try {
com.example.kingway.ptt.SendAtUtil.toLogout()
Timber.tag("LoginActivity").i("PTT logout sent")
@ -255,10 +288,32 @@ class LoginActivity : AppCompatActivity() {
Timber.tag("LoginActivity").e(e, "PTT logout failed")
}
try {
com.example.kingway.ptt.MyApplication.destroy()
com.stand.standapp.utils.GpioManager.stopListening()
} catch (e: Exception) {
Timber.tag("LoginActivity").e(e, "GPIO stop failed")
}
try {
com.stand.standapp.utils.GpioManager.stopListening()
} catch (e: Exception) {
Timber.tag("LoginActivity").e(e, "GPIO stop failed")
}
try {
// MyApplication.destroy()
Timber.tag("LoginActivity").i("PTT native destroyed")
} catch (e: Exception) {
Timber.tag("LoginActivity").e(e, "PTT destroy failed")
}
try {
// PrinterManager.shutdown()
Timber.tag("LoginActivity").i("Printer shut down")
} catch (e: Exception) {
Timber.tag("LoginActivity").e(e, "Printer shutdown failed")
}
try {
com.stand.standapp.utils.LogManager.shutdown()
Timber.tag("LoginActivity").i("LogManager shut down")
} catch (e: Exception) {
Timber.tag("LoginActivity").e(e, "LogManager shutdown failed")
}
}
}

View File

@ -61,15 +61,15 @@ class MainActivity : AppCompatActivity() {
.setMessage("您的 PTT 账号已在其他设备登录,当前设备已被迫下线。\n请点击确认退出并重新登录。")
.setCancelable(false)
.setPositiveButton("确认") { _, _ ->
try {
com.example.kingway.ptt.SendAtUtil.toLogout()
com.example.kingway.ptt.MyApplication.destroy()
com.stand.standapp.utils.GpioManager.stopListening()
} catch (_: Exception) {}
val pm = ctx.packageManager
val intent = pm.getLaunchIntentForPackage(ctx.packageName)
intent?.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
ctx.startActivity(intent)
// try {
// com.example.kingway.ptt.SendAtUtil.toLogout()
// com.example.kingway.ptt.MyApplication.destroy()
// com.stand.standapp.utils.GpioManager.stopListening()
// } catch (_: Exception) {}
// val pm = ctx.packageManager
// val intent = pm.getLaunchIntentForPackage(ctx.packageName)
// intent?.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
// ctx.startActivity(intent)
android.os.Process.killProcess(android.os.Process.myPid())
}
.show()
@ -263,32 +263,55 @@ class MainActivity : AppCompatActivity() {
.setTitle("提示")
.setMessage("确定要退出应用吗?")
.setPositiveButton("确定") { _, _ ->
pttLogoutAndDestroy()
finish()
// pttLogoutAndDestroy()
finishAffinity()
android.os.Process.killProcess(android.os.Process.myPid())
}
.setNegativeButton("取消", null)
.show()
}
private fun pttLogoutAndDestroy() {
try {
com.example.kingway.ptt.SendAtUtil.toLogout()
Timber.tag("MainActivity").i("PTT logout sent")
} catch (e: Exception) {
Timber.tag("MainActivity").e(e, "PTT logout failed")
}
try {
com.stand.standapp.utils.GpioManager.stopListening()
} catch (e: Exception) {
Timber.tag("MainActivity").e(e, "GPIO stop failed")
}
try {
com.example.kingway.ptt.MyApplication.destroy()
Timber.tag("MainActivity").i("PTT native destroyed")
} catch (e: Exception) {
Timber.tag("MainActivity").e(e, "PTT destroy failed")
}
}
// private fun pttLogoutAndDestroy() {
// try {
// com.example.kingway.ptt.SendAtUtil.toCancelLogin()
// } catch (e: Exception) {
// Timber.tag("LoginActivity").e(e, "PTT cancel failed")
// }
// try {
// com.example.kingway.ptt.SendAtUtil.toLogout()
// Timber.tag("LoginActivity").i("PTT logout sent")
// } catch (e: Exception) {
// Timber.tag("LoginActivity").e(e, "PTT logout failed")
// }
// try {
// com.stand.standapp.utils.GpioManager.stopListening()
// } catch (e: Exception) {
// Timber.tag("MainActivity").e(e, "GPIO stop failed")
// }
// try {
// com.stand.standapp.utils.GpioManager.stopListening()
// } catch (e: Exception) {
// Timber.tag("LoginActivity").e(e, "GPIO stop failed")
// }
// try {
// com.example.kingway.ptt.MyApplication.destroy()
// Timber.tag("LoginActivity").i("PTT native destroyed")
// } catch (e: Exception) {
// Timber.tag("LoginActivity").e(e, "PTT destroy failed")
// }
// try {
//// PrinterManager.shutdown()
// Timber.tag("LoginActivity").i("Printer shut down")
// } catch (e: Exception) {
// Timber.tag("LoginActivity").e(e, "Printer shutdown failed")
// }
// try {
// com.stand.standapp.utils.LogManager.shutdown()
// Timber.tag("LoginActivity").i("LogManager shut down")
// } catch (e: Exception) {
// Timber.tag("LoginActivity").e(e, "LogManager shutdown failed")
// }
// }
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
if (keyCode == KeyEvent.KEYCODE_BACK) {

View File

@ -1,50 +1,112 @@
package com.stand.standapp
import android.app.Application
import android.util.Log
import com.example.kingway.ptt.pttNative
import com.stand.standapp.utils.LogManager
import com.example.kingway.ptt.MyApplication as PTTMyApplication
import timber.log.Timber
class MyApplication : Application() {
// companion object {
// private var instance: MyApplication? = null
// fun getAppInstance(): MyApplication = instance ?: error("MyApplication not yet initialized")
// }
// override fun onCreate() {
// super.onCreate()
// instance = this
// // 第一步:初始化日志
// LogManager.init(this)
//
// // 初始化 xCrash 以捕获 Native (SO) 崩溃
// xcrash.XCrash.init(this, xcrash.XCrash.InitParameters().apply {
// setAppVersion(packageManager.getPackageInfo(packageName, 0).versionName)
// setLogDir(getExternalFilesDir("tombstones")?.absolutePath)
// setNativeDumpAllThreads(true)
//
// setJavaCallback { logPath, emergency ->
// LogManager.onCrashDetected(logPath, "Java-xCrash", null)
// }
//
// setNativeCallback { logPath, emergency ->
// LogManager.onCrashDetected(logPath, "Native-xCrash", null)
// android.util.Log.e("MyApplication", "NATIVE CRASH DETECTED at: $logPath")
// }
// })
//
// Timber.tag("MyApplication").i("=== Application Starting ===")
// }
// 单例实例
companion object {
private var instance: MyApplication? = null
fun getAppInstance(): MyApplication = instance!!
private lateinit var mApp: MyApplication
@JvmStatic
fun getAppInstance(): MyApplication {
if (pNative == null) {
initPTT()
}
return mApp
}
@JvmStatic
fun initPTT() {
if (pNative != null) return
try {
pNative = pttNative()
pNative?.pttNativeSetPackageName(pageName)
pNative?.pttNativeSetAtCallback()
pNative?.pttNativeSetPlayCallback()
pNative?.pttNativeSetPalyStartCallback()
pNative?.pttNativeSetPlayStopCallback()
pNative?.pttNativeSetRecordStartCallback()
pNative?.pttNativeSetRecordStopCallback()
pNative?.pttNativeSetTTSCallback()
pNative?.pttNativePocTaskStart()
Log.i("PTT_INIT", "PTT init success")
} catch (e: Throwable) {
Log.e("PTT_INIT", "Failed to init PTT: ${e.message}")
}
}
var pageName: String = ""
var pNative: pttNative? = null
}
/**
* 获取 pttNative 实例非空
*/
fun getpNative(): pttNative? {
return pNative
}
override fun onCreate() {
super.onCreate()
mApp = this
pageName = packageName
initPTT()
// 第一步:初始化日志
LogManager.init(this)
// 【核心】初始化 xCrash 以捕获 Native (SO) 崩溃
// 初始化 xCrash 以捕获 Native (SO) 崩溃
xcrash.XCrash.init(this, xcrash.XCrash.InitParameters().apply {
setAppVersion(packageManager.getPackageInfo(packageName, 0).versionName)
setLogDir(getExternalFilesDir("tombstones")?.absolutePath)
setNativeDumpAllThreads(true)
// Java 崩溃回调
setJavaCallback { logPath, emergency ->
LogManager.onCrashDetected(logPath, "Java-xCrash", null)
}
// Native 崩溃回调
setNativeCallback { logPath, emergency ->
LogManager.onCrashDetected(logPath, "Native-xCrash", null)
android.util.Log.e("MyApplication", "NATIVE CRASH DETECTED at: $logPath")
}
})
super.onCreate()
instance = this
Timber.tag("MyApplication").i("=== Application Starting ===")
// try {
// // 第二步:异步或保护性初始化 PTT
// PTTMyApplication.init(this)
// Timber.tag("MyApplication").i("PTT Module init sequence finished")
// } catch (e: Throwable) {
// Timber.tag("MyApplication").e(e, "PTT Module CRASH during init")
// }
}
}

View File

@ -161,6 +161,16 @@ object LogManager {
}
}
fun shutdown() {
Timber.tag("LogManager").i("Shutting down...")
try {
executor.shutdown()
executor.awaitTermination(3, java.util.concurrent.TimeUnit.SECONDS)
} catch (e: Exception) {
Timber.tag("LogManager").e(e, "Executor shutdown failed")
}
}
fun uploadLogs(context: Context, callback: (Boolean, String) -> Unit) {
executor.execute {
val zipPath = zipLogs(context) ?: return@execute callback(false, "打包失败")

View File

@ -53,5 +53,23 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="切换群组" />
<Button
android:id="@+id/btnCancel1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="取消登录" />
<Button
android:id="@+id/btnCancel2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="注销" />
<Button
android:id="@+id/login1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="login1" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>