kopia lustrzana https://github.com/sh123/codec2_talkie
466 wiersze
19 KiB
Java
466 wiersze
19 KiB
Java
package com.radio.codec2talkie;
|
|
|
|
import androidx.appcompat.app.AppCompatActivity;
|
|
import androidx.core.app.ActivityCompat;
|
|
import androidx.core.content.ContextCompat;
|
|
import androidx.preference.PreferenceManager;
|
|
|
|
import android.Manifest;
|
|
import android.annotation.SuppressLint;
|
|
import android.bluetooth.BluetoothDevice;
|
|
import android.content.BroadcastReceiver;
|
|
import android.content.Context;
|
|
import android.content.Intent;
|
|
import android.content.IntentFilter;
|
|
import android.content.SharedPreferences;
|
|
import android.content.pm.PackageManager;
|
|
import android.graphics.Color;
|
|
import android.graphics.PorterDuff;
|
|
import android.graphics.PorterDuffColorFilter;
|
|
import android.hardware.usb.UsbManager;
|
|
import android.os.Bundle;
|
|
import android.os.Handler;
|
|
import android.os.Looper;
|
|
import android.os.Message;
|
|
import android.os.SystemClock;
|
|
import android.util.Log;
|
|
import android.view.KeyEvent;
|
|
import android.view.Menu;
|
|
import android.view.MenuItem;
|
|
import android.view.MotionEvent;
|
|
import android.view.View;
|
|
import android.view.WindowManager;
|
|
import android.widget.Button;
|
|
import android.widget.FrameLayout;
|
|
import android.widget.ProgressBar;
|
|
import android.widget.TextView;
|
|
import android.widget.Toast;
|
|
|
|
import com.radio.codec2talkie.connect.BluetoothConnectActivity;
|
|
import com.radio.codec2talkie.connect.SocketHandler;
|
|
import com.radio.codec2talkie.protocol.ProtocolFactory;
|
|
import com.radio.codec2talkie.settings.PreferenceKeys;
|
|
import com.radio.codec2talkie.settings.SettingsActivity;
|
|
import com.radio.codec2talkie.tools.RadioTools;
|
|
import com.radio.codec2talkie.transport.TransportFactory;
|
|
import com.radio.codec2talkie.connect.UsbConnectActivity;
|
|
import com.radio.codec2talkie.connect.UsbPortHandler;
|
|
|
|
import java.io.IOException;
|
|
import java.util.LinkedList;
|
|
import java.util.List;
|
|
import java.util.Locale;
|
|
|
|
public class MainActivity extends AppCompatActivity {
|
|
|
|
private static final String TAG = MainActivity.class.getSimpleName();
|
|
|
|
private final static int REQUEST_CONNECT_BT = 1;
|
|
private final static int REQUEST_CONNECT_USB = 2;
|
|
private final static int REQUEST_PERMISSIONS = 3;
|
|
private final static int REQUEST_SETTINGS = 4;
|
|
|
|
// S9 level at -93 dBm as per VHF Managers Handbook
|
|
private final static int S_METER_S0_VALUE_DB = -153;
|
|
// from S0 up to S9+40
|
|
private final static int S_METER_RANGE_DB = 100;
|
|
|
|
private final static int UV_METER_MIN_DELTA = 30;
|
|
private final static int UV_METER_MAX_DELTA = -20;
|
|
|
|
private final String[] _requiredPermissions = new String[] {
|
|
Manifest.permission.BLUETOOTH,
|
|
Manifest.permission.RECORD_AUDIO
|
|
};
|
|
|
|
private AudioProcessor _audioProcessor;
|
|
|
|
private SharedPreferences _sharedPreferences;
|
|
|
|
private boolean _isTestMode;
|
|
|
|
private TextView _textConnInfo;
|
|
private TextView _textStatus;
|
|
private TextView _textCodecMode;
|
|
private TextView _textRssi;
|
|
private ProgressBar _progressAudioLevel;
|
|
private ProgressBar _progressRssi;
|
|
private Button _btnPtt;
|
|
|
|
@SuppressLint("ClickableViewAccessibility")
|
|
@Override
|
|
protected void onCreate(Bundle savedInstanceState) {
|
|
super.onCreate(savedInstanceState);
|
|
|
|
String appName = getResources().getString(R.string.app_name);
|
|
setTitle(appName + " v" + BuildConfig.VERSION_NAME);
|
|
|
|
_sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
|
|
|
|
setContentView(R.layout.activity_main);
|
|
|
|
_textConnInfo = findViewById(R.id.textBtName);
|
|
_textStatus = findViewById(R.id.textStatus);
|
|
_textRssi = findViewById(R.id.textRssi);
|
|
|
|
int barMaxValue = AudioProcessor.getAudioMaxLevel() - AudioProcessor.getAudioMinLevel();
|
|
_progressAudioLevel = findViewById(R.id.progressAudioLevel);
|
|
_progressAudioLevel.setMax(barMaxValue);
|
|
_progressAudioLevel.getProgressDrawable().setColorFilter(
|
|
new PorterDuffColorFilter(colorFromAudioLevel(AudioProcessor.getAudioMinLevel()), PorterDuff.Mode.SRC_IN));
|
|
|
|
_progressRssi = findViewById(R.id.progressRssi);
|
|
_progressRssi.setMax(S_METER_RANGE_DB);
|
|
_progressRssi.getProgressDrawable().setColorFilter(
|
|
new PorterDuffColorFilter(Color.GRAY, PorterDuff.Mode.SRC_IN));
|
|
|
|
_btnPtt = findViewById(R.id.btnPtt);
|
|
_btnPtt.setOnTouchListener(onBtnPttTouchListener);
|
|
|
|
_textCodecMode = findViewById(R.id.codecMode);
|
|
|
|
registerReceiver(onBluetoothDisconnected, new IntentFilter(BluetoothDevice.ACTION_ACL_DISCONNECTED));
|
|
registerReceiver(onUsbDetached, new IntentFilter(UsbManager.ACTION_USB_DEVICE_DETACHED));
|
|
|
|
_isTestMode = _sharedPreferences.getBoolean(PreferenceKeys.CODEC2_TEST_MODE, false);
|
|
|
|
FrameLayout frameRssi = findViewById(R.id.frameRssi);
|
|
if (!_sharedPreferences.getBoolean(PreferenceKeys.KISS_EXTENSIONS_ENABLED, false)) {
|
|
frameRssi.setVisibility(View.GONE);
|
|
}
|
|
|
|
if (_sharedPreferences.getBoolean(PreferenceKeys.APP_KEEP_SCREEN_ON, false)) {
|
|
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
|
}
|
|
startTransportConnection();
|
|
}
|
|
|
|
@Override
|
|
protected void onDestroy() {
|
|
super.onDestroy();
|
|
}
|
|
|
|
private void stopRunning() {
|
|
if (_audioProcessor != null) {
|
|
_audioProcessor.stopRunning();
|
|
}
|
|
finish();
|
|
}
|
|
|
|
private void startTransportConnection() {
|
|
if (_isTestMode) {
|
|
_textConnInfo.setText(R.string.main_status_loopback_test);
|
|
startAudioProcessing(TransportFactory.TransportType.LOOPBACK);
|
|
} else if (requestPermissions()) {
|
|
startUsbConnectActivity();
|
|
}
|
|
}
|
|
|
|
protected void startUsbConnectActivity() {
|
|
Intent usbConnectIntent = new Intent(this, UsbConnectActivity.class);
|
|
startActivityForResult(usbConnectIntent, REQUEST_CONNECT_USB);
|
|
}
|
|
|
|
protected void startBluetoothConnectActivity() {
|
|
Intent bluetoothConnectIntent = new Intent(this, BluetoothConnectActivity.class);
|
|
startActivityForResult(bluetoothConnectIntent, REQUEST_CONNECT_BT);
|
|
}
|
|
|
|
protected boolean requestPermissions() {
|
|
List<String> permissionsToRequest = new LinkedList<String>();
|
|
|
|
for (String permission : _requiredPermissions) {
|
|
if (ContextCompat.checkSelfPermission(MainActivity.this, permission) == PackageManager.PERMISSION_DENIED) {
|
|
permissionsToRequest.add(permission);
|
|
}
|
|
}
|
|
if (permissionsToRequest.size() > 0) {
|
|
ActivityCompat.requestPermissions(
|
|
MainActivity.this,
|
|
permissionsToRequest.toArray(new String[0]),
|
|
REQUEST_PERMISSIONS);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private int colorFromAudioLevel(int audioLevel) {
|
|
int color = Color.GREEN;
|
|
if (audioLevel > AudioProcessor.getAudioMaxLevel() + UV_METER_MAX_DELTA)
|
|
color = Color.RED;
|
|
else if (audioLevel < AudioProcessor.getAudioMinLevel() + UV_METER_MIN_DELTA)
|
|
color = Color.LTGRAY;
|
|
return color;
|
|
}
|
|
|
|
private final BroadcastReceiver onBluetoothDisconnected = new BroadcastReceiver() {
|
|
@Override
|
|
public void onReceive(Context context, Intent intent) {
|
|
if (_audioProcessor != null && SocketHandler.getSocket() != null && !_isTestMode) {
|
|
Toast.makeText(MainActivity.this, "Bluetooth disconnected", Toast.LENGTH_LONG).show();
|
|
_audioProcessor.stopRunning();
|
|
}
|
|
}
|
|
};
|
|
|
|
private final BroadcastReceiver onUsbDetached = new BroadcastReceiver() {
|
|
@Override
|
|
public void onReceive(Context context, Intent intent) {
|
|
if (_audioProcessor != null && UsbPortHandler.getPort() != null && !_isTestMode) {
|
|
Toast.makeText(MainActivity.this, "USB detached", Toast.LENGTH_LONG).show();
|
|
_audioProcessor.stopRunning();
|
|
}
|
|
}
|
|
};
|
|
|
|
@Override
|
|
public boolean onCreateOptionsMenu(Menu menu) {
|
|
getMenuInflater().inflate(R.menu.main_menu, menu);
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public boolean onOptionsItemSelected(MenuItem item)
|
|
{
|
|
int itemId = item.getItemId();
|
|
|
|
if (itemId == R.id.preferences) {
|
|
Intent settingsIntent = new Intent(this, SettingsActivity.class);
|
|
startActivityForResult(settingsIntent, REQUEST_SETTINGS);
|
|
return true;
|
|
}
|
|
else if (itemId == R.id.exit) {
|
|
stopRunning();
|
|
return true;
|
|
}
|
|
return super.onOptionsItemSelected(item);
|
|
}
|
|
|
|
@Override
|
|
public boolean onKeyDown(int keyCode, KeyEvent event) {
|
|
switch (keyCode) {
|
|
// headset hardware ptt cannot be used for long press
|
|
case KeyEvent.KEYCODE_HEADSETHOOK:
|
|
if (_btnPtt.isPressed()) {
|
|
_btnPtt.dispatchTouchEvent(MotionEvent.obtain(SystemClock.uptimeMillis(),
|
|
SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, 0, 0, 0));
|
|
} else {
|
|
_btnPtt.dispatchTouchEvent(MotionEvent.obtain(SystemClock.uptimeMillis(),
|
|
SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN, 0, 0, 0));
|
|
}
|
|
return true;
|
|
case KeyEvent.KEYCODE_VOLUME_DOWN:
|
|
case KeyEvent.KEYCODE_VOLUME_UP:
|
|
if (_sharedPreferences.getBoolean(PreferenceKeys.APP_VOLUME_PTT, false)) {
|
|
_btnPtt.dispatchTouchEvent(MotionEvent.obtain(SystemClock.uptimeMillis(),
|
|
SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN, 0, 0, 0));
|
|
return true;
|
|
}
|
|
break;
|
|
case KeyEvent.KEYCODE_TV_DATA_SERVICE:
|
|
_btnPtt.dispatchTouchEvent(MotionEvent.obtain(SystemClock.uptimeMillis(),
|
|
SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN, 0, 0, 0));
|
|
return true;
|
|
}
|
|
return super.onKeyDown(keyCode, event);
|
|
}
|
|
|
|
@Override
|
|
public boolean onKeyUp(int keyCode, KeyEvent event) {
|
|
switch (keyCode) {
|
|
case KeyEvent.KEYCODE_VOLUME_DOWN:
|
|
case KeyEvent.KEYCODE_VOLUME_UP:
|
|
if (_sharedPreferences.getBoolean(PreferenceKeys.APP_VOLUME_PTT, false)) {
|
|
_btnPtt.dispatchTouchEvent(MotionEvent.obtain(SystemClock.uptimeMillis(),
|
|
SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, 0, 0, 0));
|
|
return true;
|
|
}
|
|
break;
|
|
case KeyEvent.KEYCODE_TV_DATA_SERVICE:
|
|
_btnPtt.dispatchTouchEvent(MotionEvent.obtain(SystemClock.uptimeMillis(),
|
|
SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, 0, 0, 0));
|
|
return true;
|
|
}
|
|
return super.onKeyUp(keyCode, event);
|
|
}
|
|
|
|
private final View.OnTouchListener onBtnPttTouchListener = new View.OnTouchListener() {
|
|
@Override
|
|
public boolean onTouch(View v, MotionEvent event) {
|
|
switch (event.getAction()) {
|
|
case MotionEvent.ACTION_DOWN:
|
|
if (_audioProcessor != null)
|
|
_audioProcessor.startRecording();
|
|
break;
|
|
case MotionEvent.ACTION_UP:
|
|
v.performClick();
|
|
if (_audioProcessor != null)
|
|
_audioProcessor.startPlayback();
|
|
break;
|
|
}
|
|
return false;
|
|
}
|
|
};
|
|
|
|
@Override
|
|
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
|
|
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
|
if (requestCode == REQUEST_PERMISSIONS) {
|
|
boolean allGranted = true;
|
|
for (int result : grantResults) {
|
|
if (result != PackageManager.PERMISSION_GRANTED) {
|
|
allGranted = false;
|
|
break;
|
|
}
|
|
}
|
|
if (allGranted) {
|
|
Toast.makeText(MainActivity.this, "Permissions Granted", Toast.LENGTH_SHORT).show();
|
|
startUsbConnectActivity();
|
|
} else {
|
|
Toast.makeText(MainActivity.this, "Permissions Denied", Toast.LENGTH_SHORT).show();
|
|
stopRunning();
|
|
}
|
|
}
|
|
}
|
|
|
|
private final Handler onAudioProcessorStateChanged = new Handler(Looper.getMainLooper()) {
|
|
@Override
|
|
public void handleMessage(Message msg) {
|
|
switch (msg.what) {
|
|
case AudioProcessor.PROCESSOR_CONNECTED:
|
|
Toast.makeText(getBaseContext(), "Connected", Toast.LENGTH_SHORT).show();
|
|
break;
|
|
case AudioProcessor.PROCESSOR_DISCONNECTED:
|
|
_textStatus.setText(R.string.main_status_stop);
|
|
Toast.makeText(getBaseContext(), "Disconnected", Toast.LENGTH_SHORT).show();
|
|
startTransportConnection();
|
|
break;
|
|
case AudioProcessor.PROCESSOR_LISTENING:
|
|
_textStatus.setText(R.string.main_status_idle);
|
|
break;
|
|
case AudioProcessor.PROCESSOR_RECORDING:
|
|
_textStatus.setText(R.string.main_status_tx);
|
|
break;
|
|
case AudioProcessor.PROCESSOR_RECEIVING:
|
|
_textStatus.setText(R.string.main_status_rx);
|
|
break;
|
|
case AudioProcessor.PROCESSOR_PLAYING:
|
|
_textStatus.setText(R.string.main_status_play);
|
|
break;
|
|
case AudioProcessor.PROCESSOR_RX_RADIO_LEVEL:
|
|
if (msg.arg1 == 0) {
|
|
_textRssi.setText("");
|
|
_progressRssi.getProgressDrawable().setColorFilter(new PorterDuffColorFilter(Color.GRAY, PorterDuff.Mode.SRC_IN));
|
|
_progressRssi.setProgress(0);
|
|
} else {
|
|
_textRssi.setText(String.format(Locale.getDefault(), "%3d dBm, %2.2f", msg.arg1, (double)msg.arg2 / 100.0));
|
|
_progressRssi.getProgressDrawable().setColorFilter(new PorterDuffColorFilter(Color.GREEN, PorterDuff.Mode.SRC_IN));
|
|
_progressRssi.setProgress(msg.arg1 - S_METER_S0_VALUE_DB);
|
|
}
|
|
break;
|
|
// same progress bar is reused for rx and tx levels
|
|
case AudioProcessor.PROCESSOR_RX_LEVEL:
|
|
case AudioProcessor.PROCESSOR_TX_LEVEL:
|
|
_progressAudioLevel.getProgressDrawable().setColorFilter(new PorterDuffColorFilter(colorFromAudioLevel(msg.arg1), PorterDuff.Mode.SRC_IN));
|
|
_progressAudioLevel.setProgress(msg.arg1 - AudioProcessor.getAudioMinLevel());
|
|
break;
|
|
case AudioProcessor.PROCESSOR_CODEC_ERROR:
|
|
_textStatus.setText(R.string.main_status_codec_error);
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
|
|
private ProtocolFactory.ProtocolType getRequiredProtocolType() {
|
|
ProtocolFactory.ProtocolType protocolType;
|
|
|
|
if (_sharedPreferences.getBoolean(PreferenceKeys.KISS_ENABLED, true)) {
|
|
if (_sharedPreferences.getBoolean(PreferenceKeys.KISS_PARROT, false)) {
|
|
protocolType = ProtocolFactory.ProtocolType.KISS_PARROT;
|
|
}
|
|
else if (_sharedPreferences.getBoolean(PreferenceKeys.KISS_BUFFERED_ENABLED, false)) {
|
|
protocolType = ProtocolFactory.ProtocolType.KISS_BUFFERED;
|
|
}
|
|
else {
|
|
protocolType = ProtocolFactory.ProtocolType.KISS;
|
|
}
|
|
} else {
|
|
protocolType = ProtocolFactory.ProtocolType.RAW;
|
|
}
|
|
return protocolType;
|
|
}
|
|
|
|
private int getRadioSpeed() {
|
|
int resultBps = 0;
|
|
try {
|
|
if (_sharedPreferences.getBoolean(PreferenceKeys.KISS_EXTENSIONS_ENABLED, false)) {
|
|
int bw = Integer.parseInt(_sharedPreferences.getString(PreferenceKeys.KISS_EXTENSIONS_RADIO_BANDWIDTH, ""));
|
|
int sf = Integer.parseInt(_sharedPreferences.getString(PreferenceKeys.KISS_EXTENSIONS_RADIO_SF, ""));
|
|
int cr = Integer.parseInt(_sharedPreferences.getString(PreferenceKeys.KISS_EXTENSIONS_RADIO_CR, ""));
|
|
|
|
resultBps = RadioTools.calculateLoraSpeedBps(bw, sf, cr);
|
|
Log.e(TAG, String.valueOf(resultBps));
|
|
}
|
|
} catch (NumberFormatException|ArithmeticException e) {
|
|
e.printStackTrace();
|
|
}
|
|
return resultBps;
|
|
}
|
|
|
|
private void startAudioProcessing(TransportFactory.TransportType transportType) {
|
|
try {
|
|
// code mode
|
|
String codec2ModeName = _sharedPreferences.getString(PreferenceKeys.CODEC2_MODE, getResources().getStringArray(R.array.codec2_modes)[0]);
|
|
String[] codecNameCodecId = codec2ModeName.split("=");
|
|
String[] modeSpeed = codecNameCodecId[0].split("_");
|
|
String speedModeInfo = "C2: " + modeSpeed[1];
|
|
int codec2ModeId = Integer.parseInt(codecNameCodecId[1]);
|
|
|
|
ProtocolFactory.ProtocolType protocolType = getRequiredProtocolType();
|
|
_btnPtt.setEnabled(protocolType != ProtocolFactory.ProtocolType.KISS_PARROT);
|
|
|
|
int radioSpeedBps = getRadioSpeed();
|
|
if (radioSpeedBps > 0 && radioSpeedBps < 128000) {
|
|
speedModeInfo = "RF: " + radioSpeedBps + ", " + speedModeInfo;
|
|
}
|
|
|
|
speedModeInfo += ", " + protocolType.toString();
|
|
_textCodecMode.setText(speedModeInfo);
|
|
|
|
_audioProcessor = new AudioProcessor(transportType, protocolType, codec2ModeId, onAudioProcessorStateChanged, getApplicationContext());
|
|
_audioProcessor.start();
|
|
} catch (IOException e) {
|
|
e.printStackTrace();
|
|
Toast.makeText(MainActivity.this, "Failed to start audio processing", Toast.LENGTH_LONG).show();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
|
super.onActivityResult(requestCode, resultCode, data);
|
|
|
|
if (requestCode == REQUEST_CONNECT_BT) {
|
|
if (resultCode == RESULT_CANCELED) {
|
|
// fall back to loopback if bluetooth failed
|
|
_textConnInfo.setText(R.string.main_status_loopback_test);
|
|
startAudioProcessing(TransportFactory.TransportType.LOOPBACK);
|
|
} else if (resultCode == RESULT_OK) {
|
|
_textConnInfo.setText(data.getStringExtra("name"));
|
|
startAudioProcessing(TransportFactory.TransportType.BLUETOOTH);
|
|
}
|
|
}
|
|
else if (requestCode == REQUEST_CONNECT_USB) {
|
|
if (resultCode == RESULT_CANCELED) {
|
|
// fall back to bluetooth if usb failed
|
|
startBluetoothConnectActivity();
|
|
} else if (resultCode == RESULT_OK) {
|
|
_textConnInfo.setText(data.getStringExtra("name"));
|
|
startAudioProcessing(TransportFactory.TransportType.USB);
|
|
}
|
|
}
|
|
else if (requestCode == REQUEST_SETTINGS) {
|
|
stopRunning();
|
|
startActivity(getIntent());
|
|
}
|
|
}
|
|
} |