AndroidApp/MainActivity.java

3973 lines
161 KiB
Java
Raw Normal View History

2025-07-11 18:34:06 +08:00
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;
2025-07-25 14:29:27 +08:00
import android.content.ClipboardManager;
2025-07-11 18:34:06 +08:00
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;
2025-07-25 14:29:27 +08:00
import android.graphics.drawable.ColorDrawable;
2025-07-11 18:34:06 +08:00
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;
2025-07-25 14:29:27 +08:00
2025-07-11 18:34:06 +08:00
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;
2025-07-25 14:29:27 +08:00
import android.webkit.MimeTypeMap;
2025-07-11 18:34:06 +08:00
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;
2025-07-25 14:29:27 +08:00
import androidx.lifecycle.ViewModelStore;
import androidx.lifecycle.ViewModelStoreOwner;
2025-07-11 18:34:06 +08:00
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;
2025-07-25 14:29:27 +08:00
import com.example.myapplication.Service.PhotoMonitoringService;
2025-07-11 18:34:06 +08:00
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;
2025-07-25 14:29:27 +08:00
import com.example.myapplication.Tool.ReportGenerator;
2025-07-11 18:34:06 +08:00
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;
2025-07-25 14:29:27 +08:00
import com.example.myapplication.model.ChangePasswordRequest;
2025-07-11 18:34:06 +08:00
import com.example.myapplication.model.ImageEntity;
import com.example.myapplication.model.PartResponse;
2025-07-25 14:29:27 +08:00
import com.example.myapplication.model.SharedDataManager;
2025-07-11 18:34:06 +08:00
import com.getbase.floatingactionbutton.FloatingActionButton;
import com.google.android.material.datepicker.MaterialDatePicker;
import com.google.android.material.switchmaterial.SwitchMaterial;
2025-07-25 14:29:27 +08:00
import com.google.android.material.textfield.TextInputEditText;
import org.json.JSONObject;
2025-07-11 18:34:06 +08:00
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;
2025-07-25 14:29:27 +08:00
import java.util.concurrent.atomic.AtomicReference;
2025-07-11 18:34:06 +08:00
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
// 在MainActivity类中添加以下变量
2025-07-25 14:29:27 +08:00
public class MainActivity extends AppCompatActivity implements ViewModelStoreOwner {
private ViewModelStore viewModelStore = new ViewModelStore();
2025-07-11 18:34:06 +08:00
private DynamicDataFetcher fetcher;
private Spinner spinnerImageSource;
private List<Map<String,String>> imageSources = new ArrayList<>();
private PartListFetcher partListFetcher;
private List<PartResponse> cachedPartList;
private AlertDialog reportcurrentDialog; // 添加成员变量来保存当前对话框
2025-07-25 14:29:27 +08:00
private boolean haveInitalFloating=false;
2025-07-11 18:34:06 +08:00
private static String Temperator="0";
private static String Humidity="0";
private static String Weather="0";
2025-07-25 14:29:27 +08:00
private TextInputEditText etWeather, etTemperature, etHumidity;
private Button btnSaveWeather;
private TextView tvWeatherDisplay, tvTemperatureDisplay, tvHumidityDisplay;
@Override
public ViewModelStore getViewModelStore() {
return viewModelStore;
}
2025-07-11 18:34:06 +08:00
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;
2025-07-25 14:29:27 +08:00
private boolean isRecording = false;
2025-07-11 18:34:06 +08:00
private MediaRecorder mediaRecorder;
private String currentAudioPath;
2025-07-25 14:29:27 +08:00
private String partid;
2025-07-11 18:34:06 +08:00
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;
2025-07-25 14:29:27 +08:00
2025-07-11 18:34:06 +08:00
private long lastPhotoId = -1;
private boolean isMonitoring = false;
private SwitchMaterial switchMonitor;
2025-07-25 14:29:27 +08:00
2025-07-11 18:34:06 +08:00
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;
2025-07-25 14:29:27 +08:00
private final ExecutorService executor = Executors.newFixedThreadPool(4);
2025-07-11 18:34:06 +08:00
// 添加位置相关变量
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();
2025-07-25 14:29:27 +08:00
2025-07-11 18:34:06 +08:00
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<Intent> videoPickerLauncher;
private InputMethodManager inputMethodManager;
private ActivityResultLauncher<String[]> requestPermissionLauncher;
private TextView tvUnit, tvBlade,tvUnitName;
private static final String ACTION_UPDATE_VALUES = "com.example.myapplication.UPDATE_VALUES";
private BroadcastReceiver updateReceiver;
private String currentUsername;
2025-07-25 14:29:27 +08:00
private String projectName ="-1";
private String projectId="-1";
private String unit = "-1";
private String unitName = "-1";
private String imgpath ="-1";
2025-07-11 18:34:06 +08:00
private String password="-1";
2025-07-25 14:29:27 +08:00
private int count1=0;
private int count0=0;
private final int PERMISSION_REQUEST_StartMonitor_CODE=99;
2025-07-11 18:34:06 +08:00
private String name="-1";
private String token="-1";
2025-07-25 14:29:27 +08:00
private int blade =-1;
2025-07-11 18:34:06 +08:00
private Button changepassword;
private Map<Pair<String,String>, String> nameToIdMap;
private String ChooseImageSource ="-1";
private ActivityResultLauncher<Intent> overlayPermissionLauncher;
2025-07-25 14:29:27 +08:00
private SharedDataManager dataManager;
private String UserId="-1";
private Button btnReadReport;
private Button btnGenerateReport;
private ReportGenerator reportGeneratorreal;
2025-07-11 18:34:06 +08:00
@SuppressLint("UnspecifiedRegisterReceiverFlag")
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
2025-07-25 14:29:27 +08:00
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();
2025-07-11 18:34:06 +08:00
tvStatus = findViewById(R.id.tv_status);
reportGenerator = new ReportGeneratorHelper(this);
2025-07-25 14:29:27 +08:00
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());
2025-07-11 18:34:06 +08:00
initializeViews();
initializeDatabase();
initializeReceivers();
initializePermissionLaunchers();
initialProject();
requestInitialPermissions();
setupButtonListeners();
setupTimedUpload();
2025-07-25 14:29:27 +08:00
if(!haveInitalFloating)
initFloatingActionButton();
2025-07-11 18:34:06 +08:00
SharedPreferences sharedPreferences = getSharedPreferences("LoginPrefs", Context.MODE_PRIVATE);
token=sharedPreferences.getString("auth_token", "-1");
2025-07-25 14:29:27 +08:00
dataManager.setToken(token);
dataManager.setProjectId(projectId);
name=sharedPreferences.getString("name","-1");
UserId=sharedPreferences.getString("userid","-1");
dataManager.setUser(getCurrentUser());
2025-07-11 18:34:06 +08:00
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) {
Pair<String,String>key=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<String, String> selectedItem = imageSources.get(position);
String value = selectedItem.get("value");
String key = selectedItem.get("key");
ChooseImageSource=key;
2025-07-25 14:29:27 +08:00
dataManager.setChooseImageSource(ChooseImageSource);
2025-07-11 18:34:06 +08:00
Toast.makeText(MainActivity.this,
"选择了: " + value + " (Key: " + key + ")",
Toast.LENGTH_SHORT).show();
2025-07-25 14:29:27 +08:00
2025-07-11 18:34:06 +08:00
}
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
// 未选择任何项
}
});
2025-07-25 14:29:27 +08:00
}
// 在Activity中
private void showFileListDialog(File[] files) {
// 构建文件名列表和文件对象列表
List<String> fileNames = new ArrayList<>();
final List<File> 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<File> 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<File> 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<File> 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();
}
2025-07-11 18:34:06 +08:00
}
2025-07-25 14:29:27 +08:00
// 根据文件扩展名获取 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);
}
}
}
2025-07-11 18:34:06 +08:00
private void loadImageSources() {
fetcher.fetchDynamicDataAsync(new DynamicDataFetcher.DynamicDataCallback() {
@Override
public void onSuccess(List<Map<String, String>> sources) {
runOnUiThread(() -> {
// 过滤掉null值
imageSources = new ArrayList<>();
for (Map<String, String> item : sources) {
if (item != null && !item.isEmpty()) {
// 获取第一个也是唯一的entry因为每个map只有一个键值对
Map.Entry<String, String> entry = item.entrySet().iterator().next();
String key = entry.getKey();
String value = entry.getValue();
if (value != null) {
// 创建一个新的map统一结构为{"key":key, "value":value}
Map<String, String> 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<Map<String, String>> sources) {
// 提取显示文本并确保不为null
List<String> displayValues = new ArrayList<>();
for (Map<String, String> item : sources) {
if (item != null && item.get("value") != null) {
displayValues.add(item.get("value"));
}
}
// 确保列表不为空
if (displayValues.isEmpty()) {
displayValues.add("没有可用的选项");
}
// 创建适配器
ArrayAdapter<String> 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) {
Pair<String,String>key=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);
// 创建修改密码请求体
2025-07-25 14:29:27 +08:00
ChangePasswordRequest request = new ChangePasswordRequest(
2025-07-11 18:34:06 +08:00
username,
encryptedNewPassword,
encryptedOldPassword
);
// 初始化Retrofit
Retrofit retrofit = RetrofitClient.getClient(token);
// 创建API服务
AuthApi authApi = retrofit.create(AuthApi.class);
// 执行修改密码请求
retrofit2.Call<ApiResponse<Void>> call = authApi.modifyPassword(request);
retrofit2.Response<ApiResponse<Void>> 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<ImageEntity> 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<String, List<ImageEntity>> 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<String> unitSet = new HashSet<>();
for (ImageEntity image : imageDataList) {
if (image.unitName != null) {
unitSet.add(image.unitName);
}
}
List<String> unitList = new ArrayList<>(unitSet);
int n = unitList.size();
for (Map.Entry<String, List<ImageEntity>> dayEntry : imagesByDay.entrySet()) {
String day = dayEntry.getKey();
List<ImageEntity> dayImages = dayEntry.getValue();
for (int i = 0; i < n; i++) {
String unitName = unitList.get(i);
List<ImageEntity> unitImages = new ArrayList<>();
for (ImageEntity image : dayImages) {
if (unitName.equals(image.unitName)) {
unitImages.add(image);
}
}
if (unitImages.isEmpty()) {
continue;
}
List<ReportGeneratorHelper.ImageData> 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<File> 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<File> 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<File> 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<File> adapter = new ArrayAdapter<File>(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<File> 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<File> 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<File> 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<File> filesToShare) {
if (filesToShare.size() == 1) {
// 单个文件直接分享
shareGeneratedFile(filesToShare.get(0));
} else {
// 多个文件需要先压缩或使用其他方式分享
2025-07-25 14:29:27 +08:00
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<File> files) {
try {
ArrayList<Uri> 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();
2025-07-11 18:34:06 +08:00
}
}
2025-07-25 14:29:27 +08:00
2025-07-11 18:34:06 +08:00
// 删除全部报告的方法
private void deleteAllReports(List<File> 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);
2025-07-25 14:29:27 +08:00
Button readoothermsg=findViewById(R.id.readothermsg);
2025-07-11 18:34:06 +08:00
btnread.setOnClickListener(v->{
showReportsList("output",".docx");
});
2025-07-25 14:29:27 +08:00
readoothermsg.setOnClickListener(v->
{
showErrorDialog("othermsg:",dataManager.getOthermsg());
});
2025-07-11 18:34:06 +08:00
btnConvert.setOnClickListener(v -> {
if(PermissionUtils.checkAndRequestPermissions(this))
{
// 显示处理中状态
updateStatus("正在处理...");
showDateRangePicker();}
});
}
private void showDateRangePicker() {
// 创建日期选择对话框
MaterialDatePicker<Pair<Long, Long>> 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<ImageEntity> listFromDb = db.imageDao().getImagesByTimeRange(adjustedStart, adjustedEnd);
List<ImageEntity> listFromDb2 = db2.imageDao().getImagesByTimeRange(adjustedStart, adjustedEnd);
// 合并结果
List<ImageEntity> 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)
2025-07-25 14:29:27 +08:00
.setNegativeButton("复制", (dialog, which) -> {
ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clip = ClipData.newPlainText("复制信息", message);
2025-07-11 18:34:06 +08:00
clipboard.setPrimaryClip(clip);
2025-07-25 14:29:27 +08:00
// Optional: Show a toast to confirm the copy
Toast.makeText(this, "信息已复制", Toast.LENGTH_SHORT).show();
2025-07-11 18:34:06 +08:00
})
.show();
});
}
2025-07-25 14:29:27 +08:00
2025-07-11 18:34:06 +08:00
// 获取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());
}
2025-07-25 14:29:27 +08:00
private String getMimeType(String url) {
// 初始化类型为null
String type = null;
2025-07-11 18:34:06 +08:00
2025-07-25 14:29:27 +08:00
// 从URL/文件路径中获取文件扩展名
String extension = MimeTypeMap.getFileExtensionFromUrl(url);
2025-07-11 18:34:06 +08:00
2025-07-25 14:29:27 +08:00
// 如果扩展名不为空
if (extension != null) {
// 根据扩展名获取对应的MIME类型
type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension.toLowerCase());
}
2025-07-11 18:34:06 +08:00
2025-07-25 14:29:27 +08:00
// 如果找到了MIME类型则返回否则返回通用类型"*/*"
return type != null ? type : "*/*";
2025-07-11 18:34:06 +08:00
}
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");
2025-07-25 14:29:27 +08:00
dataManager.setUnit(unit);
dataManager.setBlade(blade);
String partname=getPartName(blade);
partid=getPartIdByNameFast(unit,partname);
dataManager.setPartid(partid);
dataManager.setUnitName(unitName);
2025-07-11 18:34:06 +08:00
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 -> {
2025-07-25 14:29:27 +08:00
if (FloatingWindowService.isFloatingWindowShowing) {
// 如果悬浮窗正在显示,则关闭它
FloatingWindowService.closeFloatingWindow(MainActivity.this);
} else {
// 如果悬浮窗没有显示,则检查权限并启动
if (checkOverlayPermission2()) {
startFloatingService();
} else {
BackgroundToast.show(MainActivity.this, "需要悬浮窗权限");
}
2025-07-11 18:34:06 +08:00
}
});
btnAdmin.setOnClickListener(v -> {
startActivity(new Intent(MainActivity.this, AdminActivity.class));
});
}
private void showFailedAudios() {
new Thread(() -> {
List<AudioEntity> 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<String> 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(() -> {
2025-07-25 14:29:27 +08:00
List<AudioEntity> successAudios = db2.AudioDao().getAllAudios();
2025-07-11 18:34:06 +08:00
runOnUiThread(() -> {
2025-07-25 14:29:27 +08:00
// 使用自定义主题的 AlertDialog
AlertDialog.Builder builder = new AlertDialog.Builder(this, R.style.CustomAlertDialog);
2025-07-11 18:34:06 +08:00
builder.setTitle("上传成功的录音");
2025-07-25 14:29:27 +08:00
// 设置自定义标题样式
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);
2025-07-11 18:34:06 +08:00
if (successAudios == null || successAudios.isEmpty()) {
builder.setMessage("没有上传成功的录音");
builder.setPositiveButton("确定", null);
builder.show();
return;
}
2025-07-25 14:29:27 +08:00
// 创建自定义适配器
ArrayAdapter<String> adapter = new ArrayAdapter<String>(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;
}
};
// 添加数据到适配器
2025-07-11 18:34:06 +08:00
for (AudioEntity audio : successAudios) {
2025-07-25 14:29:27 +08:00
adapter.add(String.format("录音名称: %s\n时间: %s",
getFileNameFromPath(audio.getAudioPath()),
2025-07-11 18:34:06 +08:00
audio.getTime()));
}
2025-07-25 14:29:27 +08:00
builder.setAdapter(adapter, (dialog, which) -> {
// 点击item打开录音文件
AudioEntity selectedAudio = successAudios.get(which);
openAudioFile(selectedAudio.getAudioPath());
});
2025-07-11 18:34:06 +08:00
// 添加删除按钮
builder.setNegativeButton("一键删除", (dialog, which) -> deleteAllSuccessAudios(successAudios));
2025-07-25 14:29:27 +08:00
// 添加关闭按钮
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));
2025-07-11 18:34:06 +08:00
});
}).start();
}
2025-07-25 14:29:27 +08:00
// 从路径中获取文件名
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);
}
}
2025-07-11 18:34:06 +08:00
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<AudioEntity> audios) {
new Thread(() -> {
for (AudioEntity audio : audios) {
db2.AudioDao().delete(audio);
}
runOnUiThread(() -> {
Toast.makeText(this, "已删除所有成功录音", Toast.LENGTH_SHORT).show();
});
}).start();
}
private void deleteAllFailedAudios(List<AudioEntity> audios) {
new Thread(() -> {
for (AudioEntity audio : audios) {
db.AudioDao().delete(audio);
}
runOnUiThread(() -> {
Toast.makeText(this, "已删除所有失败录音", Toast.LENGTH_SHORT).show();
});
}).start();
}
private void retryAllFailedAudios(List<AudioEntity> 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();
}
2025-07-25 14:29:27 +08:00
2025-07-11 18:34:06 +08:00
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<Uri> 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<Uri> 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<Uri> 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<VideoInfo> 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<VideoInfo> 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()) {
2025-07-25 14:29:27 +08:00
startVoiceRecording();
2025-07-11 18:34:06 +08:00
} 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()) {
2025-07-25 14:29:27 +08:00
2025-07-11 18:34:06 +08:00
} 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);
}
2025-07-25 14:29:27 +08:00
private void startVoiceRecording() {
2025-07-11 18:34:06 +08:00
Intent serviceIntent = new Intent(this, RecordingService.class);
2025-07-25 14:29:27 +08:00
serviceIntent.setAction("start_recording");
2025-07-11 18:34:06 +08:00
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForegroundService(serviceIntent);
} else {
startService(serviceIntent);
}
2025-07-25 14:29:27 +08:00
isRecording = true;
dataManager.setIsRecording(isRecording);
floatingActionButton.setImageResource(R.drawable.ic_mic_on);
2025-07-11 18:34:06 +08:00
}
private void stopVoiceRecording() {
2025-07-25 14:29:27 +08:00
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);
2025-07-11 18:34:06 +08:00
}
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;
2025-07-25 14:29:27 +08:00
String finalPartid1 = partid;
2025-07-11 18:34:06 +08:00
Executors.newSingleThreadExecutor().execute(() -> {
if (isNetworkAvailable()) {
2025-07-25 14:29:27 +08:00
uploadPhotoSync(path, time, lastKnownLocation, user, "0",finalproject,finalunit,blade, finalPartid,ChooseImageSource,unitName);
2025-07-11 18:34:06 +08:00
} else {
db2.imageDao().insert(new ImageEntity(path, time,
finalLatitude, finalLongitude,
2025-07-25 14:29:27 +08:00
finalAltitude, user, "0",finalproject,finalunit,blade,false,unitName,Temperator,Humidity,Weather,ChooseImageSource, finalPartid1));
2025-07-11 18:34:06 +08:00
}
});
}
}
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("定时上传已停止");
}
2025-07-25 14:29:27 +08:00
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) {
2025-07-11 18:34:06 +08:00
File file = new File(filePath);
2025-07-25 14:29:27 +08:00
if (!file.exists()) {
BackgroundToast.show(MainActivity.this, "文件不存在: " + filePath);
return;
}
2025-07-11 18:34:06 +08:00
double latitude = location != null ? location.getLatitude() : 0;
double longitude = location != null ? location.getLongitude() : 0;
double altitude = location != null ? location.getAltitude() : 0;
2025-07-25 14:29:27 +08:00
AudioEntity a=db.AudioDao().getAudioByPath(audioPath);
try {
2025-07-11 18:34:06 +08:00
2025-07-25 14:29:27 +08:00
// 构建请求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();
2025-07-11 18:34:06 +08:00
2025-07-25 14:29:27 +08:00
Request request = new Request.Builder()
.url(urlBuilder.build())
.post(requestBody)
.addHeader("Authorization", token)
.addHeader("Content-Type", "application/x-www-form-urlencoded")
.build();
2025-07-11 18:34:06 +08:00
2025-07-25 14:29:27 +08:00
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);
}
2025-07-11 18:34:06 +08:00
2025-07-25 14:29:27 +08:00
BackgroundToast.show(
MainActivity.this,
"上传成功: " + response.code() + " " + filePath
);
2025-07-11 18:34:06 +08:00
2025-07-25 14:29:27 +08:00
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));
});
}
}
2025-07-11 18:34:06 +08:00
}
2025-07-25 14:29:27 +08:00
} catch (Exception e) {
2025-07-11 18:34:06 +08:00
if (isUploading) { // 只有主动上传时才重新加入队列
2025-07-25 14:29:27 +08:00
executor.execute(() -> {
db2.imageDao().insert(new ImageEntity(filePath, time,
latitude, longitude, altitude, user, audioPath,
project, unit, blade, false, unitName,
Temperator, Humidity, Weather, imageSource, partId));
});
2025-07-11 18:34:06 +08:00
}
BackgroundToast.show(MainActivity.this,
"上传失败: " + e.getMessage()
);
}
}
private void stopUploadQueue() {
isUploading = false;
2025-07-25 14:29:27 +08:00
dataManager.setUploading(isUploading);
2025-07-11 18:34:06 +08:00
// 中断当前上传
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);
2025-07-25 14:29:27 +08:00
dataManager.setLocation(lastLocation);
2025-07-11 18:34:06 +08:00
}
} 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
2025-07-25 14:29:27 +08:00
dataManager.setLocation(lastKnownLocation);
2025-07-11 18:34:06 +08:00
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);
2025-07-25 14:29:27 +08:00
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) {
2025-07-11 18:34:06 +08:00
// 检查所有权限是否被授予
boolean allPermissionsGranted = true;
for (int result : grantResults) {
if (result != PackageManager.PERMISSION_GRANTED) {
allPermissionsGranted = false;
2025-07-25 14:29:27 +08:00
lastPhotoId=getLastPhotoId();
Log.e("最后一张照片值:",String.valueOf(lastPhotoId));
2025-07-11 18:34:06 +08:00
break;
}
}
if (allPermissionsGranted) {
// 所有权限都已授予,继续初始化位置管理器
initLocationManager();
2025-07-25 14:29:27 +08:00
haveInitalFloating=true;
initFloatingActionButton();
2025-07-11 18:34:06 +08:00
} 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();
}
}
2025-07-25 14:29:27 +08:00
//通知权限
2025-07-11 18:34:06 +08:00
else if (requestCode == NOTIFICATION_PERMISSION_REQUEST_CODE) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
2025-07-25 14:29:27 +08:00
BackgroundToast.show(this,"通知权限已开启");
2025-07-11 18:34:06 +08:00
} 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();
}
}
2025-07-25 14:29:27 +08:00
//图片权限
2025-07-11 18:34:06 +08:00
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();
2025-07-25 14:29:27 +08:00
2025-07-11 18:34:06 +08:00
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)) {
2025-07-25 14:29:27 +08:00
try{startMonitoring();}
catch (Exception e)
{
dataManager.setOthermsg(e.toString());
BackgroundToast.show(this,"监听失败");
}
2025-07-11 18:34:06 +08:00
} 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<ImageEntity> 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<ImageEntity> 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<ImageEntity> 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<ImageEntity> 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<ImageEntity> history) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("批量删除");
// 获取所有复选框的选中状态
List<Integer> 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<Integer> 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<Integer> 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"+
2025-07-25 14:29:27 +08:00
"叶片号:%d\n"+
"图片来源:%s\n",
2025-07-11 18:34:06 +08:00
sdf.format(new Date(item.time)), // 时间
item.path, // 路径
item.latitude, // 纬度
item.longitude, // 经度
item.altitude, // 高度(单位:米)
item.user,// 用户信息
projectName,
item.unitName,
2025-07-25 14:29:27 +08:00
item.blade,
item.imageSource
2025-07-11 18:34:06 +08:00
));
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() {
2025-07-25 14:29:27 +08:00
Intent serviceIntent = new Intent(this, PhotoMonitoringService.class);
stopService(serviceIntent);
2025-07-11 18:34:06 +08:00
statusText.setText("当前状态:未监听");
2025-07-25 14:29:27 +08:00
switchMonitor.setChecked(false);
2025-07-11 18:34:06 +08:00
}
private void startUploadQueue() {
if (isUploading) {
Toast.makeText(this, "上传正在进行中", Toast.LENGTH_SHORT).show();
return;
}
executor.execute(() -> {
isUploading = true;
2025-07-25 14:29:27 +08:00
dataManager.setUploading(isUploading);
2025-07-11 18:34:06 +08:00
runOnUiThread(() -> {
btnUploadQueue.setEnabled(false);
btnStopUpload.setEnabled(true);
statusText.setText("正在上传缓存队列..." + (isTimedUploadRunning ? "(定时上传)" : ""));
});
try {
while (isUploading) {
List<ImageEntity> queue = db2.imageDao().getAll();
if (queue.isEmpty()) {
break;
}
for (ImageEntity image : queue) {
if (!isUploading) break;
String partname=getPartName(image.blade);
2025-07-25 14:29:27 +08:00
2025-07-11 18:34:06 +08:00
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;
2025-07-25 14:29:27 +08:00
dataManager.setUploading(isUploading);
2025-07-11 18:34:06 +08:00
}
});
}
@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()) {
// 上传成功
2025-07-25 14:29:27 +08:00
db.AudioDao().deleteByPath(audioPath);
db2.AudioDao().insert(new AudioEntity(audioPath, imageId, currentTime,true));
2025-07-11 18:34:06 +08:00
BackgroundToast.show(MainActivity.this, "录音上传成功: " + imageId);
} else {
// 上传失败HTTP 错误)
2025-07-25 14:29:27 +08:00
db.AudioDao().insert(new AudioEntity(audioPath, imageId, currentTime,false));
2025-07-11 18:34:06 +08:00
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());
2025-07-25 14:29:27 +08:00
db.AudioDao().insert(new AudioEntity(audioPath, imageId, currentTime,false));
2025-07-11 18:34:06 +08:00
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;
}
}