AndroidApp/MainActivity.java

3973 lines
161 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package com.example.myapplication;
import android.annotation.SuppressLint;
import android.app.DatePickerDialog;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.TimePickerDialog;
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.ContentResolver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.res.ColorStateList;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.graphics.PixelFormat;
import android.graphics.drawable.ColorDrawable;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.media.ExifInterface;
import android.media.MediaRecorder;
import android.net.ConnectivityManager;
import android.net.NetworkCapabilities;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import androidx.core.util.Pair;
import android.os.Looper;
import android.provider.MediaStore;
import android.provider.Settings;
import android.util.Base64;
import android.util.Log;
import android.util.SparseBooleanArray;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
import android.webkit.MimeTypeMap;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.DatePicker;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.ScrollView;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.TimePicker;
import android.widget.Toast;
import android.Manifest;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.core.content.FileProvider;
import androidx.lifecycle.ViewModelStore;
import androidx.lifecycle.ViewModelStoreOwner;
import androidx.room.Room;
import com.chaquo.python.PyException;
import com.chaquo.python.PyObject;
import com.chaquo.python.Python;
import com.example.myapplication.DataBase.AppDatabase;
import com.example.myapplication.DataBase.DatabaseHelper;
import com.example.myapplication.Service.FloatingWindowService;
import com.example.myapplication.Service.PhotoMonitoringService;
import com.example.myapplication.Service.RecordingService;
import com.example.myapplication.Tool.BackgroundToast;
import com.example.myapplication.Tool.DynamicDataFetcher;
import com.example.myapplication.Tool.PartListFetcher;
import com.example.myapplication.Tool.PermissionUtils;
import com.example.myapplication.Tool.ReportGenerator;
import com.example.myapplication.Tool.ReportGeneratorHelper;
import com.example.myapplication.Tool.RetrofitClient;
import com.example.myapplication.api.AuthApi;
import com.example.myapplication.model.ApiResponse;
import com.example.myapplication.model.AudioEntity;
import com.example.myapplication.model.ChangePasswordRequest;
import com.example.myapplication.model.ImageEntity;
import com.example.myapplication.model.PartResponse;
import com.example.myapplication.model.SharedDataManager;
import com.getbase.floatingactionbutton.FloatingActionButton;
import com.google.android.material.datepicker.MaterialDatePicker;
import com.google.android.material.switchmaterial.SwitchMaterial;
import com.google.android.material.textfield.TextInputEditText;
import org.json.JSONObject;
import okhttp3.*;
import retrofit2.Retrofit;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
// 在MainActivity类中添加以下变量
public class MainActivity extends AppCompatActivity implements ViewModelStoreOwner {
private ViewModelStore viewModelStore = new ViewModelStore();
private DynamicDataFetcher fetcher;
private Spinner spinnerImageSource;
private List<Map<String,String>> imageSources = new ArrayList<>();
private PartListFetcher partListFetcher;
private List<PartResponse> cachedPartList;
private AlertDialog reportcurrentDialog; // 添加成员变量来保存当前对话框
private boolean haveInitalFloating=false;
private static String Temperator="0";
private static String Humidity="0";
private static String Weather="0";
private TextInputEditText etWeather, etTemperature, etHumidity;
private Button btnSaveWeather;
private TextView tvWeatherDisplay, tvTemperatureDisplay, tvHumidityDisplay;
@Override
public ViewModelStore getViewModelStore() {
return viewModelStore;
}
private static final int NOTIFICATION_PERMISSION_REQUEST_CODE = 102;
private DatabaseHelper dbHelper;
private long startTimestamp = 0;
private long endTimestamp = 0;
private String startDateStr = "";
private String endDateStr = "";
private TextView tvUser;
private TextView tvProject;
private TextView tvProjectId;
private Button btnAdmin;
// 在类变量声明部分添加
private Button btnUploadVideos;
private Button btnUploadVideos2;
private FloatingActionButton floatingActionButton;
private boolean isRecording = false;
private MediaRecorder mediaRecorder;
private String currentAudioPath;
private String partid;
private final OkHttpClient httpClient = new OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS) // 连接超时
.readTimeout(60, TimeUnit.SECONDS) // 读取超时
.writeTimeout(60, TimeUnit.SECONDS) // 写入超时
.build();
private static final String TAG = "MainActivity";
private static String outputPath = "report" + ".json";
private TextView tvStatus;
private final Handler mainHandler = new Handler(Looper.getMainLooper());
private ReportGeneratorHelper reportGenerator;
private Button selectTimeButton;
private TextView startTimeText;
private TextView endTimeText;
private long startTime = 0;
private long endTime = 0;
private TextView statusText;
private long lastPhotoId = -1;
private boolean isMonitoring = false;
private SwitchMaterial switchMonitor;
private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.getDefault());
private AppDatabase db;
private static final int PERMISSION_REQUEST_CODE = 100;
private static final int PERMISSIONS_REQUEST_CODE = 109;
private AppDatabase db2;
private Button btnHistory;
private Button btnFailedAudios;
private Button btnFailedAudios2;
private Button btnHistory2;
private AlertDialog currentDialog;
private AlertDialog currentDialog2;
private static final int THUMBNAIL_SIZE = 200;
private static final int DECODE_SAMPLE_SIZE = 4;
private final ExecutorService executor = Executors.newFixedThreadPool(4);
// 添加位置相关变量
private LocationManager locationManager;
private LocationListener locationListener;
private Location lastKnownLocation;
private static final int LOCATION_PERMISSION_REQUEST_CODE = 1002;
private TextView locationText;
private static final int AUDIO_PERMISSION_REQUEST_CODE = 1030;
private Button btnUploadQueue;
private Button btnStopUpload;
private volatile boolean isUploading = false;
private final Object uploadLock = new Object();
private EditText etUploadInterval;
private Button btnSetInterval;
private Button btnStartTimedUpload;
private Button btnStopTimedUpload;
private Handler timedUploadHandler;
private Runnable timedUploadRunnable;
private static final int OVERLAY_PERMISSION_REQUEST_CODE = 1003;
private int uploadIntervalMinutes = 0; // 默认上传间隔(分钟)
private boolean isTimedUploadRunning = false;
private static final int REQUEST_VIDEO_PERMISSION = 1005;
// 在类变量声明部分添加
private ActivityResultLauncher<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;
private String projectName ="-1";
private String projectId="-1";
private String unit = "-1";
private String unitName = "-1";
private String imgpath ="-1";
private String password="-1";
private int count1=0;
private int count0=0;
private final int PERMISSION_REQUEST_StartMonitor_CODE=99;
private String name="-1";
private String token="-1";
private int blade =-1;
private Button changepassword;
private Map<Pair<String,String>, String> nameToIdMap;
private String ChooseImageSource ="-1";
private ActivityResultLauncher<Intent> overlayPermissionLauncher;
private SharedDataManager dataManager;
private String UserId="-1";
private Button btnReadReport;
private Button btnGenerateReport;
private ReportGenerator reportGeneratorreal;
@SuppressLint("UnspecifiedRegisterReceiverFlag")
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
reportGeneratorreal = new ReportGenerator(this);
btnGenerateReport = findViewById(R.id.btn_generate_report);
btnReadReport = findViewById(R.id.btn_read_report);
btnGenerateReport.setOnClickListener(v -> {
String outputpath= reportGeneratorreal.generateReport();
if (outputpath != null && !outputpath.startsWith("Error")) {
// 获取文件夹中的所有文件
File folder = new File(outputpath);
if (folder.exists() && folder.isDirectory()) {
File[] files = folder.listFiles();
if (files != null && files.length > 0) {
// 显示文件列表对话框
showFileListDialog(files);
} else {
Toast.makeText(this, "文件夹为空,没有可显示的文件", Toast.LENGTH_SHORT).show();
}
} else {
Toast.makeText(this, "路径无效或不是文件夹", Toast.LENGTH_SHORT).show();
}
} else {
// 显示错误信息
Toast.makeText(this, "报告生成失败: " + outputpath, Toast.LENGTH_LONG).show();
}
// 检查路径是否有效
// 检查路径是否有效
});
String outputpath2 = getExternalFilesDir(null).getAbsolutePath();
btnReadReport.setOnClickListener( v->{if (outputpath2 != null && !outputpath2.startsWith("Error")) {
// 获取文件夹中的所有文件
File folder = new File(outputpath2);
if (folder.exists() && folder.isDirectory()) {
File[] files = folder.listFiles();
if (files != null && files.length > 0) {
// 显示文件列表对话框
showFileListDialog(files);
} else {
Toast.makeText(this, "文件夹为空,没有可显示的文件", Toast.LENGTH_SHORT).show();
}
} else {
Toast.makeText(this, "路径无效或不是文件夹", Toast.LENGTH_SHORT).show();
}
} else {
// 显示错误信息
Toast.makeText(this, "报告生成失败: " + outputpath2, Toast.LENGTH_LONG).show();
}} );
dataManager= SharedDataManager.getInstance();
tvStatus = findViewById(R.id.tv_status);
reportGenerator = new ReportGeneratorHelper(this);
etWeather = findViewById(R.id.et_weather);
etTemperature = findViewById(R.id.et_temperature);
etHumidity = findViewById(R.id.et_humidity);
btnSaveWeather = findViewById(R.id.btn_save_weather);
// 初始化天气信息显示视图
tvWeatherDisplay = findViewById(R.id.tv_weather_display);
tvTemperatureDisplay = findViewById(R.id.tv_temperature_display);
tvHumidityDisplay = findViewById(R.id.tv_humidity_display);
// 从 SharedPreferences 加载保存的天气信息
SharedPreferences prefs = getSharedPreferences("WeatherPrefs", MODE_PRIVATE);
Weather = prefs.getString("weather", "0");
Temperator = prefs.getString("temperature", "0");
Humidity = prefs.getString("humidity", "0");
// 设置输入框的默认值
etWeather.setText(Weather);
etTemperature.setText(Temperator);
etHumidity.setText(Humidity);
// 更新显示
updateWeatherDisplay();
// 设置保存按钮点击事件
btnSaveWeather.setOnClickListener(v -> saveWeatherInfo());
initializeViews();
initializeDatabase();
initializeReceivers();
initializePermissionLaunchers();
initialProject();
requestInitialPermissions();
setupButtonListeners();
setupTimedUpload();
if(!haveInitalFloating)
initFloatingActionButton();
SharedPreferences sharedPreferences = getSharedPreferences("LoginPrefs", Context.MODE_PRIVATE);
token=sharedPreferences.getString("auth_token", "-1");
dataManager.setToken(token);
dataManager.setProjectId(projectId);
name=sharedPreferences.getString("name","-1");
UserId=sharedPreferences.getString("userid","-1");
dataManager.setUser(getCurrentUser());
initApp();
changepassword=findViewById(R.id.changePassword);
changepassword.setOnClickListener(v -> {
showChangePasswordDialog(currentUsername);
});
partListFetcher=new PartListFetcher(this,token,projectId);
new Thread(() -> {
try {
// 在子线程执行网络请求
cachedPartList = partListFetcher.fetchPartListSync();
// 初始化 nameToIdMap
nameToIdMap = new HashMap<>();
for (PartResponse part : cachedPartList) {
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;
dataManager.setChooseImageSource(ChooseImageSource);
Toast.makeText(MainActivity.this,
"选择了: " + value + " (Key: " + key + ")",
Toast.LENGTH_SHORT).show();
}
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
// 未选择任何项
}
});
}
// 在Activity中
private void showFileListDialog(File[] files) {
// 构建文件名列表和文件对象列表
List<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();
}
}
// 根据文件扩展名获取 MIME 类型
private void saveWeatherInfo() {
Weather = etWeather.getText().toString().trim();
Temperator = etTemperature.getText().toString().trim();
Humidity = etHumidity.getText().toString().trim();
// 简单验证(可选)
if (Weather.isEmpty()) {
Toast.makeText(this, "请输入天气", Toast.LENGTH_SHORT).show();
return;
}
// 保存到 SharedPreferences
SharedPreferences prefs = getSharedPreferences("WeatherPrefs", MODE_PRIVATE);
SharedPreferences.Editor editor = prefs.edit();
editor.putString("weather", Weather);
editor.putString("temperature", Temperator);
editor.putString("humidity", Humidity);
editor.apply();
// 更新显示
updateWeatherDisplay();
// 提示保存成功
Toast.makeText(this, "天气信息已保存", Toast.LENGTH_SHORT).show();
}
private void updateWeatherDisplay() {
tvWeatherDisplay.setText("天气: " + (Weather.isEmpty() ? "未设置" : Weather));
tvTemperatureDisplay.setText("温度: " + (Temperator.isEmpty() ? "未设置" : Temperator + ""));
tvHumidityDisplay.setText("湿度: " + (Humidity.isEmpty() ? "未设置" : Humidity + "%"));
}
private void startMonitoring()
{
String[] requiredPermissions = {
Manifest.permission.CAMERA,
Manifest.permission.FOREGROUND_SERVICE_CAMERA,
Manifest.permission.READ_EXTERNAL_STORAGE
};
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED ||
ContextCompat.checkSelfPermission(this, Manifest.permission.FOREGROUND_SERVICE_CAMERA) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, requiredPermissions, PERMISSION_REQUEST_StartMonitor_CODE);
}
else {
Intent serviceIntent = new Intent(this, PhotoMonitoringService.class);
serviceIntent.putExtra("command", "start_monitoring");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForegroundService(serviceIntent);
statusText.setText("当前状态:正在监听相册变化");
switchMonitor.setChecked(true);
} else {
startService(serviceIntent);
statusText.setText("当前状态:正在监听相册变化");
switchMonitor.setChecked(true);
}
}
}
private void loadImageSources() {
fetcher.fetchDynamicDataAsync(new DynamicDataFetcher.DynamicDataCallback() {
@Override
public void onSuccess(List<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);
// 创建修改密码请求体
ChangePasswordRequest request = new ChangePasswordRequest(
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 {
// 多个文件需要先压缩或使用其他方式分享
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();
}
}
// 删除全部报告的方法
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);
Button readoothermsg=findViewById(R.id.readothermsg);
btnread.setOnClickListener(v->{
showReportsList("output",".docx");
});
readoothermsg.setOnClickListener(v->
{
showErrorDialog("othermsg:",dataManager.getOthermsg());
});
btnConvert.setOnClickListener(v -> {
if(PermissionUtils.checkAndRequestPermissions(this))
{
// 显示处理中状态
updateStatus("正在处理...");
showDateRangePicker();}
});
}
private void showDateRangePicker() {
// 创建日期选择对话框
MaterialDatePicker<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)
.setNegativeButton("复制", (dialog, which) -> {
ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clip = ClipData.newPlainText("复制信息", message);
clipboard.setPrimaryClip(clip);
// Optional: Show a toast to confirm the copy
Toast.makeText(this, "信息已复制", Toast.LENGTH_SHORT).show();
})
.show();
});
}
// 获取Python错误详情优化版
private String getPythonErrorDetails(PyException e) {
try {
Python py = Python.getInstance();
PyObject traceback = py.getModule("traceback");
PyObject formatExc = traceback.callAttr("format_exc");
return "Python脚本错误:\n\n" +
formatExc.toString() +
"\n\n原始异常: " + e.getMessage();
} catch (Exception tracebackError) {
return "无法解析Python错误详情: " + e.getMessage() +
"\n(解析traceback时出错: " + tracebackError.getMessage() + ")";
}
}
private String readJsonFromAssets(Context context, String filename) {
try {
InputStream is = context.getAssets().open(filename);
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
StringBuilder sb = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
reader.close();
return sb.toString();
} catch (IOException e) {
showToast("读取JSON文件错误: " + e.getMessage());
return null;
}
}
private void openGeneratedFile(File file) {
if (file == null || !file.exists()) {
showToast("文件不存在或创建失败");
return;
}
Intent intent = new Intent(Intent.ACTION_VIEW);
Uri fileUri = FileProvider.getUriForFile(
this,
getPackageName() + ".provider", // 必须与 AndroidManifest.xml 中的 authorities 一致
file);
// 根据文件扩展名动态设置 MIME 类型
String mimeType = getMimeType(file.getAbsolutePath());
intent.setDataAndType(fileUri, mimeType);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
try {
startActivity(intent);
} catch (ActivityNotFoundException e) {
showToast("没有找到可以打开该文件的应用");
// 提示用户安装支持的应用(如 WPS、Office
showOfficeInstallDialog();
}
}
private void showOfficeInstallDialog() {
mainHandler.post(() -> {
new AlertDialog.Builder(this)
.setTitle("无法打开文件")
.setMessage("您的设备上没有安装可以打开Word文档的应用。是否要安装Microsoft Word?")
.setPositiveButton("安装", (d, w) -> {
try {
startActivity(new Intent(Intent.ACTION_VIEW,
Uri.parse("market://details?id=com.microsoft.office.word")));
} catch (ActivityNotFoundException ex) {
startActivity(new Intent(Intent.ACTION_VIEW,
Uri.parse("https://play.google.com/store/apps/details?id=com.microsoft.office.word")));
}
})
.setNegativeButton("取消", null)
.show();
});
}
// 辅助方法:在主线程更新状态
private void updateStatus(String message) {
tvStatus.setText(message);
}
// 辅助方法在主线程显示Toast
private void showToast(String message) {
mainHandler.post(() -> Toast.makeText(this, message, Toast.LENGTH_SHORT).show());
}
private String getMimeType(String url) {
// 初始化类型为null
String type = null;
// 从URL/文件路径中获取文件扩展名
String extension = MimeTypeMap.getFileExtensionFromUrl(url);
// 如果扩展名不为空
if (extension != null) {
// 根据扩展名获取对应的MIME类型
type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension.toLowerCase());
}
// 如果找到了MIME类型则返回否则返回通用类型"*/*"
return type != null ? type : "*/*";
}
private void initializeViews() {
selectTimeButton = findViewById(R.id.selectTimeButton);
startTimeText = findViewById(R.id.startTimeText);
endTimeText = findViewById(R.id.endTimeText);
btnUploadVideos2 = findViewById(R.id.btn_upload_videos2);
btnUploadVideos = findViewById(R.id.btn_upload_videos);
etUploadInterval = findViewById(R.id.et_upload_interval);
etUploadInterval.setOnFocusChangeListener((v, hasFocus) -> {
if (hasFocus) {
showKeyboard(v);
} else {
hideKeyboard();
}
});
btnFailedAudios = findViewById(R.id.btn_failed_audios);
btnFailedAudios2 = findViewById(R.id.btn_success_audios);
btnSetInterval = findViewById(R.id.btn_set_interval);
btnStartTimedUpload = findViewById(R.id.btn_start_timed_upload);
btnStopTimedUpload = findViewById(R.id.btn_stop_timed_upload);
btnUploadQueue = findViewById(R.id.btn_upload_queue);
btnStopUpload = findViewById(R.id.btn_stop_upload);
btnHistory = findViewById(R.id.btn_history);
btnHistory2 = findViewById(R.id.btn_history2);
statusText = findViewById(R.id.status_text);
switchMonitor = findViewById(R.id.switch_monitor);
locationText = findViewById(R.id.location_text);
btnAdmin = findViewById(R.id.btnAdmin);
tvProject = findViewById(R.id.tv_project);
tvProjectId = findViewById(R.id.tv_projectId);
tvUser = findViewById(R.id.tv_user);
tvUnit = findViewById(R.id.tvUnit);
tvBlade = findViewById(R.id.tvBlade);
tvUnitName = findViewById(R.id.tvUnitName);
}
private void initializeDatabase() {
db = Room.databaseBuilder(getApplicationContext(),
AppDatabase.class, "image-database").build();
db2 = Room.databaseBuilder(getApplicationContext(),
AppDatabase.class, "image-database2").build();
}
@SuppressLint("UnspecifiedRegisterReceiverFlag")
private void initializeReceivers() {
updateReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
unit = intent.getStringExtra("unit");
blade = intent.getIntExtra("blade", 0);
unitName = intent.getStringExtra("unitName");
password=intent.getStringExtra("password");
dataManager.setUnit(unit);
dataManager.setBlade(blade);
String partname=getPartName(blade);
partid=getPartIdByNameFast(unit,partname);
dataManager.setPartid(partid);
dataManager.setUnitName(unitName);
updateValues(unit, blade, unitName);
}
};
IntentFilter filter = new IntentFilter(ACTION_UPDATE_VALUES);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
registerReceiver(updateReceiver, filter, Context.RECEIVER_NOT_EXPORTED);
} else {
registerReceiver(updateReceiver, filter);
}
}
private void showKeyboard(View view) {
if (view.requestFocus()) {
view.postDelayed(() -> {
if (inputMethodManager == null) {
inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
}
if (inputMethodManager != null) {
inputMethodManager.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT);
}
}, 100);
}
}
private void hideKeyboard() {
View view = this.getCurrentFocus();
if (view != null) {
if (inputMethodManager == null) {
inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
}
inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0);
// 清除当前焦点
view.clearFocus();
}
}
private void initializePermissionLaunchers() {
// Video picker launcher
videoPickerLauncher = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
result -> {
if (result.getResultCode() == RESULT_OK && result.getData() != null) {
handleVideoSelection(result.getData());
}
});
// Permission request launcher
requestPermissionLauncher = registerForActivityResult(
new ActivityResultContracts.RequestMultiplePermissions(),
permissions -> {
if (permissions.values().stream().allMatch(Boolean::booleanValue)) {
openVideoPicker();
} else {
Toast.makeText(this, "需要权限才能选择视频", Toast.LENGTH_SHORT).show();
}
});
// Overlay permission launcher
overlayPermissionLauncher = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
result -> {
new Handler(Looper.getMainLooper()).postDelayed(() -> {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (Settings.canDrawOverlays(MainActivity.this)) {
startFloatingService();
} else {
Toast.makeText(MainActivity.this,
"需要悬浮窗权限才能显示悬浮窗", Toast.LENGTH_SHORT).show();
}
}
}, 500);
});
}
private void initialProject()
{
Intent intent = getIntent();
currentUsername = intent.getStringExtra("username");
projectName = intent.getStringExtra("projectName");
projectId=intent.getStringExtra("projectId");
tvProject.setText(projectName);
tvProjectId.setText(projectId);
tvUser.setText(currentUsername);
inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
}
private void requestInitialPermissions() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
String[] permissions = {
Manifest.permission.READ_MEDIA_IMAGES,
Manifest.permission.READ_MEDIA_VIDEO,
Manifest.permission.RECORD_AUDIO,
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.POST_NOTIFICATIONS
};
if (checkSelfPermission(permissions)) {
initLocationManager();
} else {
ActivityCompat.requestPermissions(this, permissions, PERMISSIONS_REQUEST_CODE);
}
} else {
String[] permissions = {
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.RECORD_AUDIO,
Manifest.permission.ACCESS_FINE_LOCATION
};
if (checkSelfPermission(permissions)) {
initLocationManager();
} else {
ActivityCompat.requestPermissions(this, permissions, PERMISSIONS_REQUEST_CODE);
}
}
// Special handling for overlay permission
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !Settings.canDrawOverlays(this)) {
requestOverlayPermission();
}
}
private boolean checkSelfPermission(String[] permissions) {
for (String permission : permissions) {
if (ContextCompat.checkSelfPermission(this, permission)
!= PackageManager.PERMISSION_GRANTED) {
return false;
}
}
return true;
}
private void setupButtonListeners() {
// Set up all button click listeners here
btnUploadVideos2.setOnClickListener(v -> openVideoPicker());
btnUploadVideos.setOnClickListener(v -> showVideoDateTimePicker());
selectTimeButton.setOnClickListener(v -> showDateTimePicker());
btnSetInterval.setOnClickListener(v -> {
hideKeyboard(); // 先隐藏键盘
setUploadInterval(); // 然后执行设置逻辑
});
btnStartTimedUpload.setOnClickListener(v -> startTimedUpload());
btnStopTimedUpload.setOnClickListener(v -> stopTimedUpload());
btnUploadQueue.setOnClickListener(v -> startUploadQueue());
btnStopUpload.setOnClickListener(v -> stopUploadQueue());
btnFailedAudios.setOnClickListener(v -> showFailedAudios());
btnFailedAudios2.setOnClickListener(v -> showSuccessAudios());
btnHistory.setOnClickListener(v -> showHistory(db, "历史记录(点击查看大图)", "删除所有记录", "确定要删除所有历史记录吗?"));
btnHistory2.setOnClickListener(v -> showHistory(db2, "缓存队列(点击查看大图)", "删除所有缓存", "确定要删除所有缓存吗?"));
switchMonitor.setChecked(false);
switchMonitor.setOnCheckedChangeListener((buttonView, isChecked) -> {
if (isChecked) {
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(
this,
new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
LOCATION_PERMISSION_REQUEST_CODE);
} else {
checkRecordPermission();
}
} else {
stopMonitoring();
}
});
findViewById(R.id.btnShowFloating).setOnClickListener(v -> {
if (FloatingWindowService.isFloatingWindowShowing) {
// 如果悬浮窗正在显示,则关闭它
FloatingWindowService.closeFloatingWindow(MainActivity.this);
} else {
// 如果悬浮窗没有显示,则检查权限并启动
if (checkOverlayPermission2()) {
startFloatingService();
} else {
BackgroundToast.show(MainActivity.this, "需要悬浮窗权限");
}
}
});
btnAdmin.setOnClickListener(v -> {
startActivity(new Intent(MainActivity.this, AdminActivity.class));
});
}
private void showFailedAudios() {
new Thread(() -> {
List<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(() -> {
List<AudioEntity> successAudios = db2.AudioDao().getAllAudios();
runOnUiThread(() -> {
// 使用自定义主题的 AlertDialog
AlertDialog.Builder builder = new AlertDialog.Builder(this, R.style.CustomAlertDialog);
builder.setTitle("上传成功的录音");
// 设置自定义标题样式
TextView titleView = new TextView(this);
titleView.setText("上传成功的录音");
titleView.setTextSize(20);
titleView.setTextColor(ContextCompat.getColor(this, R.color.colorPrimary));
titleView.setPadding(40, 40, 40, 20);
titleView.setGravity(Gravity.CENTER);
builder.setCustomTitle(titleView);
if (successAudios == null || successAudios.isEmpty()) {
builder.setMessage("没有上传成功的录音");
builder.setPositiveButton("确定", null);
builder.show();
return;
}
// 创建自定义适配器
ArrayAdapter<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;
}
};
// 添加数据到适配器
for (AudioEntity audio : successAudios) {
adapter.add(String.format("录音名称: %s\n时间: %s",
getFileNameFromPath(audio.getAudioPath()),
audio.getTime()));
}
builder.setAdapter(adapter, (dialog, which) -> {
// 点击item打开录音文件
AudioEntity selectedAudio = successAudios.get(which);
openAudioFile(selectedAudio.getAudioPath());
});
// 添加删除按钮
builder.setNegativeButton("一键删除", (dialog, which) -> deleteAllSuccessAudios(successAudios));
// 添加关闭按钮
builder.setPositiveButton("关闭", null);
AlertDialog dialog = builder.create();
// 设置Dialog窗口属性
Window window = dialog.getWindow();
if (window != null) {
window.setBackgroundDrawable(new ColorDrawable(Color.WHITE));
window.setGravity(Gravity.CENTER);
}
dialog.show();
// 设置按钮样式
Button negativeButton = dialog.getButton(DialogInterface.BUTTON_NEGATIVE);
negativeButton.setTextColor(ContextCompat.getColor(this, R.color.colorAccent));
Button positiveButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE);
positiveButton.setTextColor(ContextCompat.getColor(this, R.color.colorPrimary));
});
}).start();
}
// 从路径中获取文件名
private String getFileNameFromPath(String path) {
if (path == null || path.isEmpty()) return "未知录音";
int index = path.lastIndexOf('/');
return index != -1 ? path.substring(index + 1) : path;
}
// 打开录音文件
private void openAudioFile(String audioPath) {
File file = new File(audioPath);
if (!file.exists()) {
Toast.makeText(this, "录音文件不存在", Toast.LENGTH_SHORT).show();
return;
}
try {
// 使用正确的 authority
Uri audioUri = FileProvider.getUriForFile(
this,
getPackageName() + ".provider",
file
);
Log.d("FileProvider", "转换后的URI: " + audioUri.toString());
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(audioUri, "audio/*");
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
// 检查是否有应用可以处理这个Intent
if (intent.resolveActivity(getPackageManager()) != null) {
startActivity(intent);
} else {
Toast.makeText(this, "没有找到可以播放录音的应用", Toast.LENGTH_SHORT).show();
}
} catch (IllegalArgumentException e) {
Log.e("录音文件失败:",e.getMessage()+"路径为"+audioPath);
showErrorDialog("打开录音文件错误:",e.getMessage()+"路径为"+audioPath);
}
}
private void setupTimedUpload() {
timedUploadHandler = new Handler(Looper.getMainLooper());
SharedPreferences prefs = getSharedPreferences("UploadPrefs", MODE_PRIVATE);
uploadIntervalMinutes = prefs.getInt("upload_interval", 0);
if (uploadIntervalMinutes > 0) {
etUploadInterval.setText(String.valueOf(uploadIntervalMinutes));
}
}
private void deleteAllSuccessAudios(List<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();
}
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()) {
startVoiceRecording();
} else {
requestRecordPermission();
}
}
});
}
private boolean checkAndRequestNotificationPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(
this,
new String[]{Manifest.permission.POST_NOTIFICATIONS},
NOTIFICATION_PERMISSION_REQUEST_CODE
);
return false;
}
}
return true;
}
private void createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel(
"recording_channel",
"Recording Service",
NotificationManager.IMPORTANCE_LOW
);
NotificationManager manager = getSystemService(NotificationManager.class);
manager.createNotificationChannel(channel);
}
}
private boolean checkOverlayPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
return Settings.canDrawOverlays(this);
}
return true;
}
private void requestOverlayPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
Uri.parse("package:" + getPackageName()));
startActivityForResult(intent, OVERLAY_PERMISSION_REQUEST_CODE);
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == OVERLAY_PERMISSION_REQUEST_CODE) {
if (checkOverlayPermission()) {
} else {
Toast.makeText(this, "悬浮窗权限被拒绝,无法显示悬浮球", Toast.LENGTH_SHORT).show();
}
}
}
private boolean checkRecordPermission1() {
return ContextCompat.checkSelfPermission(this,
Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED;
}
private void requestRecordPermission() {
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.RECORD_AUDIO},
AUDIO_PERMISSION_REQUEST_CODE);
}
private void startVoiceRecording() {
Intent serviceIntent = new Intent(this, RecordingService.class);
serviceIntent.setAction("start_recording");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForegroundService(serviceIntent);
} else {
startService(serviceIntent);
}
isRecording = true;
dataManager.setIsRecording(isRecording);
floatingActionButton.setImageResource(R.drawable.ic_mic_on);
}
private void stopVoiceRecording() {
Intent serviceIntent = new Intent(this, RecordingService.class);
serviceIntent.setAction("stop_recording");
startService(serviceIntent);
isRecording = false;
dataManager.setIsRecording(isRecording);
floatingActionButton.setImageResource(R.drawable.ic_mic_off);
}
private String getPartName(int b)
{
return "叶片" +b;
}
private void queryPhotos() {
// 定义查询时间范围的条件(示例,可根据需要修改)
String selection = MediaStore.Images.Media.DATE_TAKEN + " BETWEEN ? AND ? AND"+
"(" + MediaStore.Images.Media.DATA + " LIKE '%/DCIM/%' OR " +
MediaStore.Images.Media.DATA + " LIKE '%/Camera/%')" ;;
// 使用Executor替代AsyncTask
ExecutorService executorService = Executors.newSingleThreadExecutor();
executorService.execute(() -> {
int count = 0;
ContentResolver contentResolver = getContentResolver();
Uri uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
String[] projection = {
MediaStore.Images.Media._ID,
MediaStore.Images.Media.DATA,
MediaStore.Images.Media.DATE_TAKEN,
MediaStore.Images.Media.LATITUDE,
MediaStore.Images.Media.LONGITUDE
};
String[] selectionArgs = {String.valueOf(startTime), String.valueOf(endTime)};
String sortOrder = MediaStore.Images.Media.DATE_TAKEN + " DESC";
try (Cursor cursor = contentResolver.query(uri, projection, selection, selectionArgs, sortOrder)) {
if (cursor != null) {
while (cursor.moveToNext()) {
long id = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Images.Media._ID));
String path = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA));
long time = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATE_TAKEN));
// 初始化位置信息
double latitude = 0.0;
double longitude = 0.0;
double altitude = 0.0;
// 从媒体库读取位置信息(如果有)
int latIndex = cursor.getColumnIndex(MediaStore.Images.Media.LATITUDE);
int lngIndex = cursor.getColumnIndex(MediaStore.Images.Media.LONGITUDE);
if (latIndex != -1 && lngIndex != -1) {
latitude = cursor.getDouble(latIndex);
longitude = cursor.getDouble(lngIndex);
}
// 改进位置获取
try {
if (locationManager != null && checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
Location lastLocation = locationManager.getLastKnownLocation(
LocationManager.GPS_PROVIDER);
if (lastLocation != null) {
lastKnownLocation = lastLocation;
runOnUiThread(() -> updateLocationText(lastLocation));
}
}
} catch (SecurityException e) {
e.printStackTrace();
}
if (lastKnownLocation != null) {
latitude = lastKnownLocation.getLatitude();
longitude = lastKnownLocation.getLongitude();
altitude = lastKnownLocation.getAltitude();
}
// 改进EXIF高度读取
try {
ExifInterface exif = new ExifInterface(path);
String altitudeStr = exif.getAttribute(ExifInterface.TAG_GPS_ALTITUDE);
if (altitudeStr != null) {
String[] parts = altitudeStr.split("/");
if (parts.length == 2) {
double alt = Double.parseDouble(parts[0]) /
Double.parseDouble(parts[1]);
altitude = alt;
}
}
} catch (Exception e) {
e.printStackTrace();
}
String user = getCurrentUser();
Log.d("PhotoQuery", "Processing photo for user: " + user);
// 使用更高效的方式生成缩略图
Bitmap thumbnail = null;
try {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(path, options);
options.inSampleSize = calculateInSampleSize(options, THUMBNAIL_SIZE, THUMBNAIL_SIZE);
options.inJustDecodeBounds = false;
thumbnail = BitmapFactory.decodeFile(path, options);
} catch (Exception e) {
e.printStackTrace();
}
count++;
if (thumbnail != null) {
lastPhotoId = Math.max(lastPhotoId, id);
double finalAltitude = altitude;
double finalLongitude = longitude;
double finalLatitude = latitude;
String finalproject=getProject();
String finalunit=getUnit();
String partname=getPartName(blade);
if(partid==null)
partid=partname;
// 使用另一个线程执行上传或保存操作
String finalPartid = partid;
String finalPartid1 = partid;
Executors.newSingleThreadExecutor().execute(() -> {
if (isNetworkAvailable()) {
uploadPhotoSync(path, time, lastKnownLocation, user, "0",finalproject,finalunit,blade, finalPartid,ChooseImageSource,unitName);
} else {
db2.imageDao().insert(new ImageEntity(path, time,
finalLatitude, finalLongitude,
finalAltitude, user, "0",finalproject,finalunit,blade,false,unitName,Temperator,Humidity,Weather,ChooseImageSource, finalPartid1));
}
});
}
}
cursor.close();
}
} catch (Exception e) {
Log.e("PhotoQuery", "Error querying photos", e);
} finally {
executorService.shutdown();
}
// 更新UI需要在主线程执行
final int finalCount = count;
runOnUiThread(() -> {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.getDefault());
Toast.makeText(MainActivity.this,
"找到 " + finalCount + " 张照片 (" +
sdf.format(new Date(startTime)) + "" +
sdf.format(new Date(endTime)) + ")上传中",
Toast.LENGTH_LONG).show();
});
});
}
// 辅助方法计算采样大小
private int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
while ((halfHeight / inSampleSize) >= reqHeight
&& (halfWidth / inSampleSize) >= reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
private void showDateTimePicker() {
hideKeyboard();
final Calendar calendar = Calendar.getInstance();
// Show prompt for start time selection
Toast.makeText(this, "请选择开始时间", Toast.LENGTH_SHORT).show();
// 日期选择器
DatePickerDialog datePickerDialog = new DatePickerDialog(this,
new DatePickerDialog.OnDateSetListener() {
@Override
public void onDateSet(DatePicker view, int year, int month, int day) {
calendar.set(year, month, day);
// 时间选择器
TimePickerDialog timePickerDialog = new TimePickerDialog(MainActivity.this,
new TimePickerDialog.OnTimeSetListener() {
@Override
public void onTimeSet(TimePicker view, int hour, int minute) {
calendar.set(Calendar.HOUR_OF_DAY, hour);
calendar.set(Calendar.MINUTE, minute);
startTime = calendar.getTimeInMillis();
// Update start time display immediately
updateTimeDisplay();
// Show prompt for end time selection
Toast.makeText(MainActivity.this, "请选择结束时间", Toast.LENGTH_SHORT).show();
// 选择结束时间
showEndDateTimePicker();
}
}, calendar.get(Calendar.HOUR_OF_DAY), calendar.get(Calendar.MINUTE), true);
timePickerDialog.setTitle("选择开始时间");
timePickerDialog.show();
}
}, calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH), calendar.get(Calendar.DAY_OF_MONTH));
datePickerDialog.setOnShowListener(dialog -> {
// 确保日期选择器获得焦点
datePickerDialog.getButton(DialogInterface.BUTTON_POSITIVE).requestFocus();
});
datePickerDialog.setTitle("选择开始日期");
datePickerDialog.show();
}
private void showEndDateTimePicker() {
hideKeyboard();
final Calendar calendar = Calendar.getInstance();
// Set initial time to start time + 1 hour as a suggestion
calendar.setTimeInMillis(startTime);
calendar.add(Calendar.HOUR, 1);
DatePickerDialog datePickerDialog = new DatePickerDialog(this,
new DatePickerDialog.OnDateSetListener() {
@Override
public void onDateSet(DatePicker view, int year, int month, int day) {
calendar.set(year, month, day);
TimePickerDialog timePickerDialog = new TimePickerDialog(MainActivity.this,
new TimePickerDialog.OnTimeSetListener() {
@Override
public void onTimeSet(TimePicker view, int hour, int minute) {
calendar.set(Calendar.HOUR_OF_DAY, hour);
calendar.set(Calendar.MINUTE, minute);
endTime = calendar.getTimeInMillis();
// 验证结束时间是否晚于开始时间
if (endTime <= startTime) {
Toast.makeText(MainActivity.this, "结束时间必须晚于开始时间", Toast.LENGTH_SHORT).show();
showEndDateTimePicker(); // 重新选择
return;
}
// 更新UI显示选择的时间
updateTimeDisplay();
// 显示确认对话框
showConfirmationDialog();
}
}, calendar.get(Calendar.HOUR_OF_DAY), calendar.get(Calendar.MINUTE), true);
timePickerDialog.setTitle("选择结束时间");
timePickerDialog.show();
}
}, calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH), calendar.get(Calendar.DAY_OF_MONTH));
datePickerDialog.setOnShowListener(dialog -> {
// 确保日期选择器获得焦点
datePickerDialog.getButton(DialogInterface.BUTTON_POSITIVE).requestFocus();
});
datePickerDialog.setTitle("选择结束日期");
datePickerDialog.show();
}
private void showConfirmationDialog() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.getDefault());
String message = "确认上传以下时间范围内的照片吗?\n\n" +
"开始时间: " + sdf.format(new Date(startTime)) + "\n" +
"结束时间: " + sdf.format(new Date(endTime));
new AlertDialog.Builder(this)
.setTitle("确认查询")
.setMessage(message)
.setPositiveButton("确认", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// 用户确认,开始查询照片
queryPhotos();
}
})
.setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// 用户取消,可以重新选择时间或不做任何操作
Toast.makeText(MainActivity.this, "已取消查询", Toast.LENGTH_SHORT).show();
}
})
.setNeutralButton("重新选择", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// 用户想重新选择时间
showDateTimePicker();
}
})
.show();
}
private void updateTimeDisplay() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.getDefault());
if (startTime > 0) {
startTimeText.setText("开始时间:" + sdf.format(new Date(startTime)));
}
if (endTime > 0) {
endTimeText.setText("结束时间:" + sdf.format(new Date(endTime)));
}
}
// 设置上传间隔
private void setUploadInterval() {
try {
hideKeyboard();
int minutes = Integer.parseInt(etUploadInterval.getText().toString());
if (minutes <= 0) {
showToast("请输入大于0的分钟数");
return;
}
uploadIntervalMinutes = minutes;
// 保存到SharedPreferences
SharedPreferences.Editor editor = getSharedPreferences("UploadPrefs", MODE_PRIVATE).edit();
editor.putInt("upload_interval", uploadIntervalMinutes);
editor.apply();
showToast( "上传间隔已设置为: " + minutes + "分钟");
// 如果定时上传正在运行,重新启动以应用新间隔
if (isTimedUploadRunning) {
stopTimedUpload();
startTimedUpload();
}
} catch (NumberFormatException e) {
showToast("请输入有效的数字");
}
}
// 开始定时上传
private void startTimedUpload() {
if (uploadIntervalMinutes <= 0) {
showToast("请先设置上传间隔");
return;
}
if (isTimedUploadRunning) {
showToast("定时上传已经在运行");
return;
}
isTimedUploadRunning = true;
btnStartTimedUpload.setEnabled(false);
btnStopTimedUpload.setEnabled(true);
etUploadInterval.setEnabled(false);
btnSetInterval.setEnabled(false);
// 设置定时任务
timedUploadRunnable = new Runnable() {
@Override
public void run() {
if (!isTimedUploadRunning) return;
startUploadQueue();
timedUploadHandler.postDelayed(this, uploadIntervalMinutes * 60 * 1000L);
}
};
timedUploadHandler.postDelayed(timedUploadRunnable, uploadIntervalMinutes * 60 * 1000L);
statusText.setText("定时上传已启动,间隔: " + uploadIntervalMinutes + "分钟");
}
// 停止定时上传
private void stopTimedUpload() {
if (!isTimedUploadRunning) return;
isTimedUploadRunning = false;
if (timedUploadRunnable != null) {
timedUploadHandler.removeCallbacks(timedUploadRunnable);
}
btnStartTimedUpload.setEnabled(true);
btnStopTimedUpload.setEnabled(false);
etUploadInterval.setEnabled(true);
btnSetInterval.setEnabled(true);
statusText.setText("定时上传已停止");
}
private void uploadPhotoSync(String filePath, long time, Location location, String user,
String audioPath, String project, String unit, int blade,
String partId, String imageSource, String unitName) {
File file = new File(filePath);
if (!file.exists()) {
BackgroundToast.show(MainActivity.this, "文件不存在: " + filePath);
return;
}
double latitude = location != null ? location.getLatitude() : 0;
double longitude = location != null ? location.getLongitude() : 0;
double altitude = location != null ? location.getAltitude() : 0;
AudioEntity a=db.AudioDao().getAudioByPath(audioPath);
try {
// 构建请求URL
HttpUrl.Builder urlBuilder = HttpUrl.parse("http://pms.dtyx.net:9158/image/" + imageSource + "/upload-batch/" + partId)
.newBuilder()
.addQueryParameter("collectorId",UserId)
.addQueryParameter("collectorName", user)
.addQueryParameter("humidness", String.valueOf(Humidity))
.addQueryParameter("shootingDistance", String.valueOf(0)) // 根据需要设置实际值
.addQueryParameter("temperatureMax", String.valueOf(Temperator))
.addQueryParameter("temperatureMin", String.valueOf(Temperator))
.addQueryParameter("weather", Weather)
.addQueryParameter("windLevel", String.valueOf(0)); // 根据需要设置实际值
// 创建请求体
RequestBody requestBody = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("file", file.getName(),
RequestBody.create(file, MediaType.parse("image/*")))
.build();
Request request = new Request.Builder()
.url(urlBuilder.build())
.post(requestBody)
.addHeader("Authorization", token)
.addHeader("Content-Type", "application/x-www-form-urlencoded")
.build();
try (Response response = httpClient.newCall(request).execute()) {
if (response.isSuccessful()) {
// 解析响应数据
String responseData = response.body().string();
JSONObject jsonResponse = new JSONObject(responseData);
JSONObject data = jsonResponse.optJSONObject("data");
String imageId = data != null ? data.optString("imageId") : "";
if(a!=null)
{
uploadAudio(imageId,audioPath,token);
}
BackgroundToast.show(
MainActivity.this,
"上传成功: " + response.code() + " " + filePath
);
executor.execute(() -> {
db.imageDao().insert(new ImageEntity(filePath, time,
latitude, longitude, altitude, user, audioPath,
project, unit, blade, true, unitName,
Temperator, Humidity, Weather, imageSource, partId));
});
} else {
BackgroundToast.show(MainActivity.this,
"上传失败: " + response.code() + " " + response.message()
);
if (isUploading) { // 只有主动上传时才重新加入队列
executor.execute(() -> {
db2.imageDao().insert(new ImageEntity(filePath, time,
latitude, longitude, altitude, user, audioPath,
project, unit, blade, false, unitName,
Temperator, Humidity, Weather, imageSource, partId));
});
}
}
}
} catch (Exception e) {
if (isUploading) { // 只有主动上传时才重新加入队列
executor.execute(() -> {
db2.imageDao().insert(new ImageEntity(filePath, time,
latitude, longitude, altitude, user, audioPath,
project, unit, blade, false, unitName,
Temperator, Humidity, Weather, imageSource, partId));
});
}
BackgroundToast.show(MainActivity.this,
"上传失败: " + e.getMessage()
);
}
}
private void stopUploadQueue() {
isUploading = false;
dataManager.setUploading(isUploading);
// 中断当前上传
synchronized (uploadLock) {
// 这里会中断当前上传
uploadLock.notifyAll(); // 唤醒可能正在等待的上传线程
}
btnStopUpload.setEnabled(false);
btnUploadQueue.setEnabled(true);
// 更新状态显示
runOnUiThread(() -> {
statusText.setText(isTimedUploadRunning ? "定时上传已暂停" : "上传已停止");
Toast.makeText(MainActivity.this, "上传队列已停止", Toast.LENGTH_SHORT).show();
});
}
private void checkRecordPermission() {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO)
!= PackageManager.PERMISSION_GRANTED) {
// 请求录音权限
ActivityCompat.requestPermissions(
this,
new String[]{Manifest.permission.RECORD_AUDIO},
AUDIO_PERMISSION_REQUEST_CODE);
}else {
// 已经有录音权限,继续检查图片权限
checkPermissionAndPickImage();
}
}
private void initLocationManager() {
locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
try {
Location lastLocation = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
if (lastLocation != null) {
lastKnownLocation = lastLocation;
updateLocationText(lastLocation);
dataManager.setLocation(lastLocation);
}
} catch (SecurityException e) {
e.printStackTrace();
BackgroundToast.show( MainActivity.this,
" 获取位置失败"
);
}
locationListener = new LocationListener() {
@Override
public void onLocationChanged(Location location) {
lastKnownLocation = location;
Toast.makeText(MainActivity.this,
"位置更新: " + location.getLatitude() + ", " + location.getLongitude(),
Toast.LENGTH_SHORT).show();
// 打印到Logcat
dataManager.setLocation(lastKnownLocation);
updateLocationText(lastKnownLocation);
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {}
@Override
public void onProviderEnabled(String provider) {}
@Override
public void onProviderDisabled(String provider) {}
};
try {
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
Toast.makeText(this,"没有授予定位权限",Toast.LENGTH_SHORT).show();
return;
}
locationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER,
60000, // 5秒更新间隔
10, // 10米距离变化
locationListener);
} catch (Exception e) {
e.printStackTrace();
}
}
private void updateLocationText(Location location) {
runOnUiThread(() -> {
if (location == null) {
locationText.setText("当前位置: 无法获取位置信息");
return;
}
String locationInfo = String.format(Locale.getDefault(),
"当前位置:\n纬度: %.6f\n经度: %.6f\n海拔: %.2f米\n精度: %.2f米\n更新时间: %s",
location.getLatitude(),
location.getLongitude(),
location.getAltitude(),
location.getAccuracy(),
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()).format(new Date(location.getTime())));
locationText.setText(locationInfo);
});
}
// 将位置转换为字符串用于打印
private String locationToString(Location location) {
return String.format(Locale.getDefault(),
"纬度: %.6f, 经度: %.6f, 海拔: %.2f, 精度: %.2f, 时间: %s",
location.getLatitude(),
location.getLongitude(),
location.getAltitude(),
location.getAccuracy(),
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()).format(new Date(location.getTime())));
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == PERMISSION_REQUEST_StartMonitor_CODE) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// 权限已授予,启动服务
startMonitoring();
statusText.setText("正在监听中");
} else {
// 权限被拒绝,显示提示
Toast.makeText(this, "需要权限才能运行服务", Toast.LENGTH_SHORT).show();
}
}
else if (requestCode == PERMISSIONS_REQUEST_CODE) {
// 检查所有权限是否被授予
boolean allPermissionsGranted = true;
for (int result : grantResults) {
if (result != PackageManager.PERMISSION_GRANTED) {
allPermissionsGranted = false;
lastPhotoId=getLastPhotoId();
Log.e("最后一张照片值:",String.valueOf(lastPhotoId));
break;
}
}
if (allPermissionsGranted) {
// 所有权限都已授予,继续初始化位置管理器
initLocationManager();
haveInitalFloating=true;
initFloatingActionButton();
} else {
// 处理权限被拒绝的情况
handlePermissionDenial();
}
}
else if (requestCode == REQUEST_VIDEO_PERMISSION) {
boolean allGranted = true;
for (int result : grantResults) {
if (result != PackageManager.PERMISSION_GRANTED) {
allGranted = false;
break;
}
}
if(allGranted) {
Toast.makeText(this, "媒体权限已授权", Toast.LENGTH_SHORT).show();
queryVideos();
}
else {
Toast.makeText(this, "需要媒体权限才能查询视频", Toast.LENGTH_SHORT).show();
}
}
//通知权限
else if (requestCode == NOTIFICATION_PERMISSION_REQUEST_CODE) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
BackgroundToast.show(this,"通知权限已开启");
} else {
Toast.makeText(this, "通知权限被拒绝,无法显示录音状态", Toast.LENGTH_SHORT).show();
}
}
else if (requestCode == LOCATION_PERMISSION_REQUEST_CODE) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// 位置权限已授予,继续检查图片权限
checkRecordPermission();
initLocationManager();
Toast.makeText(this, "位置权限已授予", Toast.LENGTH_SHORT).show();
} else {
// 位置权限被拒绝,关闭开关
switchMonitor.setChecked(false);
Toast.makeText(this, "需要位置权限才能获取位置信息", Toast.LENGTH_SHORT).show();
}
}
else if (requestCode == AUDIO_PERMISSION_REQUEST_CODE) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// 录音权限已授予,继续检查位置权限
checkPermissionAndPickImage();
Toast.makeText(this, "语音权限已授予", Toast.LENGTH_SHORT).show();
} else {
// 录音权限被拒绝,关闭开关
switchMonitor.setChecked(false);
Toast.makeText(this, "需要录音权限才能录制语音", Toast.LENGTH_SHORT).show();
}
}
//图片权限
else if (requestCode == PERMISSION_REQUEST_CODE) {
boolean allGranted=true;
for (int result : grantResults) {
if (result != PackageManager.PERMISSION_GRANTED) {
allGranted = false;
break;
}
}
if (allGranted) {
Toast.makeText(this, "媒体权限已授与", Toast.LENGTH_SHORT).show();
startMonitoring();
} else {
switchMonitor.setChecked(false);
Toast.makeText(this, "需要媒体权限以选择图片", Toast.LENGTH_SHORT).show();
if (shouldShowRequestPermissionRationale(permissions)) {
showPermissionExplanationDialog(() -> {
// 重新请求权限
requestPermissions(permissions);
});
}
}
}
}
private void handlePermissionDenial() {
// 检查哪些关键权限被拒绝
boolean showRationale = false;
// 检查是否有权限被永久拒绝(用户勾选了"不再询问"
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
for (String permission : new String[]{
Manifest.permission.READ_MEDIA_IMAGES,
Manifest.permission.READ_MEDIA_VIDEO,
Manifest.permission.RECORD_AUDIO,
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.POST_NOTIFICATIONS}) {
if (!shouldShowRequestPermissionRationale(permission)
&& ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
showRationale = true;
break;
}
}
} else {
for (String permission : new String[]{
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.RECORD_AUDIO,
Manifest.permission.ACCESS_FINE_LOCATION}) {
if (!shouldShowRequestPermissionRationale(permission)
&& ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
showRationale = true;
break;
}
}
}
if (showRationale) {
// 显示解释为什么需要权限,并引导用户去设置页面
showPermissionExplanationDialog();
} else {
// 部分权限被拒绝,但用户没有选择"不再询问"
Toast.makeText(this, "部分功能可能无法正常使用", Toast.LENGTH_SHORT).show();
// 仍然尝试初始化位置管理器(如果位置权限被授予)
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
== PackageManager.PERMISSION_GRANTED) {
initLocationManager();
}
}
}
// 显示权限解释对话框
private void showPermissionExplanationDialog() {
new AlertDialog.Builder(this)
.setTitle("需要权限")
.setMessage("某些功能需要权限才能正常工作。请前往设置授予权限。")
.setPositiveButton("去设置", (dialog, which) -> {
// 跳转到应用设置页面
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
Uri uri = Uri.fromParts("package", getPackageName(), null);
intent.setData(uri);
startActivity(intent);
})
.setNegativeButton("取消", null)
.show();
}
private void checkPermissionAndPickImage() {
String[] permissions;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { // Android 14+
permissions = new String[]{
Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED,
Manifest.permission.READ_MEDIA_IMAGES,
Manifest.permission.READ_MEDIA_VIDEO
};
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { // Android 13
permissions = new String[]{Manifest.permission.READ_MEDIA_IMAGES,
Manifest.permission.READ_MEDIA_VIDEO};
} else { // Android 12-
permissions = new String[]{Manifest.permission.READ_EXTERNAL_STORAGE};
}
if (checkAnyPermissionGranted(permissions)) {
try{startMonitoring();}
catch (Exception e)
{
dataManager.setOthermsg(e.toString());
BackgroundToast.show(this,"监听失败");
}
} else {
requestPermissions(permissions);
}
}
private boolean checkAnyPermissionGranted(String[] permissions) {
for (String perm : permissions) {
if (ContextCompat.checkSelfPermission(this, perm) == PackageManager.PERMISSION_GRANTED) {
return true;
}
}
return false;
}
private void requestPermissions(String[] permissions) {
if (shouldShowRequestPermissionRationale(permissions)) {
showPermissionExplanationDialog(() ->
ActivityCompat.requestPermissions(this, permissions, PERMISSION_REQUEST_CODE));
} else {
ActivityCompat.requestPermissions(this, permissions, PERMISSION_REQUEST_CODE);
}
}
private boolean shouldShowRequestPermissionRationale(String[] permissions) {
for (String perm : permissions) {
if (ActivityCompat.shouldShowRequestPermissionRationale(this, perm)) {
return true;
}
}
return false;
}
private void openAppSettings() {
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
intent.setData(Uri.fromParts("package", getPackageName(), null));
startActivity(intent);
}
private void showPermissionExplanationDialog(Runnable onContinue) {
new AlertDialog.Builder(this)
.setTitle("需要权限")
.setMessage("此功能需要访问图片权限以监控相册变化")
.setPositiveButton("继续", (dialog, which) -> onContinue.run())
.setNegativeButton("取消", null)
.show();
}
private void showHistory(AppDatabase database, String title, String deleteBtnText, String deleteMessage) {
new Thread(() -> {
List<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"+
"叶片号:%d\n"+
"图片来源:%s\n",
sdf.format(new Date(item.time)), // 时间
item.path, // 路径
item.latitude, // 纬度
item.longitude, // 经度
item.altitude, // 高度(单位:米)
item.user,// 用户信息
projectName,
item.unitName,
item.blade,
item.imageSource
));
itemView.setOnClickListener(v -> openFullImage(item.path));
}
private void loadThumbnail(ImageView iv, String path) {
try {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = DECODE_SAMPLE_SIZE;
Bitmap src = BitmapFactory.decodeFile(path, options);
if (src != null) {
Bitmap thumbnail = Bitmap.createScaledBitmap(src, THUMBNAIL_SIZE, THUMBNAIL_SIZE, true);
runOnUiThread(() -> iv.setImageBitmap(thumbnail));
}
} catch (Exception e) {
e.printStackTrace();
}
}
private void confirmDelete(AppDatabase database, String message) {
new AlertDialog.Builder(this)
.setTitle("确认删除")
.setMessage(message)
.setPositiveButton("删除", (d, w) -> deleteAllRecords(database))
.setNegativeButton("取消", null)
.show();
}
private void deleteAllRecords(AppDatabase database) {
executor.execute(() -> {
database.imageDao().deleteAll();
runOnUiThread(() -> {
Toast.makeText(this, "已删除所有记录", Toast.LENGTH_SHORT).show();
dismissCurrentDialog(database);
});
});
}
private void updateCurrentDialog(AppDatabase database, AlertDialog dialog) {
if (database == db) {
currentDialog = dialog;
} else {
currentDialog2 = dialog;
}
}
private void dismissCurrentDialog(AppDatabase database) {
if (database == db && currentDialog != null && currentDialog.isShowing()) {
currentDialog.dismiss();
} else if (database == db2 && currentDialog2 != null && currentDialog2.isShowing()) {
currentDialog2.dismiss();
}
}
private boolean isNetworkAvailable() {
ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
if (cm == null) return false;
NetworkCapabilities capabilities = cm.getNetworkCapabilities(cm.getActiveNetwork());
return capabilities != null &&
(capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) ||
capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) ||
capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET));
}
private void stopMonitoring() {
Intent serviceIntent = new Intent(this, PhotoMonitoringService.class);
stopService(serviceIntent);
statusText.setText("当前状态:未监听");
switchMonitor.setChecked(false);
}
private void startUploadQueue() {
if (isUploading) {
Toast.makeText(this, "上传正在进行中", Toast.LENGTH_SHORT).show();
return;
}
executor.execute(() -> {
isUploading = true;
dataManager.setUploading(isUploading);
runOnUiThread(() -> {
btnUploadQueue.setEnabled(false);
btnStopUpload.setEnabled(true);
statusText.setText("正在上传缓存队列..." + (isTimedUploadRunning ? "(定时上传)" : ""));
});
try {
while (isUploading) {
List<ImageEntity> queue = db2.imageDao().getAll();
if (queue.isEmpty()) {
break;
}
for (ImageEntity image : queue) {
if (!isUploading) break;
String partname=getPartName(image.blade);
if(partid==null)
partid=partname;
synchronized (uploadLock) {
uploadPhotoSync(image.path, image.time,
new Location("") {{
setLatitude(image.latitude);
setLongitude(image.longitude);
setAltitude(image.altitude);
}},
image.user, image.audioPath,image.project,image.unit,image.blade,partid,image.imageSource,image.unitName);
}
if (isUploading) {
db2.imageDao().delete(image);
}
}
}
} finally {
runOnUiThread(() -> {
btnUploadQueue.setEnabled(true);
btnStopUpload.setEnabled(false);
String status = isUploading ?
(isTimedUploadRunning ? "定时上传完成" : "手动上传完成") :
"上传已终止";
statusText.setText(status);
});
isUploading = false;
dataManager.setUploading(isUploading);
}
});
}
@Override
protected void onDestroy() {
super.onDestroy();
if (updateReceiver != null) {
unregisterReceiver(updateReceiver);
}
stopMonitoring();
executor.shutdown();
if (httpClient != null) {
httpClient.dispatcher().executorService().shutdown();
httpClient.connectionPool().evictAll();
}
stopTimedUpload();
if (!executor.isTerminated()) {
executor.shutdownNow();
}
// 移除悬浮球
if (floatingActionButton != null) {
WindowManager windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
windowManager.removeView(floatingActionButton);
}
if(isRecording)
stopVoiceRecording();
}
private String getCurrentUser() {
String user = name;
// 如果用户没有输入信息,则使用设备型号作为默认值
if (user=="-1") {
user = currentUsername;
}
return user;
}
private String getProject(){
return projectId.isEmpty()?"项目ID未填写":projectId;
}
private String getUnit()
{
return unit.isEmpty()?"机组未填写":unit;
}
private int getBlade()
{
return blade;
}
private void openFullImage(String imagePath) {
File file = new File(imagePath);
Uri uri = FileProvider.getUriForFile(
this,
getPackageName() + ".provider",
file
);
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(uri, "image/*");
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(intent);
}
private void uploadAudio(String imageId, String audioPath, String token) {
// 1. 参数校验(在调用线程执行,避免不必要的后台任务)
if (audioPath == null || audioPath.equals("0")) {
BackgroundToast.show(MainActivity.this, "文件路径为空");
return;
}
File audioFile = new File(audioPath);
if (!audioFile.exists()) {
BackgroundToast.show(MainActivity.this, "文件不存在");
return;
}
// 2. 在后台线程执行上传逻辑
executor.execute(() -> {
// 3. 构建请求体(已在后台线程)
RequestBody requestBody = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("file", audioFile.getName(),
RequestBody.create(audioFile, MediaType.parse("audio/*")))
.build();
// 4. 构建请求
Request.Builder requestBuilder = new Request.Builder()
.url("http://pms.dtyx.net:9158/audio/upload/" + imageId)
.post(requestBody);
if (token != null && !token.isEmpty()) {
requestBuilder.addHeader("Authorization", token);
}
// 5. 同步执行请求try-with-resources 自动关闭 Response
try (Response response = httpClient.newCall(requestBuilder.build()).execute()) {
String currentTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault())
.format(new Date());
if (response.isSuccessful()) {
// 上传成功
db.AudioDao().deleteByPath(audioPath);
db2.AudioDao().insert(new AudioEntity(audioPath, imageId, currentTime,true));
BackgroundToast.show(MainActivity.this, "录音上传成功: " + imageId);
} else {
// 上传失败HTTP 错误)
db.AudioDao().insert(new AudioEntity(audioPath, imageId, currentTime,false));
BackgroundToast.show(MainActivity.this,
"录音上传失败: " + response.code() + " " + response.message());
}
} catch (IOException e) {
// 网络异常
String currentTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault())
.format(new Date());
db.AudioDao().insert(new AudioEntity(audioPath, imageId, currentTime,false));
BackgroundToast.show(MainActivity.this, "录音上传失败: " + e.getMessage());
}
});
}
private long getLastPhotoId() {
long id = -1;
Cursor cursor = getContentResolver().query(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
new String[]{"MAX(" + MediaStore.Images.Media._ID + ")"},
null,
null,
null
);
if (cursor != null && cursor.moveToFirst()) {
id = cursor.getLong(0); // MAX(_ID) 的结果在索引 0
cursor.close();
}
return id;
}
}