package com.bg7yoz.ft8cn; /** * FT8CN程序的主Activity。本APP采用Fragment框架实现,每个Fragment实现不同的功能。 * ----2022.5.6----- * 主要完成以下功能: * 1.生成MainViewModel实例。MainViewModel是用于整个生存周期,用于录音、解析等功能。 * 2.录音、存储的权限申请。 * 3.实现Fragment的导航管理。 * 4.USB串口连接后的提示 * @author BG7YOZ * @date 2022.5.6 */ import android.Manifest; import android.animation.Animator; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.annotation.SuppressLint; import android.app.AlertDialog; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; import android.media.AudioManager; import android.os.Build; import android.os.Bundle; import android.view.LayoutInflater; import android.view.MenuItem; import android.view.View; import android.view.WindowManager; import android.view.animation.AnimationUtils; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; import androidx.lifecycle.Observer; import androidx.navigation.NavController; import androidx.navigation.fragment.NavHostFragment; import androidx.navigation.ui.NavigationUI; import com.bg7yoz.ft8cn.bluetooth.BluetoothStateBroadcastReceive; import com.bg7yoz.ft8cn.callsign.CallsignDatabase; import com.bg7yoz.ft8cn.connector.CableSerialPort; import com.bg7yoz.ft8cn.database.DatabaseOpr; import com.bg7yoz.ft8cn.database.OnAfterQueryConfig; import com.bg7yoz.ft8cn.database.OperationBand; import com.bg7yoz.ft8cn.databinding.MainActivityBinding; import com.bg7yoz.ft8cn.floatview.FloatView; import com.bg7yoz.ft8cn.floatview.FloatViewButton; import com.bg7yoz.ft8cn.grid_tracker.GridTrackerMainActivity; import com.bg7yoz.ft8cn.maidenhead.MaidenheadGrid; import com.bg7yoz.ft8cn.timer.UtcTimer; import com.bg7yoz.ft8cn.ui.FreqDialog; import com.bg7yoz.ft8cn.ui.SetVolumeDialog; import com.bg7yoz.ft8cn.ui.ToastMessage; import com.google.android.material.bottomnavigation.BottomNavigationView; import java.io.File; import java.io.IOException; import java.text.ParseException; import java.text.SimpleDateFormat; import java.time.LocalDate; import java.util.ArrayList; import java.util.Date; import java.util.List; public class MainActivity extends AppCompatActivity { private BluetoothStateBroadcastReceive mReceive; private static final String TAG = "MainActivity"; private MainViewModel mainViewModel; private NavController navController; private static boolean animatorRunned = false; //private boolean animationEnd = false; private MainActivityBinding binding; private FloatView floatView; String[] permissions = new String[]{Manifest.permission.RECORD_AUDIO , Manifest.permission.ACCESS_COARSE_LOCATION , Manifest.permission.ACCESS_WIFI_STATE , Manifest.permission.BLUETOOTH , Manifest.permission.BLUETOOTH_ADMIN , Manifest.permission.MODIFY_AUDIO_SETTINGS , Manifest.permission.WAKE_LOCK , Manifest.permission.ACCESS_FINE_LOCATION}; List mPermissionList = new ArrayList<>(); private static final int PERMISSION_REQUEST = 1; @Override protected void onCreate(Bundle savedInstanceState) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { permissions = new String[]{Manifest.permission.RECORD_AUDIO , Manifest.permission.ACCESS_COARSE_LOCATION , Manifest.permission.ACCESS_WIFI_STATE , Manifest.permission.BLUETOOTH , Manifest.permission.BLUETOOTH_ADMIN , Manifest.permission.BLUETOOTH_CONNECT , Manifest.permission.MODIFY_AUDIO_SETTINGS , Manifest.permission.WAKE_LOCK , Manifest.permission.ACCESS_FINE_LOCATION}; } checkPermission(); //全屏 getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN , WindowManager.LayoutParams.FLAG_FULLSCREEN); //禁止休眠 getWindow().setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON , WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); super.onCreate(savedInstanceState); GeneralVariables.getInstance().setMainContext(getApplicationContext()); //判断是不是简体中文 GeneralVariables.isTraditionalChinese = getResources().getConfiguration().locale.getDisplayCountry().equals("中國"); //确定是不是中国、香港、澳门、台湾 GeneralVariables.isChina = (getResources().getConfiguration().locale .getLanguage().toUpperCase().startsWith("ZH")); mainViewModel = MainViewModel.getInstance(this); binding = MainActivityBinding.inflate(getLayoutInflater()); binding.initDataLayout.setVisibility(View.VISIBLE);//显示LOG页面 setContentView(binding.getRoot()); ToastMessage.getInstance(); registerBluetoothReceiver();//注册蓝牙动作改变的广播 if (mainViewModel.isBTConnected()) { mainViewModel.setBlueToothOn(); } //观察DEBUG信息 GeneralVariables.mutableDebugMessage.observe(this, new Observer() { @Override public void onChanged(String s) { if (s.length() > 1) { binding.debugLayout.setVisibility(View.VISIBLE); } else { binding.debugLayout.setVisibility(View.GONE); } binding.debugMessageTextView.setText(s); } }); binding.debugLayout.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { binding.debugLayout.setVisibility(View.GONE); } }); mainViewModel.mutableIsRecording.observe(this, new Observer() { @Override public void onChanged(Boolean aBoolean) { if (aBoolean) { binding.utcProgressBar.setVisibility(View.VISIBLE); } else { binding.utcProgressBar.setVisibility(View.GONE); } } }); //观察时钟的变化,显示进度条 mainViewModel.timerSec.observe(this, new Observer() { @Override public void onChanged(Long aLong) { if (mainViewModel.ft8TransmitSignal.sequential == UtcTimer.getNowSequential() && mainViewModel.ft8TransmitSignal.isActivated()) { binding.utcProgressBar.setBackgroundColor(getColor(R.color.calling_list_isMyCall_color)); } else { binding.utcProgressBar.setBackgroundColor(getColor(R.color.progresss_bar_back_color)); } binding.utcProgressBar.setProgress((int) ((aLong / 1000) % 15)); } }); //添加点击发射消息提示窗口点击关闭动作 binding.transmittingLayout.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { binding.transmittingLayout.setVisibility(View.GONE); } }); //清空缓存中的文件 //deleteFolderFile(this.getCacheDir().getPath()); //Log.e(TAG, this.getCacheDir().getPath()); //用于Fragment的导航。 NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.fragmentContainerView); assert navHostFragment != null;//断言不为空 navController = navHostFragment.getNavController(); NavigationUI.setupWithNavController(binding.navView, navController); //此处增加回调是因为当APP主动navigation后,无法回到解码的界面 binding.navView.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() { @Override public boolean onNavigationItemSelected(@NonNull MenuItem item) { //Log.e(TAG, "onNavigationItemSelected: "+item.toString() ); navController.navigate(item.getItemId()); //binding.navView.setLabelFor(item.getItemId()); return true; } }); //FT8CN Ver %s\nBG7YOZ\n%s binding.welcomTextView.setText(String.format(getString(R.string.version_info) , GeneralVariables.VERSION, GeneralVariables.BUILD_DATE)); floatView = new FloatView(this, 32); if (!animatorRunned) { animationImage(); animatorRunned = true; } else { binding.initDataLayout.setVisibility(View.GONE); InitFloatView(); } //初始化数据 InitData(); //观察是不是flex radio mainViewModel.mutableIsFlexRadio.observe(this, new Observer() { @Override public void onChanged(Boolean aBoolean) { //if (floatView==null) return; if (aBoolean) { //添加flex配置按钮 floatView.addButton(R.id.flex_radio, "flex_radio", R.drawable.flex_icon , new View.OnClickListener() { @Override public void onClick(View view) { navController.navigate(R.id.flexRadioInfoFragment); // if (mainViewModel.baseRig != null) { // if (mainViewModel.baseRig.isConnected()) { // ToastMessage.show("flex connected"); // }else { // ToastMessage.show("flex disconnected"); // } // }else { // ToastMessage.show("rig is null"); // } } }); } else {//删除flex配置按钮 floatView.deleteButtonByName("flex_radio"); } } }); //关闭串口设备列表按钮 binding.closeSelectSerialPortImageView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { binding.selectSerialPortLayout.setVisibility(View.GONE); } }); //观察串口设备列表的变化 mainViewModel.mutableSerialPorts.observe(this, new Observer>() { @Override public void onChanged(ArrayList serialPorts) { setSelectUsbDevice(); } }); //列USB设备列表 mainViewModel.getUsbDevice(); //设置发射消息框的动画 binding.transmittingMessageTextView.setAnimation(AnimationUtils.loadAnimation(this , R.anim.view_blink)); //观察发射的状态 mainViewModel.ft8TransmitSignal.mutableIsTransmitting.observe(this, new Observer() { @Override public void onChanged(Boolean aBoolean) { if (aBoolean) { binding.transmittingLayout.setVisibility(View.VISIBLE); } else { binding.transmittingLayout.setVisibility(View.GONE); } } }); //观察发射内容的变化 mainViewModel.ft8TransmitSignal.mutableTransmittingMessage.observe(this, new Observer() { @Override public void onChanged(String s) { binding.transmittingMessageTextView.setText(s); } }); } /** * 添加浮动按钮 */ private void InitFloatView() { //floatView = new FloatView(this, 32); binding.container.addView(floatView); floatView.setButtonMargin(0); floatView.setFloatBoard(FloatView.FLOAT_BOARD.RIGHT); floatView.setButtonBackgroundResourceId(R.drawable.float_button_style); //动态添加按钮,建议使用静态的ID,静态ID在VALUES/FLOAT_BUTTON_IDS.XML中设置 floatView.addButton(R.id.float_nav, "float_nav", R.drawable.ic_baseline_fullscreen_24 , new View.OnClickListener() { @Override public void onClick(View view) { FloatViewButton button = floatView.getButtonByName("float_nav"); if (binding.navView.getVisibility() == View.VISIBLE) { binding.navView.setVisibility(View.GONE); if (button != null) { button.setImageResource(R.drawable.ic_baseline_fullscreen_exit_24); } } else { binding.navView.setVisibility(View.VISIBLE); if (button != null) { button.setImageResource(R.drawable.ic_baseline_fullscreen_24); } } } }); floatView.addButton(R.id.float_freq, "float_freq", R.drawable.ic_baseline_freq_24 , new View.OnClickListener() { @Override public void onClick(View view) { new FreqDialog(binding.container.getContext(), mainViewModel).show(); } }); floatView.addButton(R.id.set_volume, "set_volume", R.drawable.ic_baseline_volume_up_24 , new View.OnClickListener() { @Override public void onClick(View view) { new SetVolumeDialog(binding.container.getContext(), mainViewModel).show(); } }); //打开网格追踪 floatView.addButton(R.id.grid_tracker, "grid_tracker", R.drawable.ic_baseline_grid_tracker_24 , new View.OnClickListener() { @Override public void onClick(View view) { Intent intent = new Intent(getApplicationContext(), GridTrackerMainActivity.class); startActivity(intent); } }); // floatView.addButton(R.id.flex_radio, "flex_radio", R.drawable.flex_icon // , new View.OnClickListener() { // @Override // public void onClick(View view) { // navController.navigate(R.id.flexRadioInfoFragment); // } // }); floatView.initLocation(); } /** * 初始化一些数据 */ private void InitData() { if (mainViewModel.configIsLoaded) return;//如果数据已经读取一遍了,就不用再读取了。 //读取波段数据 if (mainViewModel.operationBand == null) { mainViewModel.operationBand = OperationBand.getInstance(getBaseContext()); } mainViewModel.databaseOpr.getQslDxccToMap(); //获取所有的配置参数 mainViewModel.databaseOpr.getAllConfigParameter(new OnAfterQueryConfig() { @Override public void doOnBeforeQueryConfig(String KeyName) { } @Override public void doOnAfterQueryConfig(String KeyName, String Value) { mainViewModel.configIsLoaded = true; //此处梅登海德已经通过数据库得到了,但是如果GPS能获取到,还是用GPS的 String grid = MaidenheadGrid.getMyMaidenheadGrid(getApplicationContext()); if (!grid.equals("")) {//说明获取到了GPS数据 GeneralVariables.setMyMaidenheadGrid(grid); //写到数据库中 mainViewModel.databaseOpr.writeConfig("grid", grid, null); } mainViewModel.ft8TransmitSignal.setTimer_sec(GeneralVariables.transmitDelay); //如果呼号、网格为空,就进入设置界面 if (GeneralVariables.getMyMaidenheadGrid().equals("") || GeneralVariables.myCallsign.equals("")) { runOnUiThread(new Runnable() { @Override public void run() {//导航到设置页面 navController.navigate(R.id.menu_nav_config); } }); } } }); //把历史中通联成功的呼号与网格的对应关系 new DatabaseOpr.GetCallsignMapGrid(mainViewModel.databaseOpr.getDb()).execute(); mainViewModel.getFollowCallsignsFromDataBase(); //打开呼号位置信息的数据库,目前是以内存数据库方式。 if (GeneralVariables.callsignDatabase == null) { GeneralVariables.callsignDatabase = CallsignDatabase.getInstance(getBaseContext(), null, 1); } } /** * 检查权限 */ private void checkPermission() { mPermissionList.clear(); //判断哪些权限未授予 for (String permission : permissions) { if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) { mPermissionList.add(permission); } } //判断是否为空 if (!mPermissionList.isEmpty()) {//请求权限方法 String[] permissions = mPermissionList.toArray(new String[mPermissionList.size()]);//将List转为数组 ActivityCompat.requestPermissions(MainActivity.this, permissions, PERMISSION_REQUEST); } } /** * 响应授权 * 这里不管用户是否拒绝,都进入首页,不再重复申请权限 */ @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode != PERMISSION_REQUEST) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); } } /** * 显示串口设备列表 */ public void setSelectUsbDevice() { ArrayList ports = mainViewModel.mutableSerialPorts.getValue(); binding.selectSerialPortLinearLayout.removeAllViews(); for (int i = 0; i < ports.size(); i++) {//动态添加串口设备列表 View layout = LayoutInflater.from(getApplicationContext()) .inflate(R.layout.select_serial_port_list_view_item, null); layout.setId(i); TextView textView = layout.findViewById(R.id.selectSerialPortListViewItemTextView); textView.setText(ports.get(i).information()); binding.selectSerialPortLinearLayout.addView(layout); layout.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { //连接电台并做电台的频率设置等操作 mainViewModel.connectCableRig(getApplicationContext(), ports.get(view.getId())); binding.selectSerialPortLayout.setVisibility(View.GONE); } }); } //选择串口设备弹框 if ((ports.size() >= 1) && (!mainViewModel.isRigConnected())) { binding.selectSerialPortLayout.setVisibility(View.VISIBLE); } else {//说明没有可以识别的驱动,不显示设备弹框 binding.selectSerialPortLayout.setVisibility(View.GONE); } } /** * 删除指定文件夹中的所有文件 * * @param filePath 指定的文件夹 */ public static void deleteFolderFile(String filePath) { try { File file = new File(filePath);//获取SD卡指定路径 File[] files = file.listFiles();//获取SD卡指定路径下的文件或者文件夹 for (int i = 0; i < files.length; i++) { if (files[i].isFile()) {//如果是文件直接删除 File tempFile = new File(files[i].getPath()); tempFile.delete(); } } } catch (Exception e) { e.printStackTrace(); } } private void animationImage() { ObjectAnimator navigationAnimator = ObjectAnimator.ofFloat(binding.navView, "translationY", 200); navigationAnimator.setDuration(3000); navigationAnimator.setFloatValues(200, 200, 200, 0); ObjectAnimator hideLogoAnimator = ObjectAnimator.ofFloat(binding.initDataLayout, "alpha", 1f, 1f, 1f, 0); hideLogoAnimator.setDuration(3000); AnimatorSet animatorSet = new AnimatorSet(); animatorSet.playTogether(navigationAnimator, hideLogoAnimator); //animatorSet.playTogether(initPositionStrAnimator, logoAnimator, navigationAnimator, hideLogoAnimator); animatorSet.addListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animator) { } @Override public void onAnimationEnd(Animator animator) { //animationEnd = true; binding.initDataLayout.setVisibility(View.GONE); binding.utcProgressBar.setVisibility(View.VISIBLE); InitFloatView();//显示浮窗 //binding.floatView.setVisibility(View.VISIBLE); } @Override public void onAnimationCancel(Animator animator) { } @Override public void onAnimationRepeat(Animator animator) { } }); animatorSet.start(); } //此方法只有在android:launchMode="singleTask"模式下起作用 @Override protected void onNewIntent(Intent intent) { if ("android.hardware.usb.action.USB_DEVICE_ATTACHED".equals(intent.getAction())) { mainViewModel.getUsbDevice(); } super.onNewIntent(intent); } @Override public void onBackPressed() { if (navController.getGraph().getStartDestination() == navController.getCurrentDestination().getId()) {//说明是到最后一个页面了 AlertDialog.Builder builder = new AlertDialog.Builder(this) .setMessage(getString(R.string.exit_confirmation)) .setPositiveButton(getString(R.string.exit) , new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { if (mainViewModel.ft8TransmitSignal.isActivated()) { mainViewModel.ft8TransmitSignal.setActivated(false); } closeThisApp();//退出APP } }).setNegativeButton(getString(R.string.cancel) , new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { dialogInterface.dismiss(); } }); builder.create().show(); } else {//退出activity堆栈 navController.navigateUp(); //setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR); } } private void closeThisApp() { mainViewModel.ft8TransmitSignal.setActivated(false); if (mainViewModel.baseRig != null) { if (mainViewModel.baseRig.getConnector() != null) { mainViewModel.baseRig.getConnector().disconnect(); } } mainViewModel.ft8SignalListener.stopListen(); mainViewModel = null; System.exit(0); } /** * 注册蓝牙动作广播 */ private void registerBluetoothReceiver() { if (mReceive == null) { mReceive = new BluetoothStateBroadcastReceive(getApplicationContext(), mainViewModel); } IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); intentFilter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED); intentFilter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED); intentFilter.addAction(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED); intentFilter.addAction(AudioManager.EXTRA_SCO_AUDIO_PREVIOUS_STATE); intentFilter.addAction(AudioManager.ACTION_AUDIO_BECOMING_NOISY); intentFilter.addAction(AudioManager.EXTRA_SCO_AUDIO_STATE); intentFilter.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED); intentFilter.addAction(BluetoothAdapter.EXTRA_CONNECTION_STATE); intentFilter.addAction(BluetoothAdapter.EXTRA_STATE); intentFilter.addAction("android.bluetooth.BluetoothAdapter.STATE_OFF"); intentFilter.addAction("android.bluetooth.BluetoothAdapter.STATE_ON"); registerReceiver(mReceive, intentFilter); } /** * 注销蓝牙动作广播 */ private void unregisterBluetoothReceiver() { if (mReceive != null) { unregisterReceiver(mReceive); mReceive = null; } } @Override protected void onDestroy() { unregisterBluetoothReceiver(); super.onDestroy(); } }