package com.example.myapplication; import android.annotation.SuppressLint; import android.app.DatePickerDialog; import android.app.NotificationChannel; import android.app.NotificationManager; import android.app.TimePickerDialog; import android.content.ActivityNotFoundException; import android.content.BroadcastReceiver; import android.content.ClipData; import android.content.ClipboardManager; import android.content.ContentResolver; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.content.res.ColorStateList; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Color; import android.graphics.PixelFormat; import android.graphics.drawable.ColorDrawable; import android.location.Location; import android.location.LocationListener; import android.location.LocationManager; import android.media.ExifInterface; import android.media.MediaRecorder; import android.net.ConnectivityManager; import android.net.NetworkCapabilities; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Handler; import androidx.core.util.Pair; import android.os.Looper; import android.provider.MediaStore; import android.provider.Settings; import android.util.Base64; import android.util.Log; import android.util.SparseBooleanArray; import android.view.Gravity; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.Window; import android.view.WindowManager; import android.view.inputmethod.InputMethodManager; import android.webkit.MimeTypeMap; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.CheckBox; import android.widget.DatePicker; import android.widget.EditText; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ListView; import android.widget.ScrollView; import android.widget.Spinner; import android.widget.TextView; import android.widget.TimePicker; import android.widget.Toast; import android.Manifest; import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.contract.ActivityResultContracts; import androidx.annotation.NonNull; import androidx.annotation.RequiresApi; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; import androidx.core.content.FileProvider; import androidx.lifecycle.ViewModelStore; import androidx.lifecycle.ViewModelStoreOwner; import androidx.room.Room; import com.chaquo.python.PyException; import com.chaquo.python.PyObject; import com.chaquo.python.Python; import com.example.myapplication.DataBase.AppDatabase; import com.example.myapplication.DataBase.DatabaseHelper; import com.example.myapplication.Service.FloatingWindowService; import com.example.myapplication.Service.PhotoMonitoringService; import com.example.myapplication.Service.RecordingService; import com.example.myapplication.Tool.BackgroundToast; import com.example.myapplication.Tool.DynamicDataFetcher; import com.example.myapplication.Tool.PartListFetcher; import com.example.myapplication.Tool.PermissionUtils; import com.example.myapplication.Tool.ReportGenerator; import com.example.myapplication.Tool.ReportGeneratorHelper; import com.example.myapplication.Tool.RetrofitClient; import com.example.myapplication.api.AuthApi; import com.example.myapplication.model.ApiResponse; import com.example.myapplication.model.AudioEntity; import com.example.myapplication.model.ChangePasswordRequest; import com.example.myapplication.model.ImageEntity; import com.example.myapplication.model.PartResponse; import com.example.myapplication.model.SharedDataManager; import com.getbase.floatingactionbutton.FloatingActionButton; import com.google.android.material.datepicker.MaterialDatePicker; import com.google.android.material.switchmaterial.SwitchMaterial; import com.google.android.material.textfield.TextInputEditText; import org.json.JSONObject; import okhttp3.*; import retrofit2.Retrofit; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import javax.crypto.Cipher; import javax.crypto.spec.SecretKeySpec; // 在MainActivity类中添加以下变量 public class MainActivity extends AppCompatActivity implements ViewModelStoreOwner { private ViewModelStore viewModelStore = new ViewModelStore(); private DynamicDataFetcher fetcher; private Spinner spinnerImageSource; private List> imageSources = new ArrayList<>(); private PartListFetcher partListFetcher; private List cachedPartList; private AlertDialog reportcurrentDialog; // 添加成员变量来保存当前对话框 private boolean haveInitalFloating=false; private static String Temperator="0"; private static String Humidity="0"; private static String Weather="0"; private TextInputEditText etWeather, etTemperature, etHumidity; private Button btnSaveWeather; private TextView tvWeatherDisplay, tvTemperatureDisplay, tvHumidityDisplay; @Override public ViewModelStore getViewModelStore() { return viewModelStore; } private static final int NOTIFICATION_PERMISSION_REQUEST_CODE = 102; private DatabaseHelper dbHelper; private long startTimestamp = 0; private long endTimestamp = 0; private String startDateStr = ""; private String endDateStr = ""; private TextView tvUser; private TextView tvProject; private TextView tvProjectId; private Button btnAdmin; // 在类变量声明部分添加 private Button btnUploadVideos; private Button btnUploadVideos2; private FloatingActionButton floatingActionButton; private boolean isRecording = false; private MediaRecorder mediaRecorder; private String currentAudioPath; private String partid; private final OkHttpClient httpClient = new OkHttpClient.Builder() .connectTimeout(30, TimeUnit.SECONDS) // 连接超时 .readTimeout(60, TimeUnit.SECONDS) // 读取超时 .writeTimeout(60, TimeUnit.SECONDS) // 写入超时 .build(); private static final String TAG = "MainActivity"; private static String outputPath = "report" + ".json"; private TextView tvStatus; private final Handler mainHandler = new Handler(Looper.getMainLooper()); private ReportGeneratorHelper reportGenerator; private Button selectTimeButton; private TextView startTimeText; private TextView endTimeText; private long startTime = 0; private long endTime = 0; private TextView statusText; private long lastPhotoId = -1; private boolean isMonitoring = false; private SwitchMaterial switchMonitor; private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.getDefault()); private AppDatabase db; private static final int PERMISSION_REQUEST_CODE = 100; private static final int PERMISSIONS_REQUEST_CODE = 109; private AppDatabase db2; private Button btnHistory; private Button btnFailedAudios; private Button btnFailedAudios2; private Button btnHistory2; private AlertDialog currentDialog; private AlertDialog currentDialog2; private static final int THUMBNAIL_SIZE = 200; private static final int DECODE_SAMPLE_SIZE = 4; private final ExecutorService executor = Executors.newFixedThreadPool(4); // 添加位置相关变量 private LocationManager locationManager; private LocationListener locationListener; private Location lastKnownLocation; private static final int LOCATION_PERMISSION_REQUEST_CODE = 1002; private TextView locationText; private static final int AUDIO_PERMISSION_REQUEST_CODE = 1030; private Button btnUploadQueue; private Button btnStopUpload; private volatile boolean isUploading = false; private final Object uploadLock = new Object(); private EditText etUploadInterval; private Button btnSetInterval; private Button btnStartTimedUpload; private Button btnStopTimedUpload; private Handler timedUploadHandler; private Runnable timedUploadRunnable; private static final int OVERLAY_PERMISSION_REQUEST_CODE = 1003; private int uploadIntervalMinutes = 0; // 默认上传间隔(分钟) private boolean isTimedUploadRunning = false; private static final int REQUEST_VIDEO_PERMISSION = 1005; // 在类变量声明部分添加 private ActivityResultLauncher videoPickerLauncher; private InputMethodManager inputMethodManager; private ActivityResultLauncher requestPermissionLauncher; private TextView tvUnit, tvBlade,tvUnitName; private static final String ACTION_UPDATE_VALUES = "com.example.myapplication.UPDATE_VALUES"; private BroadcastReceiver updateReceiver; private String currentUsername; private String projectName ="-1"; private String projectId="-1"; private String unit = "-1"; private String unitName = "-1"; private String imgpath ="-1"; private String password="-1"; private int count1=0; private int count0=0; private final int PERMISSION_REQUEST_StartMonitor_CODE=99; private String name="-1"; private String token="-1"; private int blade =-1; private Button changepassword; private Map, String> nameToIdMap; private String ChooseImageSource ="-1"; private ActivityResultLauncher overlayPermissionLauncher; private SharedDataManager dataManager; private String UserId="-1"; private Button btnReadReport; private Button btnGenerateReport; private ReportGenerator reportGeneratorreal; @SuppressLint("UnspecifiedRegisterReceiverFlag") @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); reportGeneratorreal = new ReportGenerator(this); btnGenerateReport = findViewById(R.id.btn_generate_report); btnReadReport = findViewById(R.id.btn_read_report); btnGenerateReport.setOnClickListener(v -> { String outputpath= reportGeneratorreal.generateReport(); if (outputpath != null && !outputpath.startsWith("Error")) { // 获取文件夹中的所有文件 File folder = new File(outputpath); if (folder.exists() && folder.isDirectory()) { File[] files = folder.listFiles(); if (files != null && files.length > 0) { // 显示文件列表对话框 showFileListDialog(files); } else { Toast.makeText(this, "文件夹为空,没有可显示的文件", Toast.LENGTH_SHORT).show(); } } else { Toast.makeText(this, "路径无效或不是文件夹", Toast.LENGTH_SHORT).show(); } } else { // 显示错误信息 Toast.makeText(this, "报告生成失败: " + outputpath, Toast.LENGTH_LONG).show(); } // 检查路径是否有效 // 检查路径是否有效 }); String outputpath2 = getExternalFilesDir(null).getAbsolutePath(); btnReadReport.setOnClickListener( v->{if (outputpath2 != null && !outputpath2.startsWith("Error")) { // 获取文件夹中的所有文件 File folder = new File(outputpath2); if (folder.exists() && folder.isDirectory()) { File[] files = folder.listFiles(); if (files != null && files.length > 0) { // 显示文件列表对话框 showFileListDialog(files); } else { Toast.makeText(this, "文件夹为空,没有可显示的文件", Toast.LENGTH_SHORT).show(); } } else { Toast.makeText(this, "路径无效或不是文件夹", Toast.LENGTH_SHORT).show(); } } else { // 显示错误信息 Toast.makeText(this, "报告生成失败: " + outputpath2, Toast.LENGTH_LONG).show(); }} ); dataManager= SharedDataManager.getInstance(); tvStatus = findViewById(R.id.tv_status); reportGenerator = new ReportGeneratorHelper(this); etWeather = findViewById(R.id.et_weather); etTemperature = findViewById(R.id.et_temperature); etHumidity = findViewById(R.id.et_humidity); btnSaveWeather = findViewById(R.id.btn_save_weather); // 初始化天气信息显示视图 tvWeatherDisplay = findViewById(R.id.tv_weather_display); tvTemperatureDisplay = findViewById(R.id.tv_temperature_display); tvHumidityDisplay = findViewById(R.id.tv_humidity_display); // 从 SharedPreferences 加载保存的天气信息 SharedPreferences prefs = getSharedPreferences("WeatherPrefs", MODE_PRIVATE); Weather = prefs.getString("weather", "0"); Temperator = prefs.getString("temperature", "0"); Humidity = prefs.getString("humidity", "0"); // 设置输入框的默认值 etWeather.setText(Weather); etTemperature.setText(Temperator); etHumidity.setText(Humidity); // 更新显示 updateWeatherDisplay(); // 设置保存按钮点击事件 btnSaveWeather.setOnClickListener(v -> saveWeatherInfo()); initializeViews(); initializeDatabase(); initializeReceivers(); initializePermissionLaunchers(); initialProject(); requestInitialPermissions(); setupButtonListeners(); setupTimedUpload(); if(!haveInitalFloating) initFloatingActionButton(); SharedPreferences sharedPreferences = getSharedPreferences("LoginPrefs", Context.MODE_PRIVATE); token=sharedPreferences.getString("auth_token", "-1"); dataManager.setToken(token); dataManager.setProjectId(projectId); name=sharedPreferences.getString("name","-1"); UserId=sharedPreferences.getString("userid","-1"); dataManager.setUser(getCurrentUser()); initApp(); changepassword=findViewById(R.id.changePassword); changepassword.setOnClickListener(v -> { showChangePasswordDialog(currentUsername); }); partListFetcher=new PartListFetcher(this,token,projectId); new Thread(() -> { try { // 在子线程执行网络请求 cachedPartList = partListFetcher.fetchPartListSync(); // 初始化 nameToIdMap nameToIdMap = new HashMap<>(); for (PartResponse part : cachedPartList) { Pairkey=new Pair<>(part.getTurbineId(),part.getPartName()); nameToIdMap.put(key, part.getPartId()); } } catch (IOException e) { runOnUiThread(() -> showToast("网络错误: " + e.getMessage())); } catch (PartListFetcher.ApiException e) { runOnUiThread(() -> showToast("API 错误: " + e.getMessage())); } }).start(); spinnerImageSource = findViewById(R.id.spinner_image_source); // 使用 DynamicDataFetcher 替代 CommonImageSourceFetcher,指定数据类型为 "image_sources" fetcher = new DynamicDataFetcher(token, this, "image_sources"); loadImageSources(); // 设置下拉框选择监听 spinnerImageSource.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { @Override public void onItemSelected(AdapterView parent, View view, int position, long id) { if (position >= 0 && position < imageSources.size()) { Map selectedItem = imageSources.get(position); String value = selectedItem.get("value"); String key = selectedItem.get("key"); ChooseImageSource=key; dataManager.setChooseImageSource(ChooseImageSource); Toast.makeText(MainActivity.this, "选择了: " + value + " (Key: " + key + ")", Toast.LENGTH_SHORT).show(); } } @Override public void onNothingSelected(AdapterView parent) { // 未选择任何项 } }); } // 在Activity中 private void showFileListDialog(File[] files) { // 构建文件名列表和文件对象列表 List fileNames = new ArrayList<>(); final List fileList = new ArrayList<>(); // 用于存储文件对象,保持与显示列表一致 for (File file : files) { if (file.isFile()) { // 只显示文件,不显示子文件夹 fileNames.add(file.getName()); fileList.add(file); } } // 创建对话框 AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle("文件操作"); // 设置对话框选项 String[] dialogItems = {"查看文件", "删除选中文件", "删除所有文件"}; builder.setItems(dialogItems, (dialog, which) -> { switch (which) { case 0: // 查看文件 showFileSelectionDialog(fileList, false); break; case 1: // 删除选中文件 showMultiSelectDeleteDialog(fileList); break; case 2: // 删除所有文件 showDeleteAllConfirmationDialog(fileList); break; } }); builder.setNegativeButton("取消", null); builder.show(); } // 显示文件选择对话框(用于查看或单选删除) private void showFileSelectionDialog(List fileList, boolean isDeleteMode) { CharSequence[] items = new CharSequence[fileList.size()]; for (int i = 0; i < fileList.size(); i++) { items[i] = fileList.get(i).getName(); } AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(isDeleteMode ? "选择要删除的文件" : "选择要查看的文件"); builder.setItems(items, (dialog, which) -> { File selectedFile = fileList.get(which); if (isDeleteMode) { showDeleteConfirmationDialog(selectedFile); } else { openFile(selectedFile.getAbsolutePath()); } }); builder.setNegativeButton("取消", null); builder.show(); } // 显示多选删除对话框 private void showMultiSelectDeleteDialog(List fileList) { if (fileList.isEmpty()) { Toast.makeText(this, "没有可删除的文件", Toast.LENGTH_SHORT).show(); return; } CharSequence[] items = new CharSequence[fileList.size()]; final boolean[] checkedItems = new boolean[fileList.size()]; for (int i = 0; i < fileList.size(); i++) { items[i] = fileList.get(i).getName(); checkedItems[i] = false; // 初始状态未选中 } AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle("选择要删除的文件"); builder.setMultiChoiceItems(items, checkedItems, (dialog, which, isChecked) -> { checkedItems[which] = isChecked; }); builder.setPositiveButton("删除选中", (dialog, which) -> { int deleteCount = 0; for (int i = 0; i < checkedItems.length; i++) { if (checkedItems[i]) { File fileToDelete = fileList.get(i); if (fileToDelete.delete()) { deleteCount++; } } } Toast.makeText(this, "已删除 " + deleteCount + " 个文件", Toast.LENGTH_SHORT).show(); // 刷新文件列表 refreshFileList(); }); builder.setNegativeButton("取消", null); builder.show(); } // 显示删除所有文件确认对话框 private void showDeleteAllConfirmationDialog(List fileList) { if (fileList.isEmpty()) { Toast.makeText(this, "没有可删除的文件", Toast.LENGTH_SHORT).show(); return; } new AlertDialog.Builder(this) .setTitle("确认删除所有文件") .setMessage("确定要删除所有 " + fileList.size() + " 个文件吗?此操作不可恢复!") .setPositiveButton("删除全部", (dialog, which) -> { int deleteCount = 0; for (File file : fileList) { if (file.delete()) { deleteCount++; } } Toast.makeText(this, "已删除 " + deleteCount + " 个文件", Toast.LENGTH_SHORT).show(); // 刷新文件列表 refreshFileList(); }) .setNegativeButton("取消", null) .show(); } // 显示单个文件删除确认对话框 private void showDeleteConfirmationDialog(File fileToDelete) { new AlertDialog.Builder(this) .setTitle("确认删除") .setMessage("确定要删除 " + fileToDelete.getName() + " 吗?") .setPositiveButton("删除", (dialog, which) -> { if (fileToDelete.delete()) { Toast.makeText(this, "文件已删除", Toast.LENGTH_SHORT).show(); // 刷新文件列表 refreshFileList(); } else { Toast.makeText(this, "删除失败", Toast.LENGTH_SHORT).show(); } }) .setNegativeButton("取消", null) .show(); } // 刷新文件列表 private void refreshFileList() { // 重新获取文件列表并刷新对话框 File directory = getFilesDir(); // 或者您原来的文件目录 File[] files = directory.listFiles(); if (files != null) { showFileListDialog(files); } } private void openFile(String filePath) { File file = new File(filePath); if (!file.exists()) { Toast.makeText(this, "文件不存在", Toast.LENGTH_SHORT).show(); return; } Intent intent = new Intent(Intent.ACTION_VIEW); Uri uri = FileProvider.getUriForFile(this, getPackageName() + ".provider", file); // 根据文件类型设置 MIME 类型 String mimeType = getMimeType(file.getName()); if (mimeType == null) { Toast.makeText(this, "无法识别文件类型", Toast.LENGTH_SHORT).show(); return; } intent.setDataAndType(uri, mimeType); intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); try { startActivity(intent); } catch (ActivityNotFoundException e) { Toast.makeText(this, "无法打开文件,请安装支持的应用程序", Toast.LENGTH_SHORT).show(); } } // 根据文件扩展名获取 MIME 类型 private void saveWeatherInfo() { Weather = etWeather.getText().toString().trim(); Temperator = etTemperature.getText().toString().trim(); Humidity = etHumidity.getText().toString().trim(); // 简单验证(可选) if (Weather.isEmpty()) { Toast.makeText(this, "请输入天气", Toast.LENGTH_SHORT).show(); return; } // 保存到 SharedPreferences SharedPreferences prefs = getSharedPreferences("WeatherPrefs", MODE_PRIVATE); SharedPreferences.Editor editor = prefs.edit(); editor.putString("weather", Weather); editor.putString("temperature", Temperator); editor.putString("humidity", Humidity); editor.apply(); // 更新显示 updateWeatherDisplay(); // 提示保存成功 Toast.makeText(this, "天气信息已保存", Toast.LENGTH_SHORT).show(); } private void updateWeatherDisplay() { tvWeatherDisplay.setText("天气: " + (Weather.isEmpty() ? "未设置" : Weather)); tvTemperatureDisplay.setText("温度: " + (Temperator.isEmpty() ? "未设置" : Temperator + "℃")); tvHumidityDisplay.setText("湿度: " + (Humidity.isEmpty() ? "未设置" : Humidity + "%")); } private void startMonitoring() { String[] requiredPermissions = { Manifest.permission.CAMERA, Manifest.permission.FOREGROUND_SERVICE_CAMERA, Manifest.permission.READ_EXTERNAL_STORAGE }; if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(this, Manifest.permission.FOREGROUND_SERVICE_CAMERA) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, requiredPermissions, PERMISSION_REQUEST_StartMonitor_CODE); } else { Intent serviceIntent = new Intent(this, PhotoMonitoringService.class); serviceIntent.putExtra("command", "start_monitoring"); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { startForegroundService(serviceIntent); statusText.setText("当前状态:正在监听相册变化"); switchMonitor.setChecked(true); } else { startService(serviceIntent); statusText.setText("当前状态:正在监听相册变化"); switchMonitor.setChecked(true); } } } private void loadImageSources() { fetcher.fetchDynamicDataAsync(new DynamicDataFetcher.DynamicDataCallback() { @Override public void onSuccess(List> sources) { runOnUiThread(() -> { // 过滤掉null值 imageSources = new ArrayList<>(); for (Map item : sources) { if (item != null && !item.isEmpty()) { // 获取第一个也是唯一的entry(因为每个map只有一个键值对) Map.Entry entry = item.entrySet().iterator().next(); String key = entry.getKey(); String value = entry.getValue(); if (value != null) { // 创建一个新的map,统一结构为{"key":key, "value":value} Map normalizedItem = new HashMap<>(); normalizedItem.put("key", key); normalizedItem.put("value", value); imageSources.add(normalizedItem); } } } if (imageSources.isEmpty()) { showToast("图像数据为空"); } else { updateSpinner(imageSources); } }); } @Override public void onFailure(Throwable t) { runOnUiThread(() -> { Toast.makeText(MainActivity.this, "加载失败: " + t.getMessage(), Toast.LENGTH_SHORT).show(); }); } }); } private void updateSpinner(List> sources) { // 提取显示文本并确保不为null List displayValues = new ArrayList<>(); for (Map item : sources) { if (item != null && item.get("value") != null) { displayValues.add(item.get("value")); } } // 确保列表不为空 if (displayValues.isEmpty()) { displayValues.add("没有可用的选项"); } // 创建适配器 ArrayAdapter adapter = new ArrayAdapter<>( this, android.R.layout.simple_spinner_item, displayValues ); adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); // 设置适配器 spinnerImageSource.setAdapter(adapter); } public String getPartIdByNameFast(String tid,String partName) { Pairkey=new Pair<>(tid,partName); return nameToIdMap.get(key); } private void showChangePasswordDialog(String username) { hideKeyboard(); AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle("请修改密码"); View view = getLayoutInflater().inflate(R.layout.dialog_change_password, null); EditText etOldPassword = view.findViewById(R.id.etOldPassword); EditText etNewPassword = view.findViewById(R.id.etNewPassword); EditText etConfirmPassword = view.findViewById(R.id.etConfirmPassword); builder.setView(view); builder.setPositiveButton("确认", (dialog, which) -> { String oldPassword = etOldPassword.getText().toString().trim(); String newPassword = etNewPassword.getText().toString().trim(); String confirmPassword = etConfirmPassword.getText().toString().trim(); if (newPassword.equals(confirmPassword)) { changePassword(username, oldPassword, newPassword); } else { Toast.makeText(this, "新密码与确认密码不匹配", Toast.LENGTH_SHORT).show(); } }); builder.setNegativeButton("取消", null); builder.setCancelable(false); builder.show(); } private String md5(String input) { try { MessageDigest md = MessageDigest.getInstance("MD5"); byte[] messageDigest = md.digest(input.getBytes()); StringBuilder hexString = new StringBuilder(); for (byte b : messageDigest) { String hex = Integer.toHexString(0xff & b); if (hex.length() == 1) hexString.append('0'); hexString.append(hex); } return hexString.toString(); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } } private String encryptPassword(String username, String password) { try { // 1. 对账号做MD5计算 String md5Hash = md5(username); // 2. 取8-24位作为密钥 String key = md5Hash.substring(8, 24); // 8-24位(索引从0开始) Log.e("key:",key); // 3. AES加密 SecretKeySpec secretKey = new SecretKeySpec(key.getBytes(), "AES"); Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); // PKCS5Padding等同于PKCS7Padding在Java中 cipher.init(Cipher.ENCRYPT_MODE, secretKey); byte[] encryptedBytes = cipher.doFinal(password.getBytes()); return Base64.encodeToString(encryptedBytes, Base64.DEFAULT).trim(); } catch (Exception e) { e.printStackTrace(); return ""; } } private void changePassword(String username, String oldPassword, String newPassword) { ExecutorService executor = Executors.newSingleThreadExecutor(); executor.execute(() -> { try { // 加密旧密码和新密码 String encryptedOldPassword = encryptPassword(username, oldPassword); String encryptedNewPassword = encryptPassword(username, newPassword); // 创建修改密码请求体 ChangePasswordRequest request = new ChangePasswordRequest( username, encryptedNewPassword, encryptedOldPassword ); // 初始化Retrofit Retrofit retrofit = RetrofitClient.getClient(token); // 创建API服务 AuthApi authApi = retrofit.create(AuthApi.class); // 执行修改密码请求 retrofit2.Call> call = authApi.modifyPassword(request); retrofit2.Response> response = call.execute(); runOnUiThread(() -> { if (response.isSuccessful() && response.body() != null) { if (response.body().isSuccess()) { Toast.makeText(this, "密码修改成功,请重新登录", Toast.LENGTH_SHORT).show(); dbHelper.deleteUser(currentUsername); } else { Toast.makeText(this, "密码修改失败: " + response.body().toString(), Toast.LENGTH_SHORT).show(); } } else { String errorMsg = "修改密码失败: "; if (response.errorBody() != null) { try { errorMsg += response.errorBody().string(); } catch (IOException e) { errorMsg += "无法读取错误详情"; } } Toast.makeText(this, errorMsg, Toast.LENGTH_SHORT).show(); } }); } catch (Exception e) { runOnUiThread(() -> Toast.makeText(this, "修改密码出错: " + e.getMessage(), Toast.LENGTH_SHORT).show()); } }); } private void generateReportJson(String inputpath, List imageDataList, String outputPath, String starttime, String endtime) { String templateContent; File outputFile = new File(this.getExternalFilesDir(null), outputPath); if (!outputFile.getParentFile().exists()) { outputFile.getParentFile().mkdirs(); } Map> imagesByDay = new HashMap<>(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()); for (ImageEntity image : imageDataList) { String day = sdf.format(new Date(image.time)); if (!imagesByDay.containsKey(day)) { imagesByDay.put(day, new ArrayList<>()); } imagesByDay.get(day).add(image); } Set unitSet = new HashSet<>(); for (ImageEntity image : imageDataList) { if (image.unitName != null) { unitSet.add(image.unitName); } } List unitList = new ArrayList<>(unitSet); int n = unitList.size(); for (Map.Entry> dayEntry : imagesByDay.entrySet()) { String day = dayEntry.getKey(); List dayImages = dayEntry.getValue(); for (int i = 0; i < n; i++) { String unitName = unitList.get(i); List unitImages = new ArrayList<>(); for (ImageEntity image : dayImages) { if (unitName.equals(image.unitName)) { unitImages.add(image); } } if (unitImages.isEmpty()) { continue; } List imageAndResistanceList = new ArrayList<>(); for (ImageEntity image : unitImages) { imageAndResistanceList.add(new ReportGeneratorHelper.ImageData(image.path, "0.6")); } ImageEntity firstImage = unitImages.get(0); String weather = firstImage.weather != null ? firstImage.weather :"0"; String temperature = firstImage.temperature != null ? firstImage.temperature :"0"; String humidity = firstImage.humidity != null ? firstImage.humidity : "0"; if (i == 0) { templateContent = readJsonFromAssets(this, inputpath); } else { templateContent = null; } reportGenerator.generateReport( templateContent, outputFile.getAbsolutePath(), "#"+unitName, day, imageAndResistanceList, weather, temperature, humidity, starttime, endtime, new ReportGeneratorHelper.ReportGenerationCallback() { @Override public void onSuccess(String outputPath){ runOnUiThread(() -> { showToast("Json写入成功"); }); } @Override public void onError(String errorMessage) { runOnUiThread(()->{ showErrorDialog("Json写入失败",errorMessage); }); Log.e("生成失败", errorMessage); } } ); } // 在后台线程执行转换 new Thread(() -> { try { // 转换文件 File inputFile = new File(this.getExternalFilesDir(null), outputPath); String outputPath2 = "output" + day+ ".docx"; File outputFile2 = convertJsonToDocx(this,inputFile , outputPath2); // 更新UI必须在主线程 mainHandler.post(() -> { updateStatus("转换完成!"); showReportsList("output",".docx"); }); } catch (Exception e) { Log.e(TAG, "Conversion failed", e); mainHandler.post(() -> { updateStatus("转换失败"); showToast("转换失败: " + e.getMessage()); }); } }).start(); } } // 分享文件方法 // 删除文件方法 // 添加方法列出所有生成的报告 private List getGeneratedReports(String start, String end) { File dir = this.getExternalFilesDir(null); File[] files = dir.listFiles((d, name) -> (name.startsWith(start) && name.endsWith(end))); if (files == null) { return Collections.emptyList(); } List fileList = Arrays.asList(files); // 按日期从大到小排序 fileList.sort((f1, f2) -> { // 从文件名中提取日期部分 String dateStr1 = f1.getName().substring(start.length(), start.length() + 10); String dateStr2 = f2.getName().substring(start.length(), start.length() + 10); // 比较日期,返回逆序结果实现从大到小排序 return dateStr2.compareTo(dateStr1); }); return fileList; } // 显示报告列表对话框 private void showReportsList(String start, String end) { if (reportcurrentDialog != null && reportcurrentDialog.isShowing()) { reportcurrentDialog.dismiss(); } List reports = getGeneratedReports(start, end); if (reports.isEmpty()) { Toast.makeText(this, "没有找到生成的报告", Toast.LENGTH_SHORT).show(); if (reportcurrentDialog!= null && reportcurrentDialog.isShowing()) { reportcurrentDialog.dismiss(); } return; } // 创建自定义对话框 AlertDialog.Builder builder = new AlertDialog.Builder(this); LayoutInflater inflater = getLayoutInflater(); View dialogView = inflater.inflate(R.layout.dialog_report_list, null); builder.setView(dialogView); // 初始化视图 ListView listView = dialogView.findViewById(R.id.report_list); Button btnDeleteSelected = dialogView.findViewById(R.id.btn_delete_selected); Button btnDeleteAll = dialogView.findViewById(R.id.btn_delete_all); Button btnShare = dialogView.findViewById(R.id.btn_share); Button btnCancel = dialogView.findViewById(R.id.btn_cancel); // 创建适配器 ArrayAdapter adapter = new ArrayAdapter(this, R.layout.item_report, reports) { @Override public View getView(int position, View convertView, ViewGroup parent) { View view = convertView; if (view == null) { view = getLayoutInflater().inflate(R.layout.item_report, parent, false); } File file = getItem(position); CheckBox checkBox = view.findViewById(R.id.checkbox); TextView fileName = view.findViewById(R.id.file_name); fileName.setText(file.getName()); checkBox.setChecked(listView.isItemChecked(position)); return view; } }; listView.setAdapter(adapter); // 列表项点击事件 - 打开文件 listView.setOnItemClickListener((parent, view, position, id) -> { openGeneratedFile(reports.get(position)); }); // 列表项长按事件 - 切换选中状态 listView.setOnItemLongClickListener((parent, view, position, id) -> { boolean newState = !listView.isItemChecked(position); listView.setItemChecked(position, newState); adapter.notifyDataSetChanged(); return true; }); // 删除选中按钮 btnDeleteSelected.setOnClickListener(v -> { SparseBooleanArray checked = listView.getCheckedItemPositions(); List filesToDelete = new ArrayList<>(); for (int i = 0; i < checked.size(); i++) { if (checked.valueAt(i)) { filesToDelete.add(reports.get(checked.keyAt(i))); } } if (!filesToDelete.isEmpty()) { new AlertDialog.Builder(this) .setTitle("确认删除") .setMessage("确定要删除选中的 " + filesToDelete.size() + " 个报告吗?") .setPositiveButton("确定", (dialog1, which1) -> { deleteSelectedReports(filesToDelete, start, end); if (reportcurrentDialog != null && reportcurrentDialog.isShowing()) { reportcurrentDialog.dismiss(); } }) .setNegativeButton("取消", null) .show(); } else { Toast.makeText(this, "请先选择要删除的报告", Toast.LENGTH_SHORT).show(); } }); // 删除全部按钮 btnDeleteAll.setOnClickListener(v -> { if (!reports.isEmpty()) { new AlertDialog.Builder(this) .setTitle("确认删除") .setMessage("确定要删除全部 " + reports.size() + " 个报告吗?") .setPositiveButton("确定", (dialog1, which1) -> { deleteAllReports(reports, start, end); if (reportcurrentDialog != null && reportcurrentDialog.isShowing()) { reportcurrentDialog.dismiss(); } }) .setNegativeButton("取消", null) .show(); } }); // 分享按钮 btnShare.setOnClickListener(v -> { SparseBooleanArray checked = listView.getCheckedItemPositions(); List filesToShare = new ArrayList<>(); for (int i = 0; i < checked.size(); i++) { if (checked.valueAt(i)) { filesToShare.add(reports.get(checked.keyAt(i))); } } if (!filesToShare.isEmpty()) { shareSelectedFiles(filesToShare); } else { Toast.makeText(this, "请先选择要分享的报告", Toast.LENGTH_SHORT).show(); } }); // 返回按钮 btnCancel.setOnClickListener(v -> { if (reportcurrentDialog != null && reportcurrentDialog.isShowing()) { reportcurrentDialog.dismiss(); } }); // 创建并显示对话框 if (reportcurrentDialog != null && reportcurrentDialog.isShowing()) { reportcurrentDialog.dismiss(); } reportcurrentDialog = builder.create(); reportcurrentDialog.show(); } // 删除选中报告的方法 private void deleteSelectedReports(List filesToDelete, String start, String end) { int successCount = 0; for (File file : filesToDelete) { if (file.delete()) { successCount++; } } Toast.makeText(this, "已删除 " + successCount + " 个报告", Toast.LENGTH_SHORT).show(); // 删除后刷新列表 showReportsList(start, end); } private void shareSelectedFiles(List filesToShare) { if (filesToShare.size() == 1) { // 单个文件直接分享 shareGeneratedFile(filesToShare.get(0)); } else { // 多个文件需要先压缩或使用其他方式分享 shareMultipleFiles(filesToShare); } } private void shareGeneratedFile(File file) { try { Uri fileUri = FileProvider.getUriForFile(this, getApplicationContext().getPackageName() + ".provider", file); Intent shareIntent = new Intent(Intent.ACTION_SEND); shareIntent.setType(getMimeType(file.getAbsolutePath())); shareIntent.putExtra(Intent.EXTRA_STREAM, fileUri); shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); // 验证是否有应用可以处理这个Intent if (shareIntent.resolveActivity(getPackageManager()) != null) { startActivity(Intent.createChooser(shareIntent, "分享报告")); } else { Toast.makeText(this, "没有找到可以处理此文件的应用程序", Toast.LENGTH_SHORT).show(); } } catch (IllegalArgumentException e) { showErrorDialog("分享错误:",e.getMessage()); e.printStackTrace(); } } private void shareMultipleFiles(List files) { try { ArrayList uris = new ArrayList<>(); String packageName = getApplicationContext().getPackageName(); for (File file : files) { Uri fileUri = FileProvider.getUriForFile(this, packageName + ".provider", file); uris.add(fileUri); } Intent shareIntent = new Intent(Intent.ACTION_SEND_MULTIPLE); shareIntent.setType("*/*"); shareIntent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris); shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); // 验证是否有应用可以处理这个Intent if (shareIntent.resolveActivity(getPackageManager()) != null) { startActivity(Intent.createChooser(shareIntent, "分享多个报告")); } else { Toast.makeText(this, "没有找到可以处理这些文件的应用程序", Toast.LENGTH_SHORT).show(); } } catch (IllegalArgumentException e) { Toast.makeText(this, "分享文件失败: " + e.getMessage(), Toast.LENGTH_SHORT).show(); e.printStackTrace(); } } // 删除全部报告的方法 private void deleteAllReports(List reports, String start, String end) { int successCount = 0; for (File file : reports) { if (file.delete()) { successCount++; } } Toast.makeText(this, "已删除 " + successCount + " 个报告", Toast.LENGTH_SHORT).show(); // 删除后刷新列表 showReportsList(start, end); } // 获取MIME类型 private void initApp() { Button btnConvert = findViewById(R.id.btn_convert); Button btnread=findViewById(R.id.btn_readdocx); Button readoothermsg=findViewById(R.id.readothermsg); btnread.setOnClickListener(v->{ showReportsList("output",".docx"); }); readoothermsg.setOnClickListener(v-> { showErrorDialog("othermsg:",dataManager.getOthermsg()); }); btnConvert.setOnClickListener(v -> { if(PermissionUtils.checkAndRequestPermissions(this)) { // 显示处理中状态 updateStatus("正在处理..."); showDateRangePicker();} }); } private void showDateRangePicker() { // 创建日期选择对话框 MaterialDatePicker> picker = MaterialDatePicker.Builder .dateRangePicker() .setTitleText("选择日期范围") .setSelection(Pair.create( MaterialDatePicker.thisMonthInUtcMilliseconds(), MaterialDatePicker.todayInUtcMilliseconds() )) .build(); picker.addOnPositiveButtonClickListener(selection -> { try { // 保存原始时间戳 startTimestamp = selection.first; endTimestamp = selection.second; // 格式化为日期字符串 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()); startDateStr = sdf.format(new Date(startTimestamp)); endDateStr = sdf.format(new Date(endTimestamp)); // 查询数据库 queryDatabasesAndGenerateReport(); } catch (Exception e) { e.printStackTrace(); Toast.makeText(this, "日期处理错误: " + e.getMessage(), Toast.LENGTH_SHORT).show(); } }); picker.show(getSupportFragmentManager(), "DATE_RANGE_PICKER"); } private void queryDatabasesAndGenerateReport() { updateStatus("正在查询数据库..."); try { // 调整时间范围 Calendar startCal = Calendar.getInstance(); startCal.setTimeInMillis(startTimestamp); startCal.set(Calendar.HOUR_OF_DAY, 0); startCal.set(Calendar.MINUTE, 0); startCal.set(Calendar.SECOND, 0); startCal.set(Calendar.MILLISECOND, 0); Calendar endCal = Calendar.getInstance(); endCal.setTimeInMillis(endTimestamp); endCal.set(Calendar.HOUR_OF_DAY, 23); endCal.set(Calendar.MINUTE, 59); endCal.set(Calendar.SECOND, 59); endCal.set(Calendar.MILLISECOND, 999); long adjustedStart = startCal.getTimeInMillis(); long adjustedEnd = endCal.getTimeInMillis(); // 异步查询 new Thread(() -> { try { List listFromDb = db.imageDao().getImagesByTimeRange(adjustedStart, adjustedEnd); List listFromDb2 = db2.imageDao().getImagesByTimeRange(adjustedStart, adjustedEnd); // 合并结果 List combinedList = new ArrayList<>(); combinedList.addAll(listFromDb); combinedList.addAll(listFromDb2); // 回到主线程处理结果 runOnUiThread(() -> { if (combinedList.isEmpty()) { updateStatus("所选时间段内没有数据"); Toast.makeText(this, "所选时间段内没有数据", Toast.LENGTH_SHORT).show(); } else { updateStatus("正在生成报告..."); generateReportJson("testjson/input.json", combinedList, "output.json", startDateStr, endDateStr); } }); } catch (Exception e) { runOnUiThread(() -> { updateStatus("查询失败: " + e.getMessage()); Toast.makeText(this, "查询失败: " + e.getMessage(), Toast.LENGTH_SHORT).show(); }); } finally { if (db != null) db.close(); if (db2 != null) db2.close(); } }).start(); } catch (Exception e) { updateStatus("处理日期错误: " + e.getMessage()); Toast.makeText(this, "处理日期错误: " + e.getMessage(), Toast.LENGTH_SHORT).show(); } } private String readJsonFromOutputFile(File outputFile) { try { BufferedReader reader = new BufferedReader(new FileReader(outputFile)); StringBuilder sb = new StringBuilder(); String line; while ((line = reader.readLine()) != null) { sb.append(line); } reader.close(); return sb.toString(); } catch (IOException e) { showToast("读取输出JSON文件错误: " + e.getMessage()); return null; } } private File convertJsonToDocx(Context context, File outfile, String outputFileName) { String jsonData = readJsonFromOutputFile(outfile); if (jsonData == null || jsonData.trim().isEmpty()) { showErrorDialog("JSON错误", "空JSON数据或读取失败"); return null; } // 验证JSON格式 File outputFile = new File(context.getExternalFilesDir(null), outputFileName); try { Python py = Python.getInstance(); PyObject result = py.getModule("test") .callAttrThrows("json_to_docx", jsonData, outputFile.getAbsolutePath()); // 如果没有异常抛出,则认为成功 showToast("DOCX生成成功: " + outputFile.getPath()); return outputFile; } catch (PyException e) { // 专门处理Python异常 String pythonError = getPythonErrorDetails(e); showErrorDialog("Python处理错误", pythonError); return null; } catch (Throwable e) { // 处理其他Java异常 String errorMsg = buildErrorMessage((Exception) e); showErrorDialog("转换失败", errorMsg); Log.e(TAG, errorMsg); return null; } } // 构建完整的错误信息 private String buildErrorMessage(Exception e) { return "错误类型: " + e.getClass().getSimpleName() + "\n\n" + "错误信息: " + e.getMessage() + "\n\n" + "堆栈跟踪:\n" + Log.getStackTraceString(e); } // 显示错误对话框(主线程安全) private void showErrorDialog(String title, String message) { runOnUiThread(() -> { new AlertDialog.Builder(this) .setTitle(title) .setMessage(message) .setPositiveButton("确定", null) .setNegativeButton("复制", (dialog, which) -> { ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); ClipData clip = ClipData.newPlainText("复制信息", message); clipboard.setPrimaryClip(clip); // Optional: Show a toast to confirm the copy Toast.makeText(this, "信息已复制", Toast.LENGTH_SHORT).show(); }) .show(); }); } // 获取Python错误详情(优化版) private String getPythonErrorDetails(PyException e) { try { Python py = Python.getInstance(); PyObject traceback = py.getModule("traceback"); PyObject formatExc = traceback.callAttr("format_exc"); return "Python脚本错误:\n\n" + formatExc.toString() + "\n\n原始异常: " + e.getMessage(); } catch (Exception tracebackError) { return "无法解析Python错误详情: " + e.getMessage() + "\n(解析traceback时出错: " + tracebackError.getMessage() + ")"; } } private String readJsonFromAssets(Context context, String filename) { try { InputStream is = context.getAssets().open(filename); BufferedReader reader = new BufferedReader(new InputStreamReader(is)); StringBuilder sb = new StringBuilder(); String line; while ((line = reader.readLine()) != null) { sb.append(line); } reader.close(); return sb.toString(); } catch (IOException e) { showToast("读取JSON文件错误: " + e.getMessage()); return null; } } private void openGeneratedFile(File file) { if (file == null || !file.exists()) { showToast("文件不存在或创建失败"); return; } Intent intent = new Intent(Intent.ACTION_VIEW); Uri fileUri = FileProvider.getUriForFile( this, getPackageName() + ".provider", // 必须与 AndroidManifest.xml 中的 authorities 一致 file); // 根据文件扩展名动态设置 MIME 类型 String mimeType = getMimeType(file.getAbsolutePath()); intent.setDataAndType(fileUri, mimeType); intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); try { startActivity(intent); } catch (ActivityNotFoundException e) { showToast("没有找到可以打开该文件的应用"); // 提示用户安装支持的应用(如 WPS、Office) showOfficeInstallDialog(); } } private void showOfficeInstallDialog() { mainHandler.post(() -> { new AlertDialog.Builder(this) .setTitle("无法打开文件") .setMessage("您的设备上没有安装可以打开Word文档的应用。是否要安装Microsoft Word?") .setPositiveButton("安装", (d, w) -> { try { startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=com.microsoft.office.word"))); } catch (ActivityNotFoundException ex) { startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("https://play.google.com/store/apps/details?id=com.microsoft.office.word"))); } }) .setNegativeButton("取消", null) .show(); }); } // 辅助方法:在主线程更新状态 private void updateStatus(String message) { tvStatus.setText(message); } // 辅助方法:在主线程显示Toast private void showToast(String message) { mainHandler.post(() -> Toast.makeText(this, message, Toast.LENGTH_SHORT).show()); } private String getMimeType(String url) { // 初始化类型为null String type = null; // 从URL/文件路径中获取文件扩展名 String extension = MimeTypeMap.getFileExtensionFromUrl(url); // 如果扩展名不为空 if (extension != null) { // 根据扩展名获取对应的MIME类型 type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension.toLowerCase()); } // 如果找到了MIME类型则返回,否则返回通用类型"*/*" return type != null ? type : "*/*"; } private void initializeViews() { selectTimeButton = findViewById(R.id.selectTimeButton); startTimeText = findViewById(R.id.startTimeText); endTimeText = findViewById(R.id.endTimeText); btnUploadVideos2 = findViewById(R.id.btn_upload_videos2); btnUploadVideos = findViewById(R.id.btn_upload_videos); etUploadInterval = findViewById(R.id.et_upload_interval); etUploadInterval.setOnFocusChangeListener((v, hasFocus) -> { if (hasFocus) { showKeyboard(v); } else { hideKeyboard(); } }); btnFailedAudios = findViewById(R.id.btn_failed_audios); btnFailedAudios2 = findViewById(R.id.btn_success_audios); btnSetInterval = findViewById(R.id.btn_set_interval); btnStartTimedUpload = findViewById(R.id.btn_start_timed_upload); btnStopTimedUpload = findViewById(R.id.btn_stop_timed_upload); btnUploadQueue = findViewById(R.id.btn_upload_queue); btnStopUpload = findViewById(R.id.btn_stop_upload); btnHistory = findViewById(R.id.btn_history); btnHistory2 = findViewById(R.id.btn_history2); statusText = findViewById(R.id.status_text); switchMonitor = findViewById(R.id.switch_monitor); locationText = findViewById(R.id.location_text); btnAdmin = findViewById(R.id.btnAdmin); tvProject = findViewById(R.id.tv_project); tvProjectId = findViewById(R.id.tv_projectId); tvUser = findViewById(R.id.tv_user); tvUnit = findViewById(R.id.tvUnit); tvBlade = findViewById(R.id.tvBlade); tvUnitName = findViewById(R.id.tvUnitName); } private void initializeDatabase() { db = Room.databaseBuilder(getApplicationContext(), AppDatabase.class, "image-database").build(); db2 = Room.databaseBuilder(getApplicationContext(), AppDatabase.class, "image-database2").build(); } @SuppressLint("UnspecifiedRegisterReceiverFlag") private void initializeReceivers() { updateReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { unit = intent.getStringExtra("unit"); blade = intent.getIntExtra("blade", 0); unitName = intent.getStringExtra("unitName"); password=intent.getStringExtra("password"); dataManager.setUnit(unit); dataManager.setBlade(blade); String partname=getPartName(blade); partid=getPartIdByNameFast(unit,partname); dataManager.setPartid(partid); dataManager.setUnitName(unitName); updateValues(unit, blade, unitName); } }; IntentFilter filter = new IntentFilter(ACTION_UPDATE_VALUES); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { registerReceiver(updateReceiver, filter, Context.RECEIVER_NOT_EXPORTED); } else { registerReceiver(updateReceiver, filter); } } private void showKeyboard(View view) { if (view.requestFocus()) { view.postDelayed(() -> { if (inputMethodManager == null) { inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); } if (inputMethodManager != null) { inputMethodManager.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT); } }, 100); } } private void hideKeyboard() { View view = this.getCurrentFocus(); if (view != null) { if (inputMethodManager == null) { inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); } inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0); // 清除当前焦点 view.clearFocus(); } } private void initializePermissionLaunchers() { // Video picker launcher videoPickerLauncher = registerForActivityResult( new ActivityResultContracts.StartActivityForResult(), result -> { if (result.getResultCode() == RESULT_OK && result.getData() != null) { handleVideoSelection(result.getData()); } }); // Permission request launcher requestPermissionLauncher = registerForActivityResult( new ActivityResultContracts.RequestMultiplePermissions(), permissions -> { if (permissions.values().stream().allMatch(Boolean::booleanValue)) { openVideoPicker(); } else { Toast.makeText(this, "需要权限才能选择视频", Toast.LENGTH_SHORT).show(); } }); // Overlay permission launcher overlayPermissionLauncher = registerForActivityResult( new ActivityResultContracts.StartActivityForResult(), result -> { new Handler(Looper.getMainLooper()).postDelayed(() -> { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (Settings.canDrawOverlays(MainActivity.this)) { startFloatingService(); } else { Toast.makeText(MainActivity.this, "需要悬浮窗权限才能显示悬浮窗", Toast.LENGTH_SHORT).show(); } } }, 500); }); } private void initialProject() { Intent intent = getIntent(); currentUsername = intent.getStringExtra("username"); projectName = intent.getStringExtra("projectName"); projectId=intent.getStringExtra("projectId"); tvProject.setText(projectName); tvProjectId.setText(projectId); tvUser.setText(currentUsername); inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); } private void requestInitialPermissions() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { String[] permissions = { Manifest.permission.READ_MEDIA_IMAGES, Manifest.permission.READ_MEDIA_VIDEO, Manifest.permission.RECORD_AUDIO, Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.POST_NOTIFICATIONS }; if (checkSelfPermission(permissions)) { initLocationManager(); } else { ActivityCompat.requestPermissions(this, permissions, PERMISSIONS_REQUEST_CODE); } } else { String[] permissions = { Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.RECORD_AUDIO, Manifest.permission.ACCESS_FINE_LOCATION }; if (checkSelfPermission(permissions)) { initLocationManager(); } else { ActivityCompat.requestPermissions(this, permissions, PERMISSIONS_REQUEST_CODE); } } // Special handling for overlay permission if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !Settings.canDrawOverlays(this)) { requestOverlayPermission(); } } private boolean checkSelfPermission(String[] permissions) { for (String permission : permissions) { if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) { return false; } } return true; } private void setupButtonListeners() { // Set up all button click listeners here btnUploadVideos2.setOnClickListener(v -> openVideoPicker()); btnUploadVideos.setOnClickListener(v -> showVideoDateTimePicker()); selectTimeButton.setOnClickListener(v -> showDateTimePicker()); btnSetInterval.setOnClickListener(v -> { hideKeyboard(); // 先隐藏键盘 setUploadInterval(); // 然后执行设置逻辑 }); btnStartTimedUpload.setOnClickListener(v -> startTimedUpload()); btnStopTimedUpload.setOnClickListener(v -> stopTimedUpload()); btnUploadQueue.setOnClickListener(v -> startUploadQueue()); btnStopUpload.setOnClickListener(v -> stopUploadQueue()); btnFailedAudios.setOnClickListener(v -> showFailedAudios()); btnFailedAudios2.setOnClickListener(v -> showSuccessAudios()); btnHistory.setOnClickListener(v -> showHistory(db, "历史记录(点击查看大图)", "删除所有记录", "确定要删除所有历史记录吗?")); btnHistory2.setOnClickListener(v -> showHistory(db2, "缓存队列(点击查看大图)", "删除所有缓存", "确定要删除所有缓存吗?")); switchMonitor.setChecked(false); switchMonitor.setOnCheckedChangeListener((buttonView, isChecked) -> { if (isChecked) { if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions( this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, LOCATION_PERMISSION_REQUEST_CODE); } else { checkRecordPermission(); } } else { stopMonitoring(); } }); findViewById(R.id.btnShowFloating).setOnClickListener(v -> { if (FloatingWindowService.isFloatingWindowShowing) { // 如果悬浮窗正在显示,则关闭它 FloatingWindowService.closeFloatingWindow(MainActivity.this); } else { // 如果悬浮窗没有显示,则检查权限并启动 if (checkOverlayPermission2()) { startFloatingService(); } else { BackgroundToast.show(MainActivity.this, "需要悬浮窗权限"); } } }); btnAdmin.setOnClickListener(v -> { startActivity(new Intent(MainActivity.this, AdminActivity.class)); }); } private void showFailedAudios() { new Thread(() -> { List failedAudios = db.AudioDao().getAllAudios(); // Assuming you have getAll() in AudioDao runOnUiThread(() -> { AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle("上传失败的录音"); if (failedAudios == null || failedAudios.isEmpty()) { builder.setMessage("没有上传失败的录音"); builder.setPositiveButton("确定", null); builder.show(); return; } List displayItems = new ArrayList<>(); for (AudioEntity audio : failedAudios) { displayItems.add(String.format("录音路径: %s\n图片路径: %s\n时间: %s", audio.getAudioPath(), audio.getImagePath(), audio.getTime())); } // Convert to array for the adapter String[] items = displayItems.toArray(new String[0]); builder.setAdapter(new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, items), (dialog, which) -> { }); // Add delete and retry buttons builder.setNegativeButton("一键删除", (dialog, which) -> deleteAllFailedAudios(failedAudios)); builder.setNeutralButton("一键上传", (dialog, which) -> retryAllFailedAudios(failedAudios)); builder.show(); }); }).start(); } private void showSuccessAudios() { new Thread(() -> { List successAudios = db2.AudioDao().getAllAudios(); runOnUiThread(() -> { // 使用自定义主题的 AlertDialog AlertDialog.Builder builder = new AlertDialog.Builder(this, R.style.CustomAlertDialog); builder.setTitle("上传成功的录音"); // 设置自定义标题样式 TextView titleView = new TextView(this); titleView.setText("上传成功的录音"); titleView.setTextSize(20); titleView.setTextColor(ContextCompat.getColor(this, R.color.colorPrimary)); titleView.setPadding(40, 40, 40, 20); titleView.setGravity(Gravity.CENTER); builder.setCustomTitle(titleView); if (successAudios == null || successAudios.isEmpty()) { builder.setMessage("没有上传成功的录音"); builder.setPositiveButton("确定", null); builder.show(); return; } // 创建自定义适配器 ArrayAdapter adapter = new ArrayAdapter(this, R.layout.audio_item_layout, R.id.audio_item_text) { @Override public View getView(int position, View convertView, ViewGroup parent) { View view = super.getView(position, convertView, parent); TextView textView = view.findViewById(R.id.audio_item_text); // 设置item样式 textView.setTextColor(ContextCompat.getColor(MainActivity.this, R.color.textColor)); textView.setTextSize(16); textView.setPadding(30, 30, 30, 30); return view; } }; // 添加数据到适配器 for (AudioEntity audio : successAudios) { adapter.add(String.format("录音名称: %s\n时间: %s", getFileNameFromPath(audio.getAudioPath()), audio.getTime())); } builder.setAdapter(adapter, (dialog, which) -> { // 点击item打开录音文件 AudioEntity selectedAudio = successAudios.get(which); openAudioFile(selectedAudio.getAudioPath()); }); // 添加删除按钮 builder.setNegativeButton("一键删除", (dialog, which) -> deleteAllSuccessAudios(successAudios)); // 添加关闭按钮 builder.setPositiveButton("关闭", null); AlertDialog dialog = builder.create(); // 设置Dialog窗口属性 Window window = dialog.getWindow(); if (window != null) { window.setBackgroundDrawable(new ColorDrawable(Color.WHITE)); window.setGravity(Gravity.CENTER); } dialog.show(); // 设置按钮样式 Button negativeButton = dialog.getButton(DialogInterface.BUTTON_NEGATIVE); negativeButton.setTextColor(ContextCompat.getColor(this, R.color.colorAccent)); Button positiveButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE); positiveButton.setTextColor(ContextCompat.getColor(this, R.color.colorPrimary)); }); }).start(); } // 从路径中获取文件名 private String getFileNameFromPath(String path) { if (path == null || path.isEmpty()) return "未知录音"; int index = path.lastIndexOf('/'); return index != -1 ? path.substring(index + 1) : path; } // 打开录音文件 private void openAudioFile(String audioPath) { File file = new File(audioPath); if (!file.exists()) { Toast.makeText(this, "录音文件不存在", Toast.LENGTH_SHORT).show(); return; } try { // 使用正确的 authority Uri audioUri = FileProvider.getUriForFile( this, getPackageName() + ".provider", file ); Log.d("FileProvider", "转换后的URI: " + audioUri.toString()); Intent intent = new Intent(Intent.ACTION_VIEW); intent.setDataAndType(audioUri, "audio/*"); intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); // 检查是否有应用可以处理这个Intent if (intent.resolveActivity(getPackageManager()) != null) { startActivity(intent); } else { Toast.makeText(this, "没有找到可以播放录音的应用", Toast.LENGTH_SHORT).show(); } } catch (IllegalArgumentException e) { Log.e("录音文件失败:",e.getMessage()+"路径为"+audioPath); showErrorDialog("打开录音文件错误:",e.getMessage()+"路径为"+audioPath); } } private void setupTimedUpload() { timedUploadHandler = new Handler(Looper.getMainLooper()); SharedPreferences prefs = getSharedPreferences("UploadPrefs", MODE_PRIVATE); uploadIntervalMinutes = prefs.getInt("upload_interval", 0); if (uploadIntervalMinutes > 0) { etUploadInterval.setText(String.valueOf(uploadIntervalMinutes)); } } private void deleteAllSuccessAudios(List audios) { new Thread(() -> { for (AudioEntity audio : audios) { db2.AudioDao().delete(audio); } runOnUiThread(() -> { Toast.makeText(this, "已删除所有成功录音", Toast.LENGTH_SHORT).show(); }); }).start(); } private void deleteAllFailedAudios(List audios) { new Thread(() -> { for (AudioEntity audio : audios) { db.AudioDao().delete(audio); } runOnUiThread(() -> { Toast.makeText(this, "已删除所有失败录音", Toast.LENGTH_SHORT).show(); }); }).start(); } private void retryAllFailedAudios(List audios) { new Thread(() -> { for (AudioEntity audio : audios) { executor.execute(()->{ uploadAudio(audio.ImagePath,audio.AudioPath,audio.time); db.AudioDao().deleteByPath(audio.AudioPath);}); } BackgroundToast.show(MainActivity.this,"录音手动上传成功"); }).start(); } public void updateValues(String unit, int blade,String unitName) { runOnUiThread(() -> { tvUnit.setText( unit); tvBlade.setText( String.valueOf(blade)); tvUnitName.setText(unitName); }); } private boolean checkOverlayPermission2() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (!Settings.canDrawOverlays(this)) { requestOverlayPermission2(); return false; } } return true; } @RequiresApi(api = Build.VERSION_CODES.M) private void requestOverlayPermission2() { try { Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION); intent.setData(Uri.parse("package:" + getPackageName())); overlayPermissionLauncher.launch(intent); } catch (Exception e) { // 兼容厂商 ROM Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); intent.setData(Uri.parse("package:" + getPackageName())); startActivity(intent); } } private void startFloatingService() { Intent serviceIntent = new Intent(this, FloatingWindowService.class); serviceIntent.putExtra("PROJECT_ID", projectId); // 添加projectID到Intent if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { startForegroundService(serviceIntent); } else { startService(serviceIntent); } } private void openVideoPicker() { String[] requiredPermissions; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { // Android 14+ requiredPermissions = new String[]{ Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED, Manifest.permission.READ_MEDIA_VIDEO, Manifest.permission.READ_MEDIA_IMAGES }; } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { requiredPermissions = new String[]{Manifest.permission.READ_MEDIA_VIDEO, Manifest.permission.READ_MEDIA_IMAGES }; } else { requiredPermissions = new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}; } if (checkPermissions(requiredPermissions)) { launchVideoPicker(); } else { requestPermissionLauncher.launch(requiredPermissions); } } private boolean checkPermissions(String[] permissions) { for (String permission : permissions) { if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) { return false; } } return true; } private void launchVideoPicker() { Intent intent = new Intent(Intent.ACTION_GET_CONTENT); intent.setType("video/*"); intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); videoPickerLauncher.launch(Intent.createChooser(intent, "选择视频")); } private void handleVideoSelection(Intent data) { List videoUris = new ArrayList<>(); if (data.getClipData() != null) { // 多选 ClipData clipData = data.getClipData(); for (int i = 0; i < clipData.getItemCount(); i++) { videoUris.add(clipData.getItemAt(i).getUri()); } } else if (data.getData() != null) { // 单选 videoUris.add(data.getData()); } // 显示确认对话框 showQualificationDialog(videoUris); } private void showQualificationDialog(List videoUris) { new AlertDialog.Builder(this) .setTitle("机组合格确认") .setMessage("这批上传的机组视频是否合格?") .setPositiveButton("合格", (dialog, which) -> { processVideosWithQualification(videoUris, 1); }) .setNegativeButton("不合格", (dialog, which) -> { processVideosWithQualification(videoUris, 0); }) .setCancelable(false) .show(); } private void processVideosWithQualification(List videoUris, int qualified) { for (Uri videoUri : videoUris) { processVideoUri(videoUri, qualified); } } private void processVideoUri(Uri videoUri, int qualified) { String[] projection = { MediaStore.Video.Media.DATA, MediaStore.Video.Media.DATE_TAKEN, MediaStore.Video.Media.DURATION }; try (Cursor cursor = getContentResolver().query(videoUri, projection, null, null, null)) { if (cursor != null && cursor.moveToFirst()) { String path = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DATA)); long time = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DATE_TAKEN)); long duration = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATION)); String user = getCurrentUser(); uploadVideo(path, time, duration, lastKnownLocation, user, qualified); } } } private void showVideoEndDateTimePicker() { final Calendar calendar = Calendar.getInstance(); calendar.setTimeInMillis(startTime); calendar.add(Calendar.HOUR, 1); DatePickerDialog datePickerDialog = new DatePickerDialog(this, (view, year, month, day) -> { calendar.set(year, month, day); TimePickerDialog timePickerDialog = new TimePickerDialog(MainActivity.this, (view1, hour, minute) -> { calendar.set(Calendar.HOUR_OF_DAY, hour); calendar.set(Calendar.MINUTE, minute); endTime = calendar.getTimeInMillis(); if (endTime <= startTime) { Toast.makeText(MainActivity.this, "结束时间必须晚于开始时间", Toast.LENGTH_SHORT).show(); showVideoEndDateTimePicker(); return; } updateTimeDisplay(); showVideoConfirmationDialog(); }, calendar.get(Calendar.HOUR_OF_DAY), calendar.get(Calendar.MINUTE), true); timePickerDialog.setTitle("选择视频结束时间"); timePickerDialog.show(); }, calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH), calendar.get(Calendar.DAY_OF_MONTH)); datePickerDialog.setTitle("选择视频结束日期"); datePickerDialog.show(); } private void showVideoDateTimePicker() { final Calendar calendar = Calendar.getInstance(); Toast.makeText(this, "请选择视频开始时间", Toast.LENGTH_SHORT).show(); DatePickerDialog datePickerDialog = new DatePickerDialog(this, (view, year, month, day) -> { calendar.set(year, month, day); TimePickerDialog timePickerDialog = new TimePickerDialog(MainActivity.this, (view1, hour, minute) -> { calendar.set(Calendar.HOUR_OF_DAY, hour); calendar.set(Calendar.MINUTE, minute); startTime = calendar.getTimeInMillis(); updateTimeDisplay(); Toast.makeText(MainActivity.this, "请选择视频结束时间", Toast.LENGTH_SHORT).show(); showVideoEndDateTimePicker(); }, calendar.get(Calendar.HOUR_OF_DAY), calendar.get(Calendar.MINUTE), true); timePickerDialog.setTitle("选择视频开始时间"); timePickerDialog.show(); }, calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH), calendar.get(Calendar.DAY_OF_MONTH)); datePickerDialog.setTitle("选择视频开始日期"); datePickerDialog.show(); } private void showVideoConfirmationDialog() { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.getDefault()); String message = "确认上传以下时间范围内的视频吗?\n\n" + "开始时间: " + sdf.format(new Date(startTime)) + "\n" + "结束时间: " + sdf.format(new Date(endTime)); new AlertDialog.Builder(this) .setTitle("确认视频查询") .setMessage(message) .setPositiveButton("确认", (dialog, which) -> checkVideoPermissionAndQuery()) .setNegativeButton("取消", (dialog, which) -> Toast.makeText(MainActivity.this, "已取消视频查询", Toast.LENGTH_SHORT).show()) .setNeutralButton("重新选择", (dialog, which) -> showVideoDateTimePicker()) .show(); } private void checkVideoPermissionAndQuery() { String[] permissions; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { // Android 14+ permissions = new String[]{ Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED, Manifest.permission.READ_MEDIA_VIDEO, Manifest.permission.READ_MEDIA_IMAGES }; } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { // Android 13 permissions = new String[]{Manifest.permission.READ_MEDIA_VIDEO, Manifest.permission.READ_MEDIA_IMAGES }; } else { // Android 12- permissions = new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}; } if (checkAnyPermissionGranted(permissions)) { queryVideos(); } else { ActivityCompat.requestPermissions(this, permissions, REQUEST_VIDEO_PERMISSION); } } private void queryVideos() { ExecutorService executorService = Executors.newSingleThreadExecutor(); executorService.execute(() -> { List videoList = new ArrayList<>(); ContentResolver contentResolver = getContentResolver(); Uri uri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; String[] projection = { MediaStore.Video.Media._ID, MediaStore.Video.Media.DATA, MediaStore.Video.Media.DATE_TAKEN, MediaStore.Video.Media.DURATION }; String selection = MediaStore.Video.Media.DATE_TAKEN + " >= ? AND " + MediaStore.Video.Media.DATE_TAKEN + " <= ?"; String[] selectionArgs = {String.valueOf(startTime), String.valueOf(endTime)}; String sortOrder = MediaStore.Video.Media.DATE_TAKEN + " DESC"; try (Cursor cursor = contentResolver.query(uri, projection, selection, selectionArgs, sortOrder)) { if (cursor != null) { while (cursor.moveToNext()) { String path = cursor.getString(1); long time = cursor.getLong(2); long duration = cursor.getLong(3); videoList.add(new VideoInfo(path, time, duration)); } } } catch (Exception e) { Log.e("VideoQuery", "Error querying videos", e); } finally { executorService.shutdown(); } final int count = videoList.size(); runOnUiThread(() -> { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.getDefault()); String message = "找到 " + count + " 个视频 (" + sdf.format(new Date(startTime)) + " 到 " + sdf.format(new Date(endTime)) + ")"; if (count > 0) { // 显示确认对话框 new AlertDialog.Builder(MainActivity.this) .setTitle("视频上传确认") .setMessage(message + "\n\n这些视频是否合格?") .setPositiveButton("合格", (dialog, which) -> { uploadVideosWithQualification(videoList, 1); }) .setNegativeButton("不合格", (dialog, which) -> { uploadVideosWithQualification(videoList, 0); }) .show(); } else { Toast.makeText(MainActivity.this, message, Toast.LENGTH_LONG).show(); } }); }); } // 辅助类,用于存储视频信息 private static class VideoInfo { String path; long time; long duration; VideoInfo(String path, long time, long duration) { this.path = path; this.time = time; this.duration = duration; } } // 批量上传视频并设置合格标记 private void uploadVideosWithQualification(List videoList, int qualified) { String user = getCurrentUser(); for (VideoInfo video : videoList) { uploadVideo(video.path, video.time, video.duration, lastKnownLocation, user, qualified); } } // 添加视频上传方法 private void uploadVideo(String filePath, long time, long duration, Location location, String user,int q) { File file = new File(filePath); double latitude = location != null ? location.getLatitude() : 0; double longitude = location != null ? location.getLongitude() : 0; double altitude = location != null ? location.getAltitude() : 0; String finalunit = unit==null?"0":unit; RequestBody requestBody = new MultipartBody.Builder() .setType(MultipartBody.FORM) .addFormDataPart("file", file.getName(), RequestBody.create(file, MediaType.parse("video/*"))) .addFormDataPart("shootingTime", String.valueOf(time)) .addFormDataPart("duration", String.valueOf(duration)) .addFormDataPart("latitude", String.valueOf(latitude)) .addFormDataPart("longitude", String.valueOf(longitude)) .addFormDataPart("altitude", String.valueOf(altitude)) .addFormDataPart("workerUserId", user) .addFormDataPart("partId",finalunit) .addFormDataPart("qualified", String.valueOf(q)) .build(); Request request = new Request.Builder() .url("http://pms.dtyx.net:9158/video-file-info/batch-upload") .addHeader("Authorization",token)// 修改为您的视频上传URL .post(requestBody) .build(); httpClient.newCall(request).enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { BackgroundToast.show( MainActivity.this, "视频上传失败: " + e.getMessage() ); } @Override public void onResponse(Call call, Response response) { runOnUiThread(() -> { if (response.isSuccessful()) { BackgroundToast.show( MainActivity.this, "视频上传成功: " + response.code() ); } else { BackgroundToast.show( MainActivity.this, "视频上传失败: " + response.code() ); } }); } }); } private void initFloatingActionButton() { if (!checkOverlayPermission()) { requestOverlayPermission(); return; } floatingActionButton = new FloatingActionButton(this); floatingActionButton.setSize(FloatingActionButton.SIZE_MINI); floatingActionButton.setImageResource(R.drawable.ic_mic_off); floatingActionButton.setBackgroundTintList(ColorStateList.valueOf(Color.RED)); WindowManager.LayoutParams params = new WindowManager.LayoutParams( WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT, Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ? WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY : WindowManager.LayoutParams.TYPE_PHONE, WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSLUCENT); params.gravity = Gravity.TOP | Gravity.START; params.x = 0; params.y = 100; try { WindowManager windowManager = (WindowManager) getSystemService(WINDOW_SERVICE); windowManager.addView(floatingActionButton, params); } catch (Exception e) { Toast.makeText(this, "需要悬浮窗权限才能显示悬浮球", Toast.LENGTH_SHORT).show(); return; } floatingActionButton.setOnTouchListener(new View.OnTouchListener() { private int initialX; private int initialY; private float initialTouchX; private float initialTouchY; @Override public boolean onTouch(View v, MotionEvent event) { WindowManager windowManager = (WindowManager) getSystemService(WINDOW_SERVICE); WindowManager.LayoutParams params = (WindowManager.LayoutParams) floatingActionButton.getLayoutParams(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: initialX = params.x; initialY = params.y; initialTouchX = event.getRawX(); initialTouchY = event.getRawY(); return true; case MotionEvent.ACTION_MOVE: params.x = initialX + (int) (event.getRawX() - initialTouchX); params.y = initialY + (int) (event.getRawY() - initialTouchY); windowManager.updateViewLayout(floatingActionButton, params); return true; case MotionEvent.ACTION_UP: if (Math.abs(event.getRawX() - initialTouchX) < 10 && Math.abs(event.getRawY() - initialTouchY) < 10) { floatingActionButton.performClick(); } return true; } return false; } }); floatingActionButton.setOnClickListener(v -> { if (isRecording) { stopVoiceRecording(); } else { if (checkRecordPermission1()) { startVoiceRecording(); } else { requestRecordPermission(); } } }); } private boolean checkAndRequestNotificationPermission() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions( this, new String[]{Manifest.permission.POST_NOTIFICATIONS}, NOTIFICATION_PERMISSION_REQUEST_CODE ); return false; } } return true; } private void createNotificationChannel() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { NotificationChannel channel = new NotificationChannel( "recording_channel", "Recording Service", NotificationManager.IMPORTANCE_LOW ); NotificationManager manager = getSystemService(NotificationManager.class); manager.createNotificationChannel(channel); } } private boolean checkOverlayPermission() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { return Settings.canDrawOverlays(this); } return true; } private void requestOverlayPermission() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getPackageName())); startActivityForResult(intent, OVERLAY_PERMISSION_REQUEST_CODE); } } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == OVERLAY_PERMISSION_REQUEST_CODE) { if (checkOverlayPermission()) { } else { Toast.makeText(this, "悬浮窗权限被拒绝,无法显示悬浮球", Toast.LENGTH_SHORT).show(); } } } private boolean checkRecordPermission1() { return ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED; } private void requestRecordPermission() { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.RECORD_AUDIO}, AUDIO_PERMISSION_REQUEST_CODE); } private void startVoiceRecording() { Intent serviceIntent = new Intent(this, RecordingService.class); serviceIntent.setAction("start_recording"); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { startForegroundService(serviceIntent); } else { startService(serviceIntent); } isRecording = true; dataManager.setIsRecording(isRecording); floatingActionButton.setImageResource(R.drawable.ic_mic_on); } private void stopVoiceRecording() { Intent serviceIntent = new Intent(this, RecordingService.class); serviceIntent.setAction("stop_recording"); startService(serviceIntent); isRecording = false; dataManager.setIsRecording(isRecording); floatingActionButton.setImageResource(R.drawable.ic_mic_off); } private String getPartName(int b) { return "叶片" +b; } private void queryPhotos() { // 定义查询时间范围的条件(示例,可根据需要修改) String selection = MediaStore.Images.Media.DATE_TAKEN + " BETWEEN ? AND ? AND"+ "(" + MediaStore.Images.Media.DATA + " LIKE '%/DCIM/%' OR " + MediaStore.Images.Media.DATA + " LIKE '%/Camera/%')" ;; // 使用Executor替代AsyncTask ExecutorService executorService = Executors.newSingleThreadExecutor(); executorService.execute(() -> { int count = 0; ContentResolver contentResolver = getContentResolver(); Uri uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; String[] projection = { MediaStore.Images.Media._ID, MediaStore.Images.Media.DATA, MediaStore.Images.Media.DATE_TAKEN, MediaStore.Images.Media.LATITUDE, MediaStore.Images.Media.LONGITUDE }; String[] selectionArgs = {String.valueOf(startTime), String.valueOf(endTime)}; String sortOrder = MediaStore.Images.Media.DATE_TAKEN + " DESC"; try (Cursor cursor = contentResolver.query(uri, projection, selection, selectionArgs, sortOrder)) { if (cursor != null) { while (cursor.moveToNext()) { long id = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Images.Media._ID)); String path = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA)); long time = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATE_TAKEN)); // 初始化位置信息 double latitude = 0.0; double longitude = 0.0; double altitude = 0.0; // 从媒体库读取位置信息(如果有) int latIndex = cursor.getColumnIndex(MediaStore.Images.Media.LATITUDE); int lngIndex = cursor.getColumnIndex(MediaStore.Images.Media.LONGITUDE); if (latIndex != -1 && lngIndex != -1) { latitude = cursor.getDouble(latIndex); longitude = cursor.getDouble(lngIndex); } // 改进位置获取 try { if (locationManager != null && checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) { Location lastLocation = locationManager.getLastKnownLocation( LocationManager.GPS_PROVIDER); if (lastLocation != null) { lastKnownLocation = lastLocation; runOnUiThread(() -> updateLocationText(lastLocation)); } } } catch (SecurityException e) { e.printStackTrace(); } if (lastKnownLocation != null) { latitude = lastKnownLocation.getLatitude(); longitude = lastKnownLocation.getLongitude(); altitude = lastKnownLocation.getAltitude(); } // 改进EXIF高度读取 try { ExifInterface exif = new ExifInterface(path); String altitudeStr = exif.getAttribute(ExifInterface.TAG_GPS_ALTITUDE); if (altitudeStr != null) { String[] parts = altitudeStr.split("/"); if (parts.length == 2) { double alt = Double.parseDouble(parts[0]) / Double.parseDouble(parts[1]); altitude = alt; } } } catch (Exception e) { e.printStackTrace(); } String user = getCurrentUser(); Log.d("PhotoQuery", "Processing photo for user: " + user); // 使用更高效的方式生成缩略图 Bitmap thumbnail = null; try { BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeFile(path, options); options.inSampleSize = calculateInSampleSize(options, THUMBNAIL_SIZE, THUMBNAIL_SIZE); options.inJustDecodeBounds = false; thumbnail = BitmapFactory.decodeFile(path, options); } catch (Exception e) { e.printStackTrace(); } count++; if (thumbnail != null) { lastPhotoId = Math.max(lastPhotoId, id); double finalAltitude = altitude; double finalLongitude = longitude; double finalLatitude = latitude; String finalproject=getProject(); String finalunit=getUnit(); String partname=getPartName(blade); if(partid==null) partid=partname; // 使用另一个线程执行上传或保存操作 String finalPartid = partid; String finalPartid1 = partid; Executors.newSingleThreadExecutor().execute(() -> { if (isNetworkAvailable()) { uploadPhotoSync(path, time, lastKnownLocation, user, "0",finalproject,finalunit,blade, finalPartid,ChooseImageSource,unitName); } else { db2.imageDao().insert(new ImageEntity(path, time, finalLatitude, finalLongitude, finalAltitude, user, "0",finalproject,finalunit,blade,false,unitName,Temperator,Humidity,Weather,ChooseImageSource, finalPartid1)); } }); } } cursor.close(); } } catch (Exception e) { Log.e("PhotoQuery", "Error querying photos", e); } finally { executorService.shutdown(); } // 更新UI需要在主线程执行 final int finalCount = count; runOnUiThread(() -> { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.getDefault()); Toast.makeText(MainActivity.this, "找到 " + finalCount + " 张照片 (" + sdf.format(new Date(startTime)) + " 到 " + sdf.format(new Date(endTime)) + ")上传中", Toast.LENGTH_LONG).show(); }); }); } // 辅助方法计算采样大小 private int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) { final int height = options.outHeight; final int width = options.outWidth; int inSampleSize = 1; if (height > reqHeight || width > reqWidth) { final int halfHeight = height / 2; final int halfWidth = width / 2; while ((halfHeight / inSampleSize) >= reqHeight && (halfWidth / inSampleSize) >= reqWidth) { inSampleSize *= 2; } } return inSampleSize; } private void showDateTimePicker() { hideKeyboard(); final Calendar calendar = Calendar.getInstance(); // Show prompt for start time selection Toast.makeText(this, "请选择开始时间", Toast.LENGTH_SHORT).show(); // 日期选择器 DatePickerDialog datePickerDialog = new DatePickerDialog(this, new DatePickerDialog.OnDateSetListener() { @Override public void onDateSet(DatePicker view, int year, int month, int day) { calendar.set(year, month, day); // 时间选择器 TimePickerDialog timePickerDialog = new TimePickerDialog(MainActivity.this, new TimePickerDialog.OnTimeSetListener() { @Override public void onTimeSet(TimePicker view, int hour, int minute) { calendar.set(Calendar.HOUR_OF_DAY, hour); calendar.set(Calendar.MINUTE, minute); startTime = calendar.getTimeInMillis(); // Update start time display immediately updateTimeDisplay(); // Show prompt for end time selection Toast.makeText(MainActivity.this, "请选择结束时间", Toast.LENGTH_SHORT).show(); // 选择结束时间 showEndDateTimePicker(); } }, calendar.get(Calendar.HOUR_OF_DAY), calendar.get(Calendar.MINUTE), true); timePickerDialog.setTitle("选择开始时间"); timePickerDialog.show(); } }, calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH), calendar.get(Calendar.DAY_OF_MONTH)); datePickerDialog.setOnShowListener(dialog -> { // 确保日期选择器获得焦点 datePickerDialog.getButton(DialogInterface.BUTTON_POSITIVE).requestFocus(); }); datePickerDialog.setTitle("选择开始日期"); datePickerDialog.show(); } private void showEndDateTimePicker() { hideKeyboard(); final Calendar calendar = Calendar.getInstance(); // Set initial time to start time + 1 hour as a suggestion calendar.setTimeInMillis(startTime); calendar.add(Calendar.HOUR, 1); DatePickerDialog datePickerDialog = new DatePickerDialog(this, new DatePickerDialog.OnDateSetListener() { @Override public void onDateSet(DatePicker view, int year, int month, int day) { calendar.set(year, month, day); TimePickerDialog timePickerDialog = new TimePickerDialog(MainActivity.this, new TimePickerDialog.OnTimeSetListener() { @Override public void onTimeSet(TimePicker view, int hour, int minute) { calendar.set(Calendar.HOUR_OF_DAY, hour); calendar.set(Calendar.MINUTE, minute); endTime = calendar.getTimeInMillis(); // 验证结束时间是否晚于开始时间 if (endTime <= startTime) { Toast.makeText(MainActivity.this, "结束时间必须晚于开始时间", Toast.LENGTH_SHORT).show(); showEndDateTimePicker(); // 重新选择 return; } // 更新UI显示选择的时间 updateTimeDisplay(); // 显示确认对话框 showConfirmationDialog(); } }, calendar.get(Calendar.HOUR_OF_DAY), calendar.get(Calendar.MINUTE), true); timePickerDialog.setTitle("选择结束时间"); timePickerDialog.show(); } }, calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH), calendar.get(Calendar.DAY_OF_MONTH)); datePickerDialog.setOnShowListener(dialog -> { // 确保日期选择器获得焦点 datePickerDialog.getButton(DialogInterface.BUTTON_POSITIVE).requestFocus(); }); datePickerDialog.setTitle("选择结束日期"); datePickerDialog.show(); } private void showConfirmationDialog() { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.getDefault()); String message = "确认上传以下时间范围内的照片吗?\n\n" + "开始时间: " + sdf.format(new Date(startTime)) + "\n" + "结束时间: " + sdf.format(new Date(endTime)); new AlertDialog.Builder(this) .setTitle("确认查询") .setMessage(message) .setPositiveButton("确认", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { // 用户确认,开始查询照片 queryPhotos(); } }) .setNegativeButton("取消", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { // 用户取消,可以重新选择时间或不做任何操作 Toast.makeText(MainActivity.this, "已取消查询", Toast.LENGTH_SHORT).show(); } }) .setNeutralButton("重新选择", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { // 用户想重新选择时间 showDateTimePicker(); } }) .show(); } private void updateTimeDisplay() { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.getDefault()); if (startTime > 0) { startTimeText.setText("开始时间:" + sdf.format(new Date(startTime))); } if (endTime > 0) { endTimeText.setText("结束时间:" + sdf.format(new Date(endTime))); } } // 设置上传间隔 private void setUploadInterval() { try { hideKeyboard(); int minutes = Integer.parseInt(etUploadInterval.getText().toString()); if (minutes <= 0) { showToast("请输入大于0的分钟数"); return; } uploadIntervalMinutes = minutes; // 保存到SharedPreferences SharedPreferences.Editor editor = getSharedPreferences("UploadPrefs", MODE_PRIVATE).edit(); editor.putInt("upload_interval", uploadIntervalMinutes); editor.apply(); showToast( "上传间隔已设置为: " + minutes + "分钟"); // 如果定时上传正在运行,重新启动以应用新间隔 if (isTimedUploadRunning) { stopTimedUpload(); startTimedUpload(); } } catch (NumberFormatException e) { showToast("请输入有效的数字"); } } // 开始定时上传 private void startTimedUpload() { if (uploadIntervalMinutes <= 0) { showToast("请先设置上传间隔"); return; } if (isTimedUploadRunning) { showToast("定时上传已经在运行"); return; } isTimedUploadRunning = true; btnStartTimedUpload.setEnabled(false); btnStopTimedUpload.setEnabled(true); etUploadInterval.setEnabled(false); btnSetInterval.setEnabled(false); // 设置定时任务 timedUploadRunnable = new Runnable() { @Override public void run() { if (!isTimedUploadRunning) return; startUploadQueue(); timedUploadHandler.postDelayed(this, uploadIntervalMinutes * 60 * 1000L); } }; timedUploadHandler.postDelayed(timedUploadRunnable, uploadIntervalMinutes * 60 * 1000L); statusText.setText("定时上传已启动,间隔: " + uploadIntervalMinutes + "分钟"); } // 停止定时上传 private void stopTimedUpload() { if (!isTimedUploadRunning) return; isTimedUploadRunning = false; if (timedUploadRunnable != null) { timedUploadHandler.removeCallbacks(timedUploadRunnable); } btnStartTimedUpload.setEnabled(true); btnStopTimedUpload.setEnabled(false); etUploadInterval.setEnabled(true); btnSetInterval.setEnabled(true); statusText.setText("定时上传已停止"); } private void uploadPhotoSync(String filePath, long time, Location location, String user, String audioPath, String project, String unit, int blade, String partId, String imageSource, String unitName) { File file = new File(filePath); if (!file.exists()) { BackgroundToast.show(MainActivity.this, "文件不存在: " + filePath); return; } double latitude = location != null ? location.getLatitude() : 0; double longitude = location != null ? location.getLongitude() : 0; double altitude = location != null ? location.getAltitude() : 0; AudioEntity a=db.AudioDao().getAudioByPath(audioPath); try { // 构建请求URL HttpUrl.Builder urlBuilder = HttpUrl.parse("http://pms.dtyx.net:9158/image/" + imageSource + "/upload-batch/" + partId) .newBuilder() .addQueryParameter("collectorId",UserId) .addQueryParameter("collectorName", user) .addQueryParameter("humidness", String.valueOf(Humidity)) .addQueryParameter("shootingDistance", String.valueOf(0)) // 根据需要设置实际值 .addQueryParameter("temperatureMax", String.valueOf(Temperator)) .addQueryParameter("temperatureMin", String.valueOf(Temperator)) .addQueryParameter("weather", Weather) .addQueryParameter("windLevel", String.valueOf(0)); // 根据需要设置实际值 // 创建请求体 RequestBody requestBody = new MultipartBody.Builder() .setType(MultipartBody.FORM) .addFormDataPart("file", file.getName(), RequestBody.create(file, MediaType.parse("image/*"))) .build(); Request request = new Request.Builder() .url(urlBuilder.build()) .post(requestBody) .addHeader("Authorization", token) .addHeader("Content-Type", "application/x-www-form-urlencoded") .build(); try (Response response = httpClient.newCall(request).execute()) { if (response.isSuccessful()) { // 解析响应数据 String responseData = response.body().string(); JSONObject jsonResponse = new JSONObject(responseData); JSONObject data = jsonResponse.optJSONObject("data"); String imageId = data != null ? data.optString("imageId") : ""; if(a!=null) { uploadAudio(imageId,audioPath,token); } BackgroundToast.show( MainActivity.this, "上传成功: " + response.code() + " " + filePath ); executor.execute(() -> { db.imageDao().insert(new ImageEntity(filePath, time, latitude, longitude, altitude, user, audioPath, project, unit, blade, true, unitName, Temperator, Humidity, Weather, imageSource, partId)); }); } else { BackgroundToast.show(MainActivity.this, "上传失败: " + response.code() + " " + response.message() ); if (isUploading) { // 只有主动上传时才重新加入队列 executor.execute(() -> { db2.imageDao().insert(new ImageEntity(filePath, time, latitude, longitude, altitude, user, audioPath, project, unit, blade, false, unitName, Temperator, Humidity, Weather, imageSource, partId)); }); } } } } catch (Exception e) { if (isUploading) { // 只有主动上传时才重新加入队列 executor.execute(() -> { db2.imageDao().insert(new ImageEntity(filePath, time, latitude, longitude, altitude, user, audioPath, project, unit, blade, false, unitName, Temperator, Humidity, Weather, imageSource, partId)); }); } BackgroundToast.show(MainActivity.this, "上传失败: " + e.getMessage() ); } } private void stopUploadQueue() { isUploading = false; dataManager.setUploading(isUploading); // 中断当前上传 synchronized (uploadLock) { // 这里会中断当前上传 uploadLock.notifyAll(); // 唤醒可能正在等待的上传线程 } btnStopUpload.setEnabled(false); btnUploadQueue.setEnabled(true); // 更新状态显示 runOnUiThread(() -> { statusText.setText(isTimedUploadRunning ? "定时上传已暂停" : "上传已停止"); Toast.makeText(MainActivity.this, "上传队列已停止", Toast.LENGTH_SHORT).show(); }); } private void checkRecordPermission() { if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) { // 请求录音权限 ActivityCompat.requestPermissions( this, new String[]{Manifest.permission.RECORD_AUDIO}, AUDIO_PERMISSION_REQUEST_CODE); }else { // 已经有录音权限,继续检查图片权限 checkPermissionAndPickImage(); } } private void initLocationManager() { locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE); try { Location lastLocation = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER); if (lastLocation != null) { lastKnownLocation = lastLocation; updateLocationText(lastLocation); dataManager.setLocation(lastLocation); } } catch (SecurityException e) { e.printStackTrace(); BackgroundToast.show( MainActivity.this, " 获取位置失败" ); } locationListener = new LocationListener() { @Override public void onLocationChanged(Location location) { lastKnownLocation = location; Toast.makeText(MainActivity.this, "位置更新: " + location.getLatitude() + ", " + location.getLongitude(), Toast.LENGTH_SHORT).show(); // 打印到Logcat dataManager.setLocation(lastKnownLocation); updateLocationText(lastKnownLocation); } @Override public void onStatusChanged(String provider, int status, Bundle extras) {} @Override public void onProviderEnabled(String provider) {} @Override public void onProviderDisabled(String provider) {} }; try { if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) { Toast.makeText(this,"没有授予定位权限",Toast.LENGTH_SHORT).show(); return; } locationManager.requestLocationUpdates( LocationManager.GPS_PROVIDER, 60000, // 5秒更新间隔 10, // 10米距离变化 locationListener); } catch (Exception e) { e.printStackTrace(); } } private void updateLocationText(Location location) { runOnUiThread(() -> { if (location == null) { locationText.setText("当前位置: 无法获取位置信息"); return; } String locationInfo = String.format(Locale.getDefault(), "当前位置:\n纬度: %.6f\n经度: %.6f\n海拔: %.2f米\n精度: %.2f米\n更新时间: %s", location.getLatitude(), location.getLongitude(), location.getAltitude(), location.getAccuracy(), new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()).format(new Date(location.getTime()))); locationText.setText(locationInfo); }); } // 将位置转换为字符串用于打印 private String locationToString(Location location) { return String.format(Locale.getDefault(), "纬度: %.6f, 经度: %.6f, 海拔: %.2f, 精度: %.2f, 时间: %s", location.getLatitude(), location.getLongitude(), location.getAltitude(), location.getAccuracy(), new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()).format(new Date(location.getTime()))); } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == PERMISSION_REQUEST_StartMonitor_CODE) { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { // 权限已授予,启动服务 startMonitoring(); statusText.setText("正在监听中"); } else { // 权限被拒绝,显示提示 Toast.makeText(this, "需要权限才能运行服务", Toast.LENGTH_SHORT).show(); } } else if (requestCode == PERMISSIONS_REQUEST_CODE) { // 检查所有权限是否被授予 boolean allPermissionsGranted = true; for (int result : grantResults) { if (result != PackageManager.PERMISSION_GRANTED) { allPermissionsGranted = false; lastPhotoId=getLastPhotoId(); Log.e("最后一张照片值:",String.valueOf(lastPhotoId)); break; } } if (allPermissionsGranted) { // 所有权限都已授予,继续初始化位置管理器 initLocationManager(); haveInitalFloating=true; initFloatingActionButton(); } else { // 处理权限被拒绝的情况 handlePermissionDenial(); } } else if (requestCode == REQUEST_VIDEO_PERMISSION) { boolean allGranted = true; for (int result : grantResults) { if (result != PackageManager.PERMISSION_GRANTED) { allGranted = false; break; } } if(allGranted) { Toast.makeText(this, "媒体权限已授权", Toast.LENGTH_SHORT).show(); queryVideos(); } else { Toast.makeText(this, "需要媒体权限才能查询视频", Toast.LENGTH_SHORT).show(); } } //通知权限 else if (requestCode == NOTIFICATION_PERMISSION_REQUEST_CODE) { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { BackgroundToast.show(this,"通知权限已开启"); } else { Toast.makeText(this, "通知权限被拒绝,无法显示录音状态", Toast.LENGTH_SHORT).show(); } } else if (requestCode == LOCATION_PERMISSION_REQUEST_CODE) { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { // 位置权限已授予,继续检查图片权限 checkRecordPermission(); initLocationManager(); Toast.makeText(this, "位置权限已授予", Toast.LENGTH_SHORT).show(); } else { // 位置权限被拒绝,关闭开关 switchMonitor.setChecked(false); Toast.makeText(this, "需要位置权限才能获取位置信息", Toast.LENGTH_SHORT).show(); } } else if (requestCode == AUDIO_PERMISSION_REQUEST_CODE) { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { // 录音权限已授予,继续检查位置权限 checkPermissionAndPickImage(); Toast.makeText(this, "语音权限已授予", Toast.LENGTH_SHORT).show(); } else { // 录音权限被拒绝,关闭开关 switchMonitor.setChecked(false); Toast.makeText(this, "需要录音权限才能录制语音", Toast.LENGTH_SHORT).show(); } } //图片权限 else if (requestCode == PERMISSION_REQUEST_CODE) { boolean allGranted=true; for (int result : grantResults) { if (result != PackageManager.PERMISSION_GRANTED) { allGranted = false; break; } } if (allGranted) { Toast.makeText(this, "媒体权限已授与", Toast.LENGTH_SHORT).show(); startMonitoring(); } else { switchMonitor.setChecked(false); Toast.makeText(this, "需要媒体权限以选择图片", Toast.LENGTH_SHORT).show(); if (shouldShowRequestPermissionRationale(permissions)) { showPermissionExplanationDialog(() -> { // 重新请求权限 requestPermissions(permissions); }); } } } } private void handlePermissionDenial() { // 检查哪些关键权限被拒绝 boolean showRationale = false; // 检查是否有权限被永久拒绝(用户勾选了"不再询问") if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { for (String permission : new String[]{ Manifest.permission.READ_MEDIA_IMAGES, Manifest.permission.READ_MEDIA_VIDEO, Manifest.permission.RECORD_AUDIO, Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.POST_NOTIFICATIONS}) { if (!shouldShowRequestPermissionRationale(permission) && ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) { showRationale = true; break; } } } else { for (String permission : new String[]{ Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.RECORD_AUDIO, Manifest.permission.ACCESS_FINE_LOCATION}) { if (!shouldShowRequestPermissionRationale(permission) && ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) { showRationale = true; break; } } } if (showRationale) { // 显示解释为什么需要权限,并引导用户去设置页面 showPermissionExplanationDialog(); } else { // 部分权限被拒绝,但用户没有选择"不再询问" Toast.makeText(this, "部分功能可能无法正常使用", Toast.LENGTH_SHORT).show(); // 仍然尝试初始化位置管理器(如果位置权限被授予) if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) { initLocationManager(); } } } // 显示权限解释对话框 private void showPermissionExplanationDialog() { new AlertDialog.Builder(this) .setTitle("需要权限") .setMessage("某些功能需要权限才能正常工作。请前往设置授予权限。") .setPositiveButton("去设置", (dialog, which) -> { // 跳转到应用设置页面 Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); Uri uri = Uri.fromParts("package", getPackageName(), null); intent.setData(uri); startActivity(intent); }) .setNegativeButton("取消", null) .show(); } private void checkPermissionAndPickImage() { String[] permissions; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { // Android 14+ permissions = new String[]{ Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED, Manifest.permission.READ_MEDIA_IMAGES, Manifest.permission.READ_MEDIA_VIDEO }; } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { // Android 13 permissions = new String[]{Manifest.permission.READ_MEDIA_IMAGES, Manifest.permission.READ_MEDIA_VIDEO}; } else { // Android 12- permissions = new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}; } if (checkAnyPermissionGranted(permissions)) { try{startMonitoring();} catch (Exception e) { dataManager.setOthermsg(e.toString()); BackgroundToast.show(this,"监听失败"); } } else { requestPermissions(permissions); } } private boolean checkAnyPermissionGranted(String[] permissions) { for (String perm : permissions) { if (ContextCompat.checkSelfPermission(this, perm) == PackageManager.PERMISSION_GRANTED) { return true; } } return false; } private void requestPermissions(String[] permissions) { if (shouldShowRequestPermissionRationale(permissions)) { showPermissionExplanationDialog(() -> ActivityCompat.requestPermissions(this, permissions, PERMISSION_REQUEST_CODE)); } else { ActivityCompat.requestPermissions(this, permissions, PERMISSION_REQUEST_CODE); } } private boolean shouldShowRequestPermissionRationale(String[] permissions) { for (String perm : permissions) { if (ActivityCompat.shouldShowRequestPermissionRationale(this, perm)) { return true; } } return false; } private void openAppSettings() { Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); intent.setData(Uri.fromParts("package", getPackageName(), null)); startActivity(intent); } private void showPermissionExplanationDialog(Runnable onContinue) { new AlertDialog.Builder(this) .setTitle("需要权限") .setMessage("此功能需要访问图片权限以监控相册变化") .setPositiveButton("继续", (dialog, which) -> onContinue.run()) .setNegativeButton("取消", null) .show(); } private void showHistory(AppDatabase database, String title, String deleteBtnText, String deleteMessage) { new Thread(() -> { List history = database.imageDao().getAll(); runOnUiThread(() -> { AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(title); LinearLayout mainContainer = createDialogContainer(deleteBtnText, database, deleteMessage, history); ScrollView scrollContainer = createScrollContainer(history, database); mainContainer.addView(scrollContainer); builder.setView(mainContainer); builder.setPositiveButton("关闭", null); AlertDialog dialog = builder.create(); updateCurrentDialog(database, dialog); dialog.show(); }); }).start(); } private LinearLayout createDialogContainer(String deleteBtnText, AppDatabase database, String deleteMessage, List history) { LinearLayout mainContainer = new LinearLayout(this); mainContainer.setOrientation(LinearLayout.VERTICAL); // 顶部按钮布局 LinearLayout buttonLayout = new LinearLayout(this); buttonLayout.setOrientation(LinearLayout.HORIZONTAL); buttonLayout.setLayoutParams(new LinearLayout.LayoutParams( LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT)); // 批量删除按钮 Button batchDeleteButton = new Button(this); batchDeleteButton.setText("批量删除"); batchDeleteButton.setOnClickListener(v -> showBatchDeleteDialog(database, history)); buttonLayout.addView(batchDeleteButton); // 时间段删除按钮 Button timeRangeDeleteButton = new Button(this); timeRangeDeleteButton.setText("时间段删除"); timeRangeDeleteButton.setOnClickListener(v -> showTimeRangeDeleteDialog(database)); buttonLayout.addView(timeRangeDeleteButton); // 删除所有按钮 Button deleteAllButton = new Button(this); deleteAllButton.setText(deleteBtnText); deleteAllButton.setOnClickListener(v -> confirmDelete(database, deleteMessage)); buttonLayout.addView(deleteAllButton); mainContainer.addView(buttonLayout); return mainContainer; } private ScrollView createScrollContainer(List history, AppDatabase database) { ScrollView scrollContainer = new ScrollView(this); LinearLayout contentContainer = new LinearLayout(this); contentContainer.setOrientation(LinearLayout.VERTICAL); if (history.isEmpty()) { addEmptyView(contentContainer, database); } else { addHistoryItems(contentContainer, history, database); } scrollContainer.addView(contentContainer); return scrollContainer; } private void addEmptyView(LinearLayout contentContainer, AppDatabase database) { TextView emptyText = new TextView(this); emptyText.setText(database == db ? "暂无历史记录" : "暂无缓存记录"); contentContainer.addView(emptyText); } private void addHistoryItems(LinearLayout contentContainer, List history, AppDatabase database) { for (ImageEntity item : history) { View itemView = LayoutInflater.from(this).inflate(R.layout.item_history, contentContainer, false); // 添加复选框 CheckBox checkBox = new CheckBox(this); checkBox.setTag(item.id); // 使用记录的ID作为tag LinearLayout itemLayout = (LinearLayout) itemView.findViewById(R.id.item_layout); if (itemLayout != null) { itemLayout.addView(checkBox, 0); // 将复选框添加到最前面 } setupItemView(itemView, item); contentContainer.addView(itemView); } } private void showBatchDeleteDialog(AppDatabase database, List history) { AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle("批量删除"); // 获取所有复选框的选中状态 List selectedIds = new ArrayList<>(); ViewGroup container = (ViewGroup) ((ViewGroup) currentDialog.getWindow().getDecorView()).getChildAt(0); findSelectedCheckBoxes(container, selectedIds); if (selectedIds.isEmpty()) { builder.setMessage("请先勾选要删除的项"); builder.setPositiveButton("确定", null); } else { builder.setMessage("确定要删除选中的 " + selectedIds.size() + " 项记录吗?"); builder.setPositiveButton("删除", (dialog, which) -> { deleteSelectedRecords(database, selectedIds); }); builder.setNegativeButton("取消", null); } builder.show(); } // 查找选中的复选框 private void findSelectedCheckBoxes(ViewGroup container, List selectedIds) { for (int i = 0; i < container.getChildCount(); i++) { View child = container.getChildAt(i); if (child instanceof ViewGroup) { findSelectedCheckBoxes((ViewGroup) child, selectedIds); } else if (child instanceof CheckBox) { CheckBox checkBox = (CheckBox) child; if (checkBox.isChecked()) { selectedIds.add((Integer) checkBox.getTag()); } } } } // 删除选中的记录 private void deleteSelectedRecords(AppDatabase database, List selectedIds) { executor.execute(() -> { database.imageDao().deleteByIds(selectedIds); runOnUiThread(() -> { Toast.makeText(this, "已删除选中的 " + selectedIds.size() + " 项记录", Toast.LENGTH_SHORT).show(); dismissCurrentDialog(database); }); }); } // 显示时间段删除对话框 // 显示时间段删除对话框 - 优化后的版本 private void showTimeRangeDeleteDialog(AppDatabase database) { AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle("按时间段删除"); // 使用ScrollView确保内容可滚动 ScrollView scrollView = new ScrollView(this); LinearLayout layout = new LinearLayout(this); layout.setOrientation(LinearLayout.VERTICAL); layout.setPadding(32, 32, 32, 32); // 增加内边距 // 开始时间部分 TextView startText = new TextView(this); startText.setText("开始时间:"); startText.setTextSize(18); layout.addView(startText); // 开始日期和时间选择器 LinearLayout startTimeLayout = new LinearLayout(this); startTimeLayout.setOrientation(LinearLayout.VERTICAL); final DatePicker startDatePicker = new DatePicker(this); startDatePicker.setCalendarViewShown(false); // 使用spinner模式更紧凑 startTimeLayout.addView(startDatePicker); final TimePicker startTimePicker = new TimePicker(this); startTimePicker.setIs24HourView(true); startTimeLayout.addView(startTimePicker); layout.addView(startTimeLayout); // 添加分隔空间 View space = new View(this); space.setLayoutParams(new LinearLayout.LayoutParams( LinearLayout.LayoutParams.MATCH_PARENT, 32)); layout.addView(space); // 结束时间部分 TextView endText = new TextView(this); endText.setText("结束时间:"); endText.setTextSize(18); layout.addView(endText); // 结束日期和时间选择器 LinearLayout endTimeLayout = new LinearLayout(this); endTimeLayout.setOrientation(LinearLayout.VERTICAL); final DatePicker endDatePicker = new DatePicker(this); endDatePicker.setCalendarViewShown(false); endTimeLayout.addView(endDatePicker); final TimePicker endTimePicker = new TimePicker(this); endTimePicker.setIs24HourView(true); endTimeLayout.addView(endTimePicker); layout.addView(endTimeLayout); scrollView.addView(layout); builder.setView(scrollView); builder.setPositiveButton("删除", (dialog, which) -> { // 获取开始时间 Calendar startCal = Calendar.getInstance(); startCal.set(startDatePicker.getYear(), startDatePicker.getMonth(), startDatePicker.getDayOfMonth(), startTimePicker.getHour(), startTimePicker.getMinute(), 0); long startTime = startCal.getTimeInMillis(); // 获取结束时间 Calendar endCal = Calendar.getInstance(); endCal.set(endDatePicker.getYear(), endDatePicker.getMonth(), endDatePicker.getDayOfMonth(), endTimePicker.getHour(), endTimePicker.getMinute(), 59); long endTime = endCal.getTimeInMillis(); if (startTime > endTime) { Toast.makeText(this, "开始时间不能晚于结束时间", Toast.LENGTH_SHORT).show(); return; } confirmTimeRangeDelete(database, startTime, endTime); }); builder.setNegativeButton("取消", null); // 创建并显示对话框 AlertDialog dialog = builder.create(); // 设置对话框大小 Window window = dialog.getWindow(); if (window != null) { WindowManager.LayoutParams lp = new WindowManager.LayoutParams(); lp.copyFrom(window.getAttributes()); lp.width = WindowManager.LayoutParams.MATCH_PARENT; lp.height = WindowManager.LayoutParams.WRAP_CONTENT; window.setAttributes(lp); } dialog.show(); } // 确认时间段删除 private void confirmTimeRangeDelete(AppDatabase database, long startTime, long endTime) { new AlertDialog.Builder(this) .setTitle("确认删除") .setMessage(String.format("确定要删除 %s 至 %s 的所有记录吗?", sdf.format(new Date(startTime)), sdf.format(new Date(endTime)))) .setPositiveButton("删除", (d, w) -> deleteRecordsByTimeRange(database, startTime, endTime)) .setNegativeButton("取消", null) .show(); } // 删除时间段内的记录 private void deleteRecordsByTimeRange(AppDatabase database, long startTime, long endTime) { executor.execute(() -> { int deletedCount = database.imageDao().deleteByTimeRange(startTime, endTime); runOnUiThread(() -> { Toast.makeText(this, "已删除 " + deletedCount + " 条记录", Toast.LENGTH_SHORT).show(); dismissCurrentDialog(database); }); }); } private void setupItemView(View itemView, ImageEntity item) { ImageView iv = itemView.findViewById(R.id.iv_thumbnail); TextView tv = itemView.findViewById(R.id.tv_info); executor.execute(() -> loadThumbnail(iv, item.path)); tv.setText(String.format( Locale.US, "时间: %s\n" + "路径: %s\n"+ "纬度: %.6f\n" + // 保留6位小数 "经度: %.6f\n" + "高度: %.2f 米\n" + // 保留2位小数 "参与人名: %s\n"+ "项目名字:%s\n"+ "机组号:%s\n"+ "叶片号:%d\n"+ "图片来源:%s\n", sdf.format(new Date(item.time)), // 时间 item.path, // 路径 item.latitude, // 纬度 item.longitude, // 经度 item.altitude, // 高度(单位:米) item.user,// 用户信息 projectName, item.unitName, item.blade, item.imageSource )); itemView.setOnClickListener(v -> openFullImage(item.path)); } private void loadThumbnail(ImageView iv, String path) { try { BitmapFactory.Options options = new BitmapFactory.Options(); options.inSampleSize = DECODE_SAMPLE_SIZE; Bitmap src = BitmapFactory.decodeFile(path, options); if (src != null) { Bitmap thumbnail = Bitmap.createScaledBitmap(src, THUMBNAIL_SIZE, THUMBNAIL_SIZE, true); runOnUiThread(() -> iv.setImageBitmap(thumbnail)); } } catch (Exception e) { e.printStackTrace(); } } private void confirmDelete(AppDatabase database, String message) { new AlertDialog.Builder(this) .setTitle("确认删除") .setMessage(message) .setPositiveButton("删除", (d, w) -> deleteAllRecords(database)) .setNegativeButton("取消", null) .show(); } private void deleteAllRecords(AppDatabase database) { executor.execute(() -> { database.imageDao().deleteAll(); runOnUiThread(() -> { Toast.makeText(this, "已删除所有记录", Toast.LENGTH_SHORT).show(); dismissCurrentDialog(database); }); }); } private void updateCurrentDialog(AppDatabase database, AlertDialog dialog) { if (database == db) { currentDialog = dialog; } else { currentDialog2 = dialog; } } private void dismissCurrentDialog(AppDatabase database) { if (database == db && currentDialog != null && currentDialog.isShowing()) { currentDialog.dismiss(); } else if (database == db2 && currentDialog2 != null && currentDialog2.isShowing()) { currentDialog2.dismiss(); } } private boolean isNetworkAvailable() { ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); if (cm == null) return false; NetworkCapabilities capabilities = cm.getNetworkCapabilities(cm.getActiveNetwork()); return capabilities != null && (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) || capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) || capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)); } private void stopMonitoring() { Intent serviceIntent = new Intent(this, PhotoMonitoringService.class); stopService(serviceIntent); statusText.setText("当前状态:未监听"); switchMonitor.setChecked(false); } private void startUploadQueue() { if (isUploading) { Toast.makeText(this, "上传正在进行中", Toast.LENGTH_SHORT).show(); return; } executor.execute(() -> { isUploading = true; dataManager.setUploading(isUploading); runOnUiThread(() -> { btnUploadQueue.setEnabled(false); btnStopUpload.setEnabled(true); statusText.setText("正在上传缓存队列..." + (isTimedUploadRunning ? "(定时上传)" : "")); }); try { while (isUploading) { List queue = db2.imageDao().getAll(); if (queue.isEmpty()) { break; } for (ImageEntity image : queue) { if (!isUploading) break; String partname=getPartName(image.blade); if(partid==null) partid=partname; synchronized (uploadLock) { uploadPhotoSync(image.path, image.time, new Location("") {{ setLatitude(image.latitude); setLongitude(image.longitude); setAltitude(image.altitude); }}, image.user, image.audioPath,image.project,image.unit,image.blade,partid,image.imageSource,image.unitName); } if (isUploading) { db2.imageDao().delete(image); } } } } finally { runOnUiThread(() -> { btnUploadQueue.setEnabled(true); btnStopUpload.setEnabled(false); String status = isUploading ? (isTimedUploadRunning ? "定时上传完成" : "手动上传完成") : "上传已终止"; statusText.setText(status); }); isUploading = false; dataManager.setUploading(isUploading); } }); } @Override protected void onDestroy() { super.onDestroy(); if (updateReceiver != null) { unregisterReceiver(updateReceiver); } stopMonitoring(); executor.shutdown(); if (httpClient != null) { httpClient.dispatcher().executorService().shutdown(); httpClient.connectionPool().evictAll(); } stopTimedUpload(); if (!executor.isTerminated()) { executor.shutdownNow(); } // 移除悬浮球 if (floatingActionButton != null) { WindowManager windowManager = (WindowManager) getSystemService(WINDOW_SERVICE); windowManager.removeView(floatingActionButton); } if(isRecording) stopVoiceRecording(); } private String getCurrentUser() { String user = name; // 如果用户没有输入信息,则使用设备型号作为默认值 if (user=="-1") { user = currentUsername; } return user; } private String getProject(){ return projectId.isEmpty()?"项目ID未填写":projectId; } private String getUnit() { return unit.isEmpty()?"机组未填写":unit; } private int getBlade() { return blade; } private void openFullImage(String imagePath) { File file = new File(imagePath); Uri uri = FileProvider.getUriForFile( this, getPackageName() + ".provider", file ); Intent intent = new Intent(Intent.ACTION_VIEW); intent.setDataAndType(uri, "image/*"); intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); startActivity(intent); } private void uploadAudio(String imageId, String audioPath, String token) { // 1. 参数校验(在调用线程执行,避免不必要的后台任务) if (audioPath == null || audioPath.equals("0")) { BackgroundToast.show(MainActivity.this, "文件路径为空"); return; } File audioFile = new File(audioPath); if (!audioFile.exists()) { BackgroundToast.show(MainActivity.this, "文件不存在"); return; } // 2. 在后台线程执行上传逻辑 executor.execute(() -> { // 3. 构建请求体(已在后台线程) RequestBody requestBody = new MultipartBody.Builder() .setType(MultipartBody.FORM) .addFormDataPart("file", audioFile.getName(), RequestBody.create(audioFile, MediaType.parse("audio/*"))) .build(); // 4. 构建请求 Request.Builder requestBuilder = new Request.Builder() .url("http://pms.dtyx.net:9158/audio/upload/" + imageId) .post(requestBody); if (token != null && !token.isEmpty()) { requestBuilder.addHeader("Authorization", token); } // 5. 同步执行请求(try-with-resources 自动关闭 Response) try (Response response = httpClient.newCall(requestBuilder.build()).execute()) { String currentTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()) .format(new Date()); if (response.isSuccessful()) { // 上传成功 db.AudioDao().deleteByPath(audioPath); db2.AudioDao().insert(new AudioEntity(audioPath, imageId, currentTime,true)); BackgroundToast.show(MainActivity.this, "录音上传成功: " + imageId); } else { // 上传失败(HTTP 错误) db.AudioDao().insert(new AudioEntity(audioPath, imageId, currentTime,false)); BackgroundToast.show(MainActivity.this, "录音上传失败: " + response.code() + " " + response.message()); } } catch (IOException e) { // 网络异常 String currentTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()) .format(new Date()); db.AudioDao().insert(new AudioEntity(audioPath, imageId, currentTime,false)); BackgroundToast.show(MainActivity.this, "录音上传失败: " + e.getMessage()); } }); } private long getLastPhotoId() { long id = -1; Cursor cursor = getContentResolver().query( MediaStore.Images.Media.EXTERNAL_CONTENT_URI, new String[]{"MAX(" + MediaStore.Images.Media._ID + ")"}, null, null, null ); if (cursor != null && cursor.moveToFirst()) { id = cursor.getLong(0); // MAX(_ID) 的结果在索引 0 cursor.close(); } return id; } }