2022-07-03 08:53:51 +00:00
|
|
|
package com.radio.codec2talkie.app;
|
2020-11-29 18:02:34 +00:00
|
|
|
|
2022-07-06 09:25:18 +00:00
|
|
|
import android.app.Application;
|
2021-02-06 16:08:33 +00:00
|
|
|
import android.content.Context;
|
2021-10-14 12:25:21 +00:00
|
|
|
import android.content.SharedPreferences;
|
2020-11-29 18:02:34 +00:00
|
|
|
import android.media.AudioAttributes;
|
2022-08-10 10:07:35 +00:00
|
|
|
import android.media.AudioDeviceInfo;
|
2020-11-29 18:02:34 +00:00
|
|
|
import android.media.AudioFormat;
|
2022-08-10 10:07:35 +00:00
|
|
|
import android.media.AudioManager;
|
2020-11-29 18:02:34 +00:00
|
|
|
import android.media.AudioRecord;
|
|
|
|
import android.media.AudioTrack;
|
2020-12-03 13:25:02 +00:00
|
|
|
import android.os.Handler;
|
2021-01-30 19:32:25 +00:00
|
|
|
import android.os.Looper;
|
2020-12-03 13:25:02 +00:00
|
|
|
import android.os.Message;
|
2022-07-30 17:55:43 +00:00
|
|
|
import android.os.Process;
|
2021-01-28 14:12:16 +00:00
|
|
|
import android.util.Log;
|
2020-11-29 18:02:34 +00:00
|
|
|
|
2021-10-14 12:25:21 +00:00
|
|
|
import androidx.preference.PreferenceManager;
|
|
|
|
|
2020-11-29 18:02:34 +00:00
|
|
|
import java.io.IOException;
|
2021-01-30 19:32:25 +00:00
|
|
|
import java.util.Timer;
|
|
|
|
import java.util.TimerTask;
|
2020-11-29 18:02:34 +00:00
|
|
|
|
2022-07-04 19:18:49 +00:00
|
|
|
import com.radio.codec2talkie.R;
|
2023-07-03 15:53:02 +00:00
|
|
|
import com.radio.codec2talkie.protocol.aprs.tools.AprsIsData;
|
2022-07-18 17:17:40 +00:00
|
|
|
import com.radio.codec2talkie.protocol.message.TextMessage;
|
2022-07-10 16:55:47 +00:00
|
|
|
import com.radio.codec2talkie.storage.log.LogItem;
|
|
|
|
import com.radio.codec2talkie.storage.log.LogItemRepository;
|
2022-07-03 09:05:00 +00:00
|
|
|
import com.radio.codec2talkie.protocol.ProtocolCallback;
|
2021-01-28 08:47:22 +00:00
|
|
|
import com.radio.codec2talkie.protocol.Protocol;
|
2021-01-28 10:03:30 +00:00
|
|
|
import com.radio.codec2talkie.protocol.ProtocolFactory;
|
2022-07-02 09:12:07 +00:00
|
|
|
import com.radio.codec2talkie.protocol.position.Position;
|
2021-10-14 12:25:21 +00:00
|
|
|
import com.radio.codec2talkie.settings.PreferenceKeys;
|
2022-07-18 19:24:48 +00:00
|
|
|
import com.radio.codec2talkie.storage.message.MessageItemRepository;
|
2022-07-23 18:54:25 +00:00
|
|
|
import com.radio.codec2talkie.storage.position.PositionItemRepository;
|
2022-09-03 11:11:29 +00:00
|
|
|
import com.radio.codec2talkie.storage.station.StationItemRepository;
|
2021-01-27 10:13:07 +00:00
|
|
|
import com.radio.codec2talkie.tools.AudioTools;
|
2021-01-26 18:53:37 +00:00
|
|
|
import com.radio.codec2talkie.transport.Transport;
|
2021-01-27 10:52:54 +00:00
|
|
|
import com.radio.codec2talkie.transport.TransportFactory;
|
2020-11-29 19:37:39 +00:00
|
|
|
|
2022-07-03 08:53:51 +00:00
|
|
|
public class AppWorker extends Thread {
|
2020-11-29 18:02:34 +00:00
|
|
|
|
2022-07-03 08:53:51 +00:00
|
|
|
private static final String TAG = AppWorker.class.getSimpleName();
|
2020-12-12 09:25:56 +00:00
|
|
|
|
2021-10-09 17:27:17 +00:00
|
|
|
private static final int AUDIO_MIN_LEVEL = -70;
|
|
|
|
private static final int AUDIO_MAX_LEVEL = 0;
|
2022-07-09 11:33:27 +00:00
|
|
|
private static final int AUDIO_SAMPLE_SIZE = 8000;
|
2021-01-31 12:07:20 +00:00
|
|
|
|
2022-07-09 11:33:27 +00:00
|
|
|
private static final int PROCESS_INTERVAL_MS = 20;
|
|
|
|
private static final int LISTEN_AFTER_MS = 1500;
|
2020-12-04 20:20:14 +00:00
|
|
|
|
2022-07-09 11:33:27 +00:00
|
|
|
private boolean _needTransmission = false;
|
2022-07-05 11:59:15 +00:00
|
|
|
private AppMessage _currentStatus = AppMessage.EV_DISCONNECTED;
|
2020-12-03 08:59:13 +00:00
|
|
|
|
2021-01-28 08:47:22 +00:00
|
|
|
private final Protocol _protocol;
|
2021-01-27 10:52:54 +00:00
|
|
|
private final Transport _transport;
|
2020-11-29 18:02:34 +00:00
|
|
|
|
2022-06-29 12:16:05 +00:00
|
|
|
private final int _codec2Mode;
|
|
|
|
|
2021-01-26 18:53:37 +00:00
|
|
|
// input data, bt -> audio
|
2021-01-27 10:52:54 +00:00
|
|
|
private AudioTrack _systemAudioPlayer;
|
2020-12-02 19:45:03 +00:00
|
|
|
|
2020-12-02 16:33:38 +00:00
|
|
|
// output data., mic -> bt
|
2021-01-27 10:52:54 +00:00
|
|
|
private AudioRecord _systemAudioRecorder;
|
2022-08-11 09:01:02 +00:00
|
|
|
private short[] _recordAudioBuffer;
|
2020-12-02 19:45:03 +00:00
|
|
|
|
2020-12-04 20:20:14 +00:00
|
|
|
// callbacks
|
2022-08-20 12:52:10 +00:00
|
|
|
private final Handler _onWorkerStateChanged;
|
2021-01-31 12:03:18 +00:00
|
|
|
private Handler _onMessageReceived;
|
|
|
|
private final Timer _processPeriodicTimer;
|
2020-12-04 13:50:01 +00:00
|
|
|
|
2021-01-30 19:32:25 +00:00
|
|
|
// listen timer
|
|
|
|
private Timer _listenTimer;
|
|
|
|
|
2022-07-18 19:24:48 +00:00
|
|
|
// storage integration
|
2022-07-06 09:25:18 +00:00
|
|
|
private final LogItemRepository _logItemRepository;
|
2022-07-18 19:24:48 +00:00
|
|
|
private final MessageItemRepository _messageItemRepository;
|
2022-07-23 18:54:25 +00:00
|
|
|
private final PositionItemRepository _positionItemRepository;
|
2022-09-03 11:11:29 +00:00
|
|
|
private final StationItemRepository _stationItemRepository;
|
2022-07-06 09:25:18 +00:00
|
|
|
|
2021-02-10 15:49:33 +00:00
|
|
|
private final Context _context;
|
2021-10-14 12:25:21 +00:00
|
|
|
private final SharedPreferences _sharedPreferences;
|
2021-02-06 16:08:33 +00:00
|
|
|
|
2022-07-04 19:18:49 +00:00
|
|
|
public AppWorker(TransportFactory.TransportType transportType,
|
2022-08-20 12:52:10 +00:00
|
|
|
Handler onWorkerStateChanged, Context context) throws IOException {
|
|
|
|
_onWorkerStateChanged = onWorkerStateChanged;
|
2020-11-29 18:02:34 +00:00
|
|
|
|
2021-02-06 16:08:33 +00:00
|
|
|
_context = context;
|
2021-10-14 12:25:21 +00:00
|
|
|
_sharedPreferences = PreferenceManager.getDefaultSharedPreferences(_context);
|
2021-02-06 16:08:33 +00:00
|
|
|
|
2022-07-04 19:18:49 +00:00
|
|
|
String codec2ModeName = _sharedPreferences.getString(PreferenceKeys.CODEC2_MODE, _context.getResources().getStringArray(R.array.codec2_modes)[0]);
|
|
|
|
_codec2Mode = AudioTools.extractCodec2ModeId(codec2ModeName);
|
|
|
|
|
2022-07-29 14:54:24 +00:00
|
|
|
_logItemRepository = new LogItemRepository((Application)context);
|
|
|
|
_messageItemRepository = new MessageItemRepository((Application)context);
|
|
|
|
_positionItemRepository = new PositionItemRepository((Application)context);
|
2022-09-03 11:11:29 +00:00
|
|
|
_stationItemRepository = new StationItemRepository((Application)context);
|
2022-07-29 14:54:24 +00:00
|
|
|
|
2022-07-28 20:29:36 +00:00
|
|
|
_transport = TransportFactory.create(transportType, context);
|
2022-07-04 19:18:49 +00:00
|
|
|
_protocol = ProtocolFactory.create(_codec2Mode, context);
|
2021-01-27 10:52:54 +00:00
|
|
|
|
2021-01-31 12:03:18 +00:00
|
|
|
_processPeriodicTimer = new Timer();
|
2022-06-29 12:16:05 +00:00
|
|
|
|
2022-08-16 07:59:33 +00:00
|
|
|
int audioSource = Integer.parseInt(_sharedPreferences.getString(PreferenceKeys.APP_AUDIO_SOURCE, "6"));
|
|
|
|
int audioDestination = Integer.parseInt(_sharedPreferences.getString(PreferenceKeys.APP_AUDIO_DESTINATION, "1"));
|
|
|
|
constructSystemAudioDevices(transportType, audioSource, audioDestination);
|
2021-01-27 10:52:54 +00:00
|
|
|
}
|
|
|
|
|
2022-08-16 07:59:33 +00:00
|
|
|
private void constructSystemAudioDevices(TransportFactory.TransportType transportType, int audioSource, int audioDestination) {
|
2020-12-04 20:20:14 +00:00
|
|
|
int _audioRecorderMinBufferSize = AudioRecord.getMinBufferSize(
|
2020-12-03 17:17:11 +00:00
|
|
|
AUDIO_SAMPLE_SIZE,
|
2020-11-29 18:02:34 +00:00
|
|
|
AudioFormat.CHANNEL_IN_MONO,
|
|
|
|
AudioFormat.ENCODING_PCM_16BIT);
|
2021-01-27 10:52:54 +00:00
|
|
|
_systemAudioRecorder = new AudioRecord(
|
2021-10-31 18:35:45 +00:00
|
|
|
audioSource,
|
2020-12-03 17:17:11 +00:00
|
|
|
AUDIO_SAMPLE_SIZE,
|
2020-11-29 19:37:39 +00:00
|
|
|
AudioFormat.CHANNEL_IN_MONO,
|
2020-11-29 18:02:34 +00:00
|
|
|
AudioFormat.ENCODING_PCM_16BIT,
|
2020-12-10 21:30:43 +00:00
|
|
|
10 * _audioRecorderMinBufferSize);
|
2020-11-29 18:02:34 +00:00
|
|
|
|
2020-12-04 20:20:14 +00:00
|
|
|
int _audioPlayerMinBufferSize = AudioTrack.getMinBufferSize(
|
2020-12-03 17:17:11 +00:00
|
|
|
AUDIO_SAMPLE_SIZE,
|
2020-11-29 18:02:34 +00:00
|
|
|
AudioFormat.CHANNEL_OUT_MONO,
|
|
|
|
AudioFormat.ENCODING_PCM_16BIT);
|
2021-01-27 10:52:54 +00:00
|
|
|
_systemAudioPlayer = new AudioTrack.Builder()
|
2020-11-29 18:02:34 +00:00
|
|
|
.setAudioAttributes(new AudioAttributes.Builder()
|
2022-08-16 07:59:33 +00:00
|
|
|
.setUsage(audioDestination)
|
2020-12-10 21:30:43 +00:00
|
|
|
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
|
2020-11-29 18:02:34 +00:00
|
|
|
.build())
|
|
|
|
.setAudioFormat(new AudioFormat.Builder()
|
|
|
|
.setEncoding(AudioFormat.ENCODING_PCM_16BIT)
|
2020-12-03 17:17:11 +00:00
|
|
|
.setSampleRate(AUDIO_SAMPLE_SIZE)
|
2020-11-29 18:02:34 +00:00
|
|
|
.setChannelMask(AudioFormat.CHANNEL_OUT_MONO)
|
|
|
|
.build())
|
2020-12-04 13:50:01 +00:00
|
|
|
.setTransferMode(AudioTrack.MODE_STREAM)
|
2020-12-10 21:30:43 +00:00
|
|
|
.setBufferSizeInBytes(10 * _audioPlayerMinBufferSize)
|
2020-11-29 18:02:34 +00:00
|
|
|
.build();
|
2022-08-10 10:07:35 +00:00
|
|
|
|
2022-08-10 10:41:23 +00:00
|
|
|
// Use built in mic and speaker for speech when sound modem is in use
|
|
|
|
if (transportType == TransportFactory.TransportType.SOUND_MODEM) {
|
2022-08-16 07:59:33 +00:00
|
|
|
selectBuiltinMicAndSpeakerEarpiece(audioDestination != AudioAttributes.USAGE_VOICE_COMMUNICATION);
|
2022-08-10 10:41:23 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void selectBuiltinMicAndSpeakerEarpiece(boolean isSpeakerOutput) {
|
|
|
|
AudioManager audioManager = (AudioManager)_context.getSystemService(Context.AUDIO_SERVICE);
|
|
|
|
|
|
|
|
for (AudioDeviceInfo inputDevice : audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)) {
|
|
|
|
boolean isBuiltIn = inputDevice.getType() == AudioDeviceInfo.TYPE_BUILTIN_MIC;
|
|
|
|
Log.i(TAG, "input device: " + isBuiltIn + " " + inputDevice.getProductName() + " " + inputDevice.getType());
|
|
|
|
if (isBuiltIn) {
|
|
|
|
boolean isSet = _systemAudioRecorder.setPreferredDevice(inputDevice);
|
|
|
|
if (!isSet)
|
|
|
|
Log.w(TAG, "cannot select input " + inputDevice.getProductName());
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-10 10:07:35 +00:00
|
|
|
for (AudioDeviceInfo outputDevice : audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS)) {
|
2022-08-10 10:41:23 +00:00
|
|
|
boolean isBuiltIn = outputDevice.getType() == (isSpeakerOutput ? AudioDeviceInfo.TYPE_BUILTIN_SPEAKER : AudioDeviceInfo.TYPE_BUILTIN_EARPIECE);
|
|
|
|
Log.i(TAG, "output device: " + isBuiltIn + " " + outputDevice.getProductName() + " " + outputDevice.getType());
|
|
|
|
if (isBuiltIn) {
|
|
|
|
boolean isSet = _systemAudioPlayer.setPreferredDevice(outputDevice);
|
|
|
|
if (!isSet)
|
|
|
|
Log.w(TAG, "cannot select output " + outputDevice.getProductName());
|
|
|
|
break;
|
|
|
|
}
|
2022-08-10 10:07:35 +00:00
|
|
|
}
|
2020-12-03 17:17:11 +00:00
|
|
|
}
|
|
|
|
|
2021-01-31 12:03:18 +00:00
|
|
|
public static int getAudioMinLevel() {
|
|
|
|
return AUDIO_MIN_LEVEL;
|
|
|
|
}
|
|
|
|
|
|
|
|
public static int getAudioMaxLevel() {
|
|
|
|
return AUDIO_MAX_LEVEL;
|
|
|
|
}
|
|
|
|
|
2022-07-08 08:38:55 +00:00
|
|
|
public String getTransportName() {
|
|
|
|
return _transport.name();
|
|
|
|
}
|
|
|
|
|
2022-07-05 07:47:58 +00:00
|
|
|
public void startReceive() {
|
2022-07-09 11:33:27 +00:00
|
|
|
_needTransmission = false;
|
2021-01-31 12:03:18 +00:00
|
|
|
}
|
|
|
|
|
2022-07-05 07:47:58 +00:00
|
|
|
public void startTransmit() {
|
2022-07-09 11:33:27 +00:00
|
|
|
_needTransmission = true;
|
2021-01-31 12:03:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public void stopRunning() {
|
2022-07-05 11:59:15 +00:00
|
|
|
if (_currentStatus == AppMessage.EV_DISCONNECTED) return;
|
2022-07-03 08:53:51 +00:00
|
|
|
Log.i(TAG, "stopRunning()");
|
|
|
|
Message msg = new Message();
|
2022-07-05 11:59:15 +00:00
|
|
|
msg.what = AppMessage.CMD_QUIT.toInt();
|
2022-07-03 08:53:51 +00:00
|
|
|
_onMessageReceived.sendMessage(msg);
|
|
|
|
}
|
|
|
|
|
2022-07-09 11:33:27 +00:00
|
|
|
public void sendPositionToTnc(Position position) {
|
2022-07-05 11:59:15 +00:00
|
|
|
if (_currentStatus == AppMessage.EV_DISCONNECTED) return;
|
2022-07-03 08:53:51 +00:00
|
|
|
Message msg = new Message();
|
2022-07-09 11:33:27 +00:00
|
|
|
msg.what = AppMessage.CMD_SEND_LOCATION_TO_TNC.toInt();
|
2022-07-03 12:19:07 +00:00
|
|
|
msg.obj = position;
|
2022-07-23 15:17:09 +00:00
|
|
|
Log.i(TAG, String.format("Position sent: %s, lat: %f, lon: %f, course: %f, speed: %f, alt: %f",
|
|
|
|
position.maidenHead, position.latitude, position.longitude,
|
2022-07-22 14:26:45 +00:00
|
|
|
position.bearingDegrees, position.speedMetersPerSecond, position.altitudeMeters));
|
2022-07-03 08:53:51 +00:00
|
|
|
_onMessageReceived.sendMessage(msg);
|
2021-01-31 12:03:18 +00:00
|
|
|
}
|
|
|
|
|
2022-07-18 17:17:40 +00:00
|
|
|
public void sendTextMessage(TextMessage textMessage) {
|
|
|
|
Message msg = Message.obtain();
|
|
|
|
msg.what = AppMessage.CMD_SEND_MESSAGE.toInt();
|
|
|
|
msg.obj = textMessage;
|
|
|
|
_onMessageReceived.sendMessage(msg);
|
|
|
|
}
|
|
|
|
|
2022-07-05 11:59:15 +00:00
|
|
|
private void sendStatusUpdate(AppMessage newStatus, String note) {
|
2022-07-03 17:49:11 +00:00
|
|
|
|
2021-01-31 18:22:47 +00:00
|
|
|
if (newStatus != _currentStatus) {
|
|
|
|
_currentStatus = newStatus;
|
|
|
|
Message msg = Message.obtain();
|
2022-07-05 11:59:15 +00:00
|
|
|
msg.what = newStatus.toInt();
|
2022-07-01 12:18:38 +00:00
|
|
|
if (note != null) {
|
|
|
|
msg.obj = note;
|
|
|
|
}
|
2022-08-20 12:52:10 +00:00
|
|
|
_onWorkerStateChanged.sendMessage(msg);
|
2021-01-31 18:22:47 +00:00
|
|
|
}
|
2022-07-05 11:59:15 +00:00
|
|
|
if (newStatus != AppMessage.EV_LISTENING) {
|
2022-07-03 17:49:11 +00:00
|
|
|
restartListening();
|
|
|
|
}
|
2020-12-10 14:25:11 +00:00
|
|
|
}
|
|
|
|
|
2021-02-06 11:45:09 +00:00
|
|
|
private void sendRxRadioLevelUpdate(int rssi, int snr) {
|
|
|
|
Message msg = Message.obtain();
|
2022-07-05 11:59:15 +00:00
|
|
|
msg.what = AppMessage.EV_RX_RADIO_LEVEL.toInt();
|
2021-02-06 11:45:09 +00:00
|
|
|
msg.arg1 = rssi;
|
|
|
|
msg.arg2 = snr;
|
2022-08-20 12:52:10 +00:00
|
|
|
_onWorkerStateChanged.sendMessage(msg);
|
2021-02-06 11:45:09 +00:00
|
|
|
}
|
|
|
|
|
2022-12-17 21:58:44 +00:00
|
|
|
private void sendTelemetryUpdate(int batVoltage) {
|
|
|
|
Message msg = Message.obtain();
|
|
|
|
msg.what = AppMessage.EV_TELEMETRY.toInt();
|
|
|
|
msg.arg1 = batVoltage;
|
|
|
|
_onWorkerStateChanged.sendMessage(msg);
|
|
|
|
}
|
2021-01-27 10:52:54 +00:00
|
|
|
private void sendRxAudioLevelUpdate(short [] pcmAudioSamples) {
|
2020-12-09 17:10:01 +00:00
|
|
|
Message msg = Message.obtain();
|
2022-07-05 11:59:15 +00:00
|
|
|
msg.what = AppMessage.EV_RX_LEVEL.toInt();
|
2021-01-27 10:52:54 +00:00
|
|
|
msg.arg1 = AudioTools.getSampleLevelDb(pcmAudioSamples);
|
2022-08-20 12:52:10 +00:00
|
|
|
_onWorkerStateChanged.sendMessage(msg);
|
2021-01-27 10:52:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
private void sendTxAudioLevelUpdate(short [] pcmAudioSamples) {
|
|
|
|
Message msg = Message.obtain();
|
2022-07-05 11:59:15 +00:00
|
|
|
msg.what = AppMessage.EV_TX_LEVEL.toInt();
|
2021-01-27 10:13:07 +00:00
|
|
|
msg.arg1 = AudioTools.getSampleLevelDb(pcmAudioSamples);
|
2022-08-20 12:52:10 +00:00
|
|
|
_onWorkerStateChanged.sendMessage(msg);
|
2020-12-09 17:10:01 +00:00
|
|
|
}
|
|
|
|
|
2021-01-27 10:13:07 +00:00
|
|
|
private void recordAndSendAudioFrame() throws IOException {
|
2022-06-29 12:16:05 +00:00
|
|
|
_systemAudioRecorder.read(_recordAudioBuffer, 0, _recordAudioBuffer.length);
|
2022-07-01 14:18:37 +00:00
|
|
|
_protocol.sendPcmAudio(null, null, _codec2Mode, _recordAudioBuffer);
|
2020-12-02 19:45:03 +00:00
|
|
|
}
|
|
|
|
|
2022-07-03 09:05:00 +00:00
|
|
|
private final ProtocolCallback _protocolCallback = new ProtocolCallback() {
|
2022-06-29 19:23:02 +00:00
|
|
|
@Override
|
2022-07-02 09:12:07 +00:00
|
|
|
protected void onReceivePosition(Position position) {
|
2023-07-04 17:33:26 +00:00
|
|
|
Log.i(TAG, String.format("Position received: %s→%s, %s, lat: %f, lon: %f, course: %f, speed: %f, alt: %f, sym: %s, range: %.2f, status: %s, comment: %s",
|
|
|
|
position.srcCallsign, position.dstCallsign, position.maidenHead, position.latitude, position.longitude,
|
2022-07-23 13:38:57 +00:00
|
|
|
position.bearingDegrees, position.speedMetersPerSecond, position.altitudeMeters,
|
2022-09-01 16:18:31 +00:00
|
|
|
position.symbolCode, position.rangeMiles, position.status, position.comment));
|
2022-09-02 11:26:21 +00:00
|
|
|
_positionItemRepository.upsertPositionItem(position.toPositionItem(false));
|
2022-09-03 11:11:29 +00:00
|
|
|
_stationItemRepository.upsertStationItem(position.toStationItem());
|
2022-07-24 14:00:56 +00:00
|
|
|
|
|
|
|
String note = (position.srcCallsign == null ? "UNK" : position.srcCallsign) + "→" +
|
|
|
|
(position.dstCallsign == null ? "UNK" : position.dstCallsign);
|
|
|
|
sendStatusUpdate(AppMessage.EV_POSITION_RECEIVED, note);
|
2022-06-29 19:23:02 +00:00
|
|
|
}
|
|
|
|
|
2021-01-28 08:47:22 +00:00
|
|
|
@Override
|
2022-06-29 12:16:05 +00:00
|
|
|
protected void onReceivePcmAudio(String src, String dst, int codec, short[] pcmFrame) {
|
2022-07-01 12:18:38 +00:00
|
|
|
String note = (src == null ? "UNK" : src) + "→" + (dst == null ? "UNK" : dst);
|
2022-07-10 16:22:29 +00:00
|
|
|
sendStatusUpdate(AppMessage.EV_VOICE_RECEIVED, note);
|
2022-06-29 12:16:05 +00:00
|
|
|
sendRxAudioLevelUpdate(pcmFrame);
|
2022-08-10 10:07:35 +00:00
|
|
|
if (_systemAudioPlayer.getPlayState() != AudioTrack.PLAYSTATE_PLAYING)
|
|
|
|
_systemAudioPlayer.play();
|
2022-06-29 12:16:05 +00:00
|
|
|
_systemAudioPlayer.write(pcmFrame, 0, pcmFrame.length);
|
2022-08-10 10:07:35 +00:00
|
|
|
_systemAudioPlayer.stop();
|
2022-06-29 12:16:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
protected void onReceiveCompressedAudio(String src, String dst, int codec2Mode, byte[] audioFrame) {
|
2022-06-29 21:13:48 +00:00
|
|
|
throw new UnsupportedOperationException();
|
2021-01-28 08:47:22 +00:00
|
|
|
}
|
2021-02-05 19:55:12 +00:00
|
|
|
|
2022-07-18 19:24:48 +00:00
|
|
|
@Override
|
|
|
|
protected void onReceiveTextMessage(TextMessage textMessage) {
|
|
|
|
String note = (textMessage.src == null ? "UNK" : textMessage.src) + "→" +
|
|
|
|
(textMessage.dst == null ? "UNK" : textMessage.dst);
|
2022-07-19 13:53:15 +00:00
|
|
|
sendStatusUpdate(AppMessage.EV_TEXT_MESSAGE_RECEIVED, note + ": " + textMessage.text);
|
2023-07-16 08:12:55 +00:00
|
|
|
if (textMessage.isAutoReply()) {
|
|
|
|
// TODO, acknowledge or reject message with the given (src, dst, ackId)
|
|
|
|
} else {
|
|
|
|
_messageItemRepository.insertMessageItem(textMessage.toMessageItem(false));
|
|
|
|
}
|
2022-07-19 08:57:15 +00:00
|
|
|
Log.i(TAG, "message received: " + textMessage.text);
|
2022-07-18 19:24:48 +00:00
|
|
|
}
|
|
|
|
|
2022-06-26 13:45:43 +00:00
|
|
|
@Override
|
2022-08-19 16:53:43 +00:00
|
|
|
protected void onReceiveData(String src, String dst, String path, byte[] data) {
|
2022-07-03 17:49:11 +00:00
|
|
|
String note = (src == null ? "UNK" : src) + "→" + (dst == null ? "UNK" : dst);
|
2022-07-10 16:22:29 +00:00
|
|
|
sendStatusUpdate(AppMessage.EV_DATA_RECEIVED, note);
|
2022-06-26 13:45:43 +00:00
|
|
|
}
|
|
|
|
|
2021-02-05 19:55:12 +00:00
|
|
|
@Override
|
2022-07-01 08:51:55 +00:00
|
|
|
protected void onReceiveSignalLevel(short rssi, short snr) {
|
|
|
|
sendRxRadioLevelUpdate(rssi, snr);
|
2021-02-05 19:55:12 +00:00
|
|
|
}
|
2021-10-14 20:21:11 +00:00
|
|
|
|
2022-12-17 21:58:44 +00:00
|
|
|
@Override
|
|
|
|
protected void onReceiveTelemetry(int batVoltage) {
|
|
|
|
sendTelemetryUpdate(batVoltage);
|
|
|
|
}
|
|
|
|
|
2022-07-01 14:39:45 +00:00
|
|
|
@Override
|
|
|
|
protected void onReceiveLog(String logData) {
|
2022-08-21 12:47:30 +00:00
|
|
|
Log.i(TAG, "RX-LOG: " + logData);
|
2022-07-06 08:32:31 +00:00
|
|
|
storeLogData(logData, false);
|
2022-07-01 14:39:45 +00:00
|
|
|
}
|
|
|
|
|
2022-07-01 14:18:37 +00:00
|
|
|
@Override
|
|
|
|
protected void onTransmitPcmAudio(String src, String dst, int codec, short[] frame) {
|
|
|
|
String note = (src == null ? "UNK" : src) + "→" + (dst == null ? "UNK" : dst);
|
2022-07-10 16:22:29 +00:00
|
|
|
sendStatusUpdate(AppMessage.EV_TRANSMITTED_VOICE, note);
|
2022-07-01 14:18:37 +00:00
|
|
|
sendTxAudioLevelUpdate(frame);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
protected void onTransmitCompressedAudio(String src, String dst, int codec, byte[] frame) {
|
|
|
|
throw new UnsupportedOperationException();
|
|
|
|
}
|
|
|
|
|
2022-07-18 19:24:48 +00:00
|
|
|
@Override
|
|
|
|
protected void onTransmitTextMessage(TextMessage textMessage) {
|
|
|
|
String note = (textMessage.src == null ? "UNK" : textMessage.src) + "→" +
|
|
|
|
(textMessage.dst == null ? "UNK" : textMessage.dst);
|
2022-07-18 20:08:05 +00:00
|
|
|
sendStatusUpdate(AppMessage.EV_TEXT_MESSAGE_TRANSMITTED, note);
|
2023-07-16 08:12:55 +00:00
|
|
|
if (!textMessage.isAutoReply()) {
|
|
|
|
_messageItemRepository.insertMessageItem(textMessage.toMessageItem(true));
|
|
|
|
}
|
2022-07-18 19:24:48 +00:00
|
|
|
}
|
|
|
|
|
2022-07-31 14:03:31 +00:00
|
|
|
@Override
|
|
|
|
protected void onTransmitPosition(Position position) {
|
2022-09-03 09:28:47 +00:00
|
|
|
_positionItemRepository.upsertPositionItem(position.toPositionItem(true));
|
2022-09-03 11:11:29 +00:00
|
|
|
_stationItemRepository.upsertStationItem(position.toStationItem());
|
2022-07-31 14:03:31 +00:00
|
|
|
}
|
|
|
|
|
2022-07-01 14:18:37 +00:00
|
|
|
@Override
|
2022-08-19 16:53:43 +00:00
|
|
|
protected void onTransmitData(String src, String dst, String path, byte[] data) {
|
2022-07-01 14:18:37 +00:00
|
|
|
String note = (src == null ? "UNK" : src) + "→" + (dst == null ? "UNK" : dst);
|
2022-07-10 16:22:29 +00:00
|
|
|
sendStatusUpdate(AppMessage.EV_TRANSMITTED_VOICE, note);
|
2022-07-01 14:18:37 +00:00
|
|
|
}
|
|
|
|
|
2022-07-01 14:39:45 +00:00
|
|
|
@Override
|
|
|
|
protected void onTransmitLog(String logData) {
|
2022-08-21 12:47:30 +00:00
|
|
|
Log.i(TAG, "TX-LOG: " + logData);
|
2022-07-06 08:32:31 +00:00
|
|
|
storeLogData(logData, true);
|
2022-07-01 14:39:45 +00:00
|
|
|
}
|
|
|
|
|
2021-10-14 20:21:11 +00:00
|
|
|
@Override
|
2021-10-19 18:20:11 +00:00
|
|
|
protected void onProtocolRxError() {
|
2022-07-05 11:59:15 +00:00
|
|
|
sendStatusUpdate(AppMessage.EV_RX_ERROR, null);
|
2021-10-19 18:20:11 +00:00
|
|
|
Log.e(TAG, "Protocol RX error");
|
2021-10-14 20:21:11 +00:00
|
|
|
}
|
2022-07-01 14:18:37 +00:00
|
|
|
|
|
|
|
@Override
|
|
|
|
protected void onProtocolTxError() {
|
2022-07-05 11:59:15 +00:00
|
|
|
sendStatusUpdate(AppMessage.EV_TX_ERROR, null);
|
2022-07-01 14:18:37 +00:00
|
|
|
Log.e(TAG, "Protocol TX error");
|
|
|
|
}
|
2021-01-28 08:47:22 +00:00
|
|
|
};
|
|
|
|
|
2023-07-03 15:53:02 +00:00
|
|
|
private void storeLogData(String logData, boolean isTransmit) {
|
|
|
|
AprsIsData aprsIsData = AprsIsData.fromString(logData);
|
|
|
|
if (aprsIsData != null) {
|
2022-07-06 08:32:31 +00:00
|
|
|
LogItem logItem = new LogItem();
|
|
|
|
logItem.setTimestampEpoch(System.currentTimeMillis());
|
|
|
|
logItem.setIsTransmit(isTransmit);
|
2023-07-03 15:53:02 +00:00
|
|
|
logItem.setSrcCallsign(aprsIsData.src);
|
|
|
|
logItem.setLogLine(logData);
|
2022-07-06 09:25:18 +00:00
|
|
|
_logItemRepository.insertLogItem(logItem);
|
2022-09-03 11:11:29 +00:00
|
|
|
_stationItemRepository.upsertStationItem(logItem.toStationItem());
|
2023-07-03 15:53:02 +00:00
|
|
|
if (aprsIsData.hasThirdParty()) {
|
|
|
|
LogItem logItemThirdParty = new LogItem();
|
|
|
|
logItemThirdParty.setTimestampEpoch(System.currentTimeMillis());
|
|
|
|
logItemThirdParty.setIsTransmit(isTransmit);
|
|
|
|
logItemThirdParty.setSrcCallsign(aprsIsData.thirdParty.src);
|
|
|
|
logItemThirdParty.setLogLine(aprsIsData.thirdParty.convertToString(true));
|
|
|
|
_logItemRepository.insertLogItem(logItemThirdParty);
|
|
|
|
_stationItemRepository.upsertStationItem(logItemThirdParty.toStationItem());
|
|
|
|
}
|
2022-07-06 08:32:31 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-30 19:32:25 +00:00
|
|
|
private void restartListening() {
|
|
|
|
cancelListening();
|
|
|
|
startListening();
|
|
|
|
}
|
|
|
|
|
|
|
|
private void startListening() {
|
2022-07-05 11:59:15 +00:00
|
|
|
if (_currentStatus == AppMessage.EV_LISTENING) {
|
2021-01-30 19:32:25 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
_listenTimer = new Timer();
|
|
|
|
_listenTimer.schedule(new TimerTask() {
|
|
|
|
@Override
|
|
|
|
public void run() {
|
|
|
|
onListening();
|
|
|
|
}
|
2021-01-31 12:07:20 +00:00
|
|
|
}, LISTEN_AFTER_MS);
|
2021-01-30 19:32:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
private void cancelListening() {
|
|
|
|
try {
|
|
|
|
if (_listenTimer != null) {
|
|
|
|
_listenTimer.cancel();
|
|
|
|
_listenTimer.purge();
|
|
|
|
}
|
|
|
|
} catch (IllegalStateException e) {
|
|
|
|
e.printStackTrace();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void onListening() {
|
|
|
|
sendRxAudioLevelUpdate(null);
|
|
|
|
sendTxAudioLevelUpdate(null);
|
2021-02-06 11:45:09 +00:00
|
|
|
sendRxRadioLevelUpdate(0, 0);
|
2022-07-05 11:59:15 +00:00
|
|
|
sendStatusUpdate(AppMessage.EV_LISTENING, null);
|
2021-01-30 19:32:25 +00:00
|
|
|
}
|
|
|
|
|
2020-12-03 17:17:11 +00:00
|
|
|
private void processRecordPlaybackToggle() throws IOException {
|
2020-12-04 15:11:47 +00:00
|
|
|
// playback -> recording
|
2022-07-09 11:33:27 +00:00
|
|
|
if (_needTransmission && _systemAudioRecorder.getRecordingState() != AudioRecord.RECORDSTATE_RECORDING) {
|
2021-01-27 10:52:54 +00:00
|
|
|
_systemAudioPlayer.stop();
|
|
|
|
_systemAudioRecorder.startRecording();
|
|
|
|
sendRxAudioLevelUpdate(null);
|
2020-12-03 12:50:53 +00:00
|
|
|
}
|
2020-12-04 15:11:47 +00:00
|
|
|
// recording -> playback
|
2022-07-09 11:33:27 +00:00
|
|
|
if (!_needTransmission && _systemAudioRecorder.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING) {
|
2021-01-27 10:52:54 +00:00
|
|
|
_protocol.flush();
|
|
|
|
_systemAudioRecorder.stop();
|
|
|
|
sendTxAudioLevelUpdate(null);
|
2020-12-03 12:50:53 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-04 20:11:37 +00:00
|
|
|
private void cleanup() {
|
2021-10-24 09:25:07 +00:00
|
|
|
Log.i(TAG, "cleanup() started");
|
2021-01-27 10:52:54 +00:00
|
|
|
_systemAudioRecorder.stop();
|
|
|
|
_systemAudioRecorder.release();
|
2020-12-04 20:11:37 +00:00
|
|
|
|
2021-01-27 10:52:54 +00:00
|
|
|
_systemAudioPlayer.stop();
|
|
|
|
_systemAudioPlayer.release();
|
2020-12-04 20:11:37 +00:00
|
|
|
|
2021-01-27 08:57:36 +00:00
|
|
|
try {
|
2021-01-27 10:52:54 +00:00
|
|
|
_protocol.flush();
|
2021-01-27 08:57:36 +00:00
|
|
|
} catch (IOException e) {
|
|
|
|
e.printStackTrace();
|
|
|
|
}
|
2021-10-19 18:06:32 +00:00
|
|
|
_protocol.close();
|
2021-01-26 18:53:37 +00:00
|
|
|
try {
|
|
|
|
_transport.close();
|
|
|
|
} catch (IOException e) {
|
|
|
|
e.printStackTrace();
|
2020-12-12 09:54:03 +00:00
|
|
|
}
|
2021-10-24 09:25:07 +00:00
|
|
|
Log.i(TAG, "cleanup() completed");
|
2020-12-04 20:11:37 +00:00
|
|
|
}
|
|
|
|
|
2021-01-31 12:03:18 +00:00
|
|
|
private void processRxTx() throws IOException {
|
2021-01-27 10:52:54 +00:00
|
|
|
processRecordPlaybackToggle();
|
|
|
|
|
|
|
|
// recording
|
|
|
|
if (_systemAudioRecorder.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING) {
|
|
|
|
recordAndSendAudioFrame();
|
|
|
|
} else {
|
|
|
|
// playback
|
2022-07-01 14:18:37 +00:00
|
|
|
if (_protocol.receive()) {
|
2022-07-05 11:59:15 +00:00
|
|
|
sendStatusUpdate(AppMessage.EV_RECEIVING, null);
|
2021-01-27 10:52:54 +00:00
|
|
|
}
|
|
|
|
}
|
2021-01-31 12:03:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
private void quitProcessing() {
|
2021-10-24 09:25:07 +00:00
|
|
|
Log.i(TAG, "quitProcessing()");
|
2021-01-31 12:03:18 +00:00
|
|
|
_processPeriodicTimer.cancel();
|
|
|
|
_processPeriodicTimer.purge();
|
|
|
|
Looper.myLooper().quitSafely();
|
|
|
|
}
|
|
|
|
|
2022-07-09 11:33:27 +00:00
|
|
|
private void onWorkerIncomingMessage(Message msg) {
|
2022-07-05 11:59:15 +00:00
|
|
|
switch (AppMessage.values()[msg.what]) {
|
|
|
|
case CMD_PROCESS:
|
2021-01-31 12:03:18 +00:00
|
|
|
try {
|
|
|
|
processRxTx();
|
|
|
|
} catch (IOException e) {
|
|
|
|
e.printStackTrace();
|
|
|
|
quitProcessing();
|
|
|
|
}
|
|
|
|
break;
|
2022-07-05 11:59:15 +00:00
|
|
|
case CMD_QUIT:
|
2021-01-31 12:03:18 +00:00
|
|
|
quitProcessing();
|
|
|
|
break;
|
2022-07-09 11:33:27 +00:00
|
|
|
case CMD_SEND_LOCATION_TO_TNC:
|
2022-07-03 08:53:51 +00:00
|
|
|
try {
|
2022-07-31 14:03:31 +00:00
|
|
|
_protocol.sendPosition((Position)msg.obj);
|
2022-07-03 08:53:51 +00:00
|
|
|
} catch (IOException e) {
|
|
|
|
e.printStackTrace();
|
|
|
|
quitProcessing();
|
|
|
|
}
|
|
|
|
break;
|
2022-07-18 17:17:40 +00:00
|
|
|
case CMD_SEND_MESSAGE:
|
|
|
|
TextMessage textMessage = (TextMessage) msg.obj;
|
|
|
|
try {
|
|
|
|
_protocol.sendTextMessage(textMessage);
|
|
|
|
} catch (IOException e) {
|
|
|
|
e.printStackTrace();
|
|
|
|
quitProcessing();
|
|
|
|
}
|
|
|
|
break;
|
2021-01-31 12:03:18 +00:00
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-09 11:33:27 +00:00
|
|
|
private void startWorkerMessageHandler() {
|
2021-01-31 12:03:18 +00:00
|
|
|
_onMessageReceived = new Handler(Looper.myLooper()) {
|
|
|
|
@Override
|
|
|
|
public void handleMessage(Message msg) {
|
2022-07-09 11:33:27 +00:00
|
|
|
onWorkerIncomingMessage(msg);
|
2021-01-31 12:03:18 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
_processPeriodicTimer.schedule(new TimerTask() {
|
|
|
|
@Override
|
|
|
|
public void run() {
|
|
|
|
Message msg = new Message();
|
2022-07-05 11:59:15 +00:00
|
|
|
msg.what = AppMessage.CMD_PROCESS.toInt();
|
2021-01-31 12:03:18 +00:00
|
|
|
_onMessageReceived.sendMessage(msg);
|
|
|
|
}
|
2021-01-31 12:07:20 +00:00
|
|
|
}, 0, PROCESS_INTERVAL_MS);
|
2021-01-27 10:52:54 +00:00
|
|
|
}
|
|
|
|
|
2020-12-02 16:27:58 +00:00
|
|
|
@Override
|
|
|
|
public void run() {
|
2021-10-24 09:25:07 +00:00
|
|
|
Log.i(TAG, "Starting message loop");
|
2022-07-30 17:55:43 +00:00
|
|
|
android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_AUDIO);
|
2021-01-31 12:03:18 +00:00
|
|
|
Looper.prepare();
|
2021-01-28 07:24:14 +00:00
|
|
|
|
2022-07-05 11:59:15 +00:00
|
|
|
sendStatusUpdate(AppMessage.EV_CONNECTED, null);
|
2022-07-31 11:05:35 +00:00
|
|
|
_systemAudioPlayer.play();
|
2021-01-28 07:24:14 +00:00
|
|
|
|
2020-12-03 13:25:02 +00:00
|
|
|
try {
|
2022-07-01 14:18:37 +00:00
|
|
|
_protocol.initialize(_transport, _context, _protocolCallback);
|
2022-08-13 17:48:45 +00:00
|
|
|
_recordAudioBuffer = new short[_protocol.getPcmAudioRecordBufferSize()];
|
2022-07-09 11:33:27 +00:00
|
|
|
startWorkerMessageHandler();
|
2021-01-31 12:03:18 +00:00
|
|
|
Looper.loop();
|
|
|
|
} catch (IOException e) {
|
2020-12-03 13:25:02 +00:00
|
|
|
e.printStackTrace();
|
|
|
|
}
|
2020-12-04 15:11:47 +00:00
|
|
|
|
2020-12-04 20:11:37 +00:00
|
|
|
cleanup();
|
2022-07-05 11:59:15 +00:00
|
|
|
sendStatusUpdate(AppMessage.EV_DISCONNECTED, null);
|
2021-10-24 09:25:07 +00:00
|
|
|
Log.i(TAG, "Exiting message loop");
|
2020-11-29 18:02:34 +00:00
|
|
|
}
|
|
|
|
}
|