3973 lines
161 KiB
Java
3973 lines
161 KiB
Java
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;
|
||
}
|
||
|
||
|
||
} |