diff --git a/FloatingWindowService.java b/FloatingWindowService.java index a1de064..1423288 100644 --- a/FloatingWindowService.java +++ b/FloatingWindowService.java @@ -1,4 +1,3 @@ - package com.example.myapplication.Service; import android.annotation.SuppressLint; @@ -18,6 +17,8 @@ import android.os.Build; import android.os.Handler; import android.os.IBinder; import android.os.Looper; +import android.os.VibrationEffect; +import android.os.Vibrator; import android.text.Editable; import android.text.TextUtils; import android.text.TextWatcher; @@ -75,9 +76,11 @@ public class FloatingWindowService extends Service { private String lastUnitName = ""; private AppDatabase database; private TurbineDao turbineDao; - private AutoCompleteTextView actvUnit; // 替换原来的EditText + private AutoCompleteTextView actvUnit; private ArrayAdapter turbineAdapter; private String projectID; + public static boolean isFloatingWindowShowing = false; + @SuppressLint("SetTextI18n") @Override public void onCreate() { @@ -85,19 +88,17 @@ public class FloatingWindowService extends Service { createNotificationChannel(); database = AppDatabase.getDatabase(this); turbineDao = database.turbineDao(); - createNotificationChannel(); showFloatingWindow(); - } + @Override public int onStartCommand(Intent intent, int flags, int startId) { if (intent != null && intent.hasExtra(EXTRA_PROJECT_ID)) { projectID = intent.getStringExtra(EXTRA_PROJECT_ID); - - } return super.onStartCommand(intent, flags, startId); } + private void createNotificationChannel() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { NotificationChannel channel = new NotificationChannel( @@ -112,6 +113,7 @@ public class FloatingWindowService extends Service { private void showFloatingWindow() { // 创建前台服务通知 + isFloatingWindowShowing = true; Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID) .setContentTitle("悬浮窗服务运行中") .setContentText("正在显示悬浮提示框") @@ -120,11 +122,9 @@ public class FloatingWindowService extends Service { .build(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - // Android 10+ 需要指定服务类型 startForeground(NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE); } else { - // Android 8.0-9.0 startForeground(NOTIFICATION_ID, notification); } @@ -154,14 +154,57 @@ public class FloatingWindowService extends Service { setupTurbineDropdown(); radioGroup = floatingView.findViewById(R.id.radioGroup); + // 设置关闭按钮点击事件 + View btnClose = floatingView.findViewById(R.id.btnClose); + btnClose.setOnClickListener(v -> { + // 震动反馈 + Vibrator vibrator = (Vibrator) getSystemService(VIBRATOR_SERVICE); + if (vibrator != null && vibrator.hasVibrator()) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + vibrator.vibrate(VibrationEffect.createOneShot(50, VibrationEffect.DEFAULT_AMPLITUDE)); + } else { + vibrator.vibrate(50); + } + } + closeFloatingWindow(); + }); + setupDragListener(params); setupValueChangeListeners(); - setupAutoCompleteBehavior(); + } + private void closeFloatingWindow() { + if (floatingView != null && floatingView.getWindowToken() != null) { + // 添加淡出动画 + floatingView.animate() + .alpha(0f) + .setDuration(200) + .withEndAction(() -> { + removeFloatingView(); + stopSelf(); + }) + .start(); + } else { + removeFloatingView(); + stopSelf(); + } + isFloatingWindowShowing = false; + } + + private synchronized void removeFloatingView() { + try { + if (windowManager != null && floatingView != null && floatingView.getWindowToken() != null) { + windowManager.removeView(floatingView); + } + } catch (IllegalArgumentException e) { + Log.e("FloatingWindow", "View already removed", e); + } finally { + floatingView = null; + } } private void setupAutoCompleteBehavior() { - actvUnit.setFocusableInTouchMode(true); // 允许触摸获取焦点 + actvUnit.setFocusableInTouchMode(true); actvUnit.setFocusable(true); actvUnit.setOnClickListener(v -> { if (turbineAdapter.getCount() > 0) { @@ -190,6 +233,7 @@ public class FloatingWindowService extends Service { return false; }); } + private void showSoftInputDelayed(View view) { view.postDelayed(() -> { InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE); @@ -205,52 +249,55 @@ public class FloatingWindowService extends Service { imm.hideSoftInputFromWindow(view.getWindowToken(), 0); } } -// 移除 setupInputMethod() 方法,因为功能已整合到上面的方法中 - private void setupDragListener(WindowManager.LayoutParams params) { - floatingView.findViewById(R.id.floatingContainer).setOnTouchListener(new View.OnTouchListener() { + View dragHandle = floatingView.findViewById(R.id.floatingContainer); + + dragHandle.setOnTouchListener(new View.OnTouchListener() { private int initialX, initialY; private float initialTouchX, initialTouchY; @Override public boolean onTouch(View v, MotionEvent event) { + // 检查是否点击在关闭按钮上 + if (!isViewAttached()) { + return false; + } + Rect closeButtonRect = new Rect(); + floatingView.findViewById(R.id.btnClose).getGlobalVisibleRect(closeButtonRect); + if (closeButtonRect.contains((int)event.getRawX(), (int)event.getRawY())) { + return false; + } + + // 检查是否点击在输入区域 + Rect inputRect = new Rect(); + actvUnit.getGlobalVisibleRect(inputRect); + if (inputRect.contains((int)event.getRawX(), (int)event.getRawY())) { + return false; + } + switch (event.getAction()) { case MotionEvent.ACTION_DOWN: - // 检查是否点击在输入区域 - Rect rect = new Rect(); - actvUnit.getGlobalVisibleRect(rect); - if (rect.contains((int)event.getRawX(), (int)event.getRawY())) { - // 如果是点击输入区域,不处理拖动,让输入框获取焦点 - return false; - } - - // 记录初始位置 initialX = params.x; initialY = params.y; initialTouchX = event.getRawX(); initialTouchY = event.getRawY(); - - // 隐藏键盘和下拉菜单 - hideSoftInput(actvUnit); - actvUnit.dismissDropDown(); return true; case MotionEvent.ACTION_MOVE: - // 更新悬浮窗位置 params.x = initialX + (int)(event.getRawX() - initialTouchX); params.y = initialY + (int)(event.getRawY() - initialTouchY); windowManager.updateViewLayout(floatingView, params); return true; case MotionEvent.ACTION_UP: - // 拖动结束,不处理焦点 return true; } return false; } }); } + private void setupTurbineDropdown() { turbineAdapter = new ArrayAdapter(this, R.layout.dropdown_item, R.id.text1) { @@ -261,7 +308,6 @@ public class FloatingWindowService extends Service { TextView textView = view.findViewById(R.id.text1); Turbine turbine = getItem(position); if (turbine != null) { - // 显示更详细的信息 textView.setText(String.format(turbine.turbineName)); textView.setSingleLine(false); textView.setMaxLines(3); @@ -277,17 +323,14 @@ public class FloatingWindowService extends Service { FilterResults results = new FilterResults(); List filtered; if (TextUtils.isEmpty(constraint)) { - // 无输入条件时显示全部数据 filtered = turbineDao.getTurbinesByProject(projectID); } else { - // 有输入条件时执行搜索 filtered = turbineDao.searchTurbines(projectID, "%" + constraint + "%"); } results.values = filtered; results.count = filtered.size(); return results; - } @Override @@ -308,11 +351,10 @@ public class FloatingWindowService extends Service { }; actvUnit.setAdapter(turbineAdapter); - actvUnit.setThreshold(0); // 输入1个字符后开始搜索 + actvUnit.setThreshold(0); actvUnit.setDropDownHeight(ViewGroup.LayoutParams.WRAP_CONTENT); - actvUnit.setDropDownVerticalOffset(10); // 下拉框与输入框的垂直偏移 + actvUnit.setDropDownVerticalOffset(10); - // 设置输入监听 actvUnit.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {} @Override public void afterTextChanged(Editable s) {} @@ -321,9 +363,8 @@ public class FloatingWindowService extends Service { public void onTextChanged(CharSequence s, int start, int before, int count) { String input=s.toString().trim(); lastUnit = input; - lastUnitName = input; // 如果没有匹配项,使用原始输入作为名称 + lastUnitName = input; if (turbineAdapter.getCount() > 0 && !TextUtils.isEmpty(input)) { - // 尝试查找完全匹配的项 for (int i = 0; i < turbineAdapter.getCount(); i++) { Turbine turbine = turbineAdapter.getItem(i); if (turbine != null && turbine.turbineName.equalsIgnoreCase(input)) { @@ -333,7 +374,7 @@ public class FloatingWindowService extends Service { } } } - checkAndSendUpdate(); // 实时触发检查 + checkAndSendUpdate(); } }); @@ -344,14 +385,11 @@ public class FloatingWindowService extends Service { ExecutorService executor = Executors.newSingleThreadExecutor(); executor.execute(() -> { try { - // 先检查本地是否有数据 List localTurbines = turbineDao.getTurbinesByProject(projectID); if (localTurbines.isEmpty() || isNetworkAvailable()) { - // 如果本地无数据或有网络,尝试从API获取 fetchTurbinesFromApi(); } else { - // 使用本地数据更新UI mainHandler.post(() -> updateTurbineList(localTurbines)); } } catch (Exception e) { @@ -359,13 +397,13 @@ public class FloatingWindowService extends Service { } }); } + private void updateTurbineList(List turbines) { turbineAdapter.clear(); if (!turbines.isEmpty()) { turbineAdapter.addAll(turbines); } - // 如果输入框有内容,重新过滤 if (!TextUtils.isEmpty(actvUnit.getText())) { turbineAdapter.getFilter().filter(actvUnit.getText()); } @@ -401,16 +439,14 @@ public class FloatingWindowService extends Service { List turbines = apiResponse.getData(); Log.d("API_SUCCESS", "Fetched " + turbines.size() + " turbines"); - // 保存到数据库(子线程) new Thread(() -> { try { turbineDao.deleteByProjectId(projectID); for (Turbine turbine : turbines) { - turbine.projectId = projectID; // 确保 projectId 正确 + turbine.projectId = projectID; } turbineDao.insertAll(turbines); - // 使用 mainHandler 更新 UI(主线程) mainHandler.post(() -> { updateTurbineList(turbines); showToast("数据加载成功"); @@ -420,19 +456,14 @@ public class FloatingWindowService extends Service { mainHandler.post(() -> showToast("保存数据失败: " + e.getMessage())); } }).start(); - } else { - // API 返回错误(如 code != 200) String errorMsg = "服务器错误: " + apiResponse.getCode() + " - " + apiResponse.getMsg(); Log.e("API_ERROR", errorMsg); mainHandler.post(() -> showToast(errorMsg)); } - } else { - // HTTP 状态码非 200(如 404、500) String errorMsg = "请求失败: HTTP " + response.code(); if (response.errorBody() != null) { - try { errorMsg += "\n" + response.errorBody().string(); } catch (IOException e) { @@ -447,7 +478,6 @@ public class FloatingWindowService extends Service { @Override public void onFailure(Call>> call, Throwable t) { - // 网络请求失败(如无网络、超时) String errorMsg = "网络错误: " + t.getMessage(); Log.e("NETWORK_ERROR", errorMsg, t); mainHandler.post(() -> showToast(errorMsg)); @@ -455,12 +485,11 @@ public class FloatingWindowService extends Service { }); } - // 显示 Toast 的辅助方法 private void showToast(String message) { Toast.makeText(this, message, Toast.LENGTH_LONG).show(); } + private void setupValueChangeListeners() { - // 监听机组号输入框变化 actvUnit.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {} @@ -469,21 +498,19 @@ public class FloatingWindowService extends Service { public void onTextChanged(CharSequence s, int start, int before, int count) {} @Override - public void afterTextChanged(Editable s) { - - } + public void afterTextChanged(Editable s) {} }); - // 添加下拉框项点击监听 + actvUnit.setOnItemClickListener((parent, view, position, id) -> { Turbine selected = turbineAdapter.getItem(position); if (selected != null) { lastUnit = selected.turbineId; lastUnitName = selected.turbineName; - actvUnit.setText(selected.turbineName); // 显示名称 + actvUnit.setText(selected.turbineName); checkAndSendUpdate(); } }); - // 监听叶片号选择变化 + radioGroup.setOnCheckedChangeListener((group, checkedId) -> { int currentBlade = 0; if (checkedId != -1) { @@ -499,7 +526,6 @@ public class FloatingWindowService extends Service { } private void checkAndSendUpdate() { - // 只有当两个值都有有效变化时才发送广播 if (!lastUnit.isEmpty() && lastBlade != -1) { sendUpdateBroadcast(); highlightChanges(); @@ -510,13 +536,12 @@ public class FloatingWindowService extends Service { Intent intent = new Intent("com.example.myapplication.UPDATE_VALUES"); intent.putExtra("unit", lastUnit); intent.putExtra("blade", lastBlade); - intent.putExtra("unitName", lastUnitName); // 发送机组名称 + intent.putExtra("unitName", lastUnitName); intent.setPackage(getPackageName()); sendBroadcast(intent); } private void highlightChanges() { - // 高亮效果 - 改变背景色然后恢复 int originalColor = actvUnit.getSolidColor(); actvUnit.setBackgroundColor(Color.YELLOW); radioGroup.setBackgroundColor(Color.YELLOW); @@ -524,7 +549,7 @@ public class FloatingWindowService extends Service { new Handler().postDelayed(() -> { actvUnit.setBackgroundColor(originalColor); radioGroup.setBackgroundColor(Color.TRANSPARENT); - }, 300); // 0.3秒后恢复 + }, 300); } @Override @@ -532,14 +557,26 @@ public class FloatingWindowService extends Service { super.onDestroy(); hideSoftInput(actvUnit); actvUnit.dismissDropDown(); - if (floatingView != null && windowManager != null) { - windowManager.removeView(floatingView); - } + isFloatingWindowShowing = false; + + // 直接调用移除方法,不再重复执行stopSelf() + removeFloatingView(); + mainHandler.removeCallbacksAndMessages(null); } - + private boolean isViewAttached() { + return floatingView != null + && floatingView.getWindowToken() != null + && windowManager != null; + } @Override public IBinder onBind(Intent intent) { return null; } + + public static void closeFloatingWindow(Context context) { + isFloatingWindowShowing = false; + Intent serviceIntent = new Intent(context, FloatingWindowService.class); + context.stopService(serviceIntent); + } } \ No newline at end of file diff --git a/MainActivity.java b/MainActivity.java index b304adc..121fb33 100644 --- a/MainActivity.java +++ b/MainActivity.java @@ -1,10 +1,6 @@ package com.example.myapplication; - - - import android.annotation.SuppressLint; -import android.app.Activity; import android.app.DatePickerDialog; import android.app.NotificationChannel; import android.app.NotificationManager; @@ -12,6 +8,7 @@ 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; @@ -20,12 +17,12 @@ import android.content.IntentFilter; import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.content.res.ColorStateList; -import android.database.ContentObserver; 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; @@ -34,16 +31,15 @@ import android.media.MediaRecorder; import android.net.ConnectivityManager; import android.net.NetworkCapabilities; import android.net.Uri; -import android.os.AsyncTask; 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.JsonReader; import android.util.Log; import android.util.SparseBooleanArray; @@ -55,6 +51,7 @@ 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; @@ -80,12 +77,10 @@ import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; import androidx.core.app.ActivityCompat; -import androidx.core.app.NotificationCompat; -import androidx.core.app.NotificationManagerCompat; import androidx.core.content.ContextCompat; import androidx.core.content.FileProvider; -import androidx.recyclerview.widget.GridLayoutManager; -import androidx.recyclerview.widget.RecyclerView; +import androidx.lifecycle.ViewModelStore; +import androidx.lifecycle.ViewModelStoreOwner; import androidx.room.Room; @@ -95,27 +90,28 @@ 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.CommonImageSourceFetcher; 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.api.PartService; 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.ImageInfo; -import com.example.myapplication.model.ImageSourceItem; 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.gson.JsonObject; -import com.google.gson.JsonParser; +import com.google.android.material.textfield.TextInputEditText; + +import org.json.JSONObject; import okhttp3.*; import retrofit2.Retrofit; @@ -128,12 +124,8 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; -import java.io.StringReader; -import java.io.UnsupportedEncodingException; -import java.net.URLEncoder; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; @@ -146,15 +138,12 @@ import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; -import java.util.Objects; import java.util.Set; -import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; -import java.util.regex.Matcher; -import java.util.regex.Pattern; +import java.util.concurrent.atomic.AtomicReference; import javax.crypto.Cipher; import javax.crypto.spec.SecretKeySpec; @@ -162,16 +151,25 @@ import javax.crypto.spec.SecretKeySpec; -public class MainActivity extends AppCompatActivity { +public class MainActivity extends AppCompatActivity implements ViewModelStoreOwner { + private ViewModelStore viewModelStore = new ViewModelStore(); private DynamicDataFetcher fetcher; private Spinner spinnerImageSource; private List> imageSources = new ArrayList<>(); private PartListFetcher partListFetcher; private List cachedPartList; private AlertDialog reportcurrentDialog; // 添加成员变量来保存当前对话框 + private boolean haveInitalFloating=false; private static String Temperator="0"; private static String Humidity="0"; private static String Weather="0"; + private TextInputEditText etWeather, etTemperature, etHumidity; + private Button btnSaveWeather; + private TextView tvWeatherDisplay, tvTemperatureDisplay, tvHumidityDisplay; + @Override + public ViewModelStore getViewModelStore() { + return viewModelStore; + } private static final int NOTIFICATION_PERMISSION_REQUEST_CODE = 102; private DatabaseHelper dbHelper; private long startTimestamp = 0; @@ -187,9 +185,10 @@ public class MainActivity extends AppCompatActivity { private Button btnUploadVideos2; private FloatingActionButton floatingActionButton; - private static boolean isRecording = false; + private boolean isRecording = false; private MediaRecorder mediaRecorder; private String currentAudioPath; + private String partid; private final OkHttpClient httpClient = new OkHttpClient.Builder() .connectTimeout(30, TimeUnit.SECONDS) // 连接超时 @@ -212,16 +211,14 @@ public class MainActivity extends AppCompatActivity { private long endTime = 0; private TextView statusText; - private TextView TempText; - private TextView WeatText; - private TextView HumText; - private ContentObserver contentObserver; + + private long lastPhotoId = -1; private boolean isMonitoring = false; private SwitchMaterial switchMonitor; - private static final int REQUEST_CODE_SELECT_PHOTOS = 1001; + private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.getDefault()); @@ -240,7 +237,7 @@ public class MainActivity extends AppCompatActivity { private static final int DECODE_SAMPLE_SIZE = 4; - private final ExecutorService executor = Executors.newFixedThreadPool(20); + private final ExecutorService executor = Executors.newFixedThreadPool(4); // 添加位置相关变量 private LocationManager locationManager; @@ -249,14 +246,12 @@ public class MainActivity extends AppCompatActivity { private static final int LOCATION_PERMISSION_REQUEST_CODE = 1002; private TextView locationText; - private Button btnSetUser ; private static final int AUDIO_PERMISSION_REQUEST_CODE = 1030; - private EditText etUserInfo; private Button btnUploadQueue; private Button btnStopUpload; private volatile boolean isUploading = false; private final Object uploadLock = new Object(); - private final Object monitoringLock = new Object(); + private EditText etUploadInterval; private Button btnSetInterval; private Button btnStartTimedUpload; @@ -267,53 +262,146 @@ public class MainActivity extends AppCompatActivity { private int uploadIntervalMinutes = 0; // 默认上传间隔(分钟) private boolean isTimedUploadRunning = false; private static final int REQUEST_VIDEO_PERMISSION = 1005; - private static final int REQUEST_CODE_SELECT_VIDEOS = 1004; // 在类变量声明部分添加 private ActivityResultLauncher videoPickerLauncher; private InputMethodManager inputMethodManager; - private ActivityResultLauncher requestPermissionLauncher; - private static final int REQUEST_CODE_OVERLAY = 101; 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 ="0"; - private String projectId="0"; - private String unit = "0"; - private String unitName = "0"; - private String imgpath ="0"; + 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 =0; + private int blade =-1; private Button changepassword; private Map, String> nameToIdMap; private String ChooseImageSource ="-1"; private ActivityResultLauncher overlayPermissionLauncher; + private SharedDataManager dataManager; + private String UserId="-1"; + private Button btnReadReport; + + private Button btnGenerateReport; + private ReportGenerator reportGeneratorreal; @SuppressLint("UnspecifiedRegisterReceiverFlag") @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); + + reportGeneratorreal = new ReportGenerator(this); + + btnGenerateReport = findViewById(R.id.btn_generate_report); + btnReadReport = findViewById(R.id.btn_read_report); + + btnGenerateReport.setOnClickListener(v -> { + + + String outputpath= reportGeneratorreal.generateReport(); + + + if (outputpath != null && !outputpath.startsWith("Error")) { + // 获取文件夹中的所有文件 + File folder = new File(outputpath); + if (folder.exists() && folder.isDirectory()) { + File[] files = folder.listFiles(); + if (files != null && files.length > 0) { + // 显示文件列表对话框 + showFileListDialog(files); + } else { + Toast.makeText(this, "文件夹为空,没有可显示的文件", Toast.LENGTH_SHORT).show(); + } + } else { + Toast.makeText(this, "路径无效或不是文件夹", Toast.LENGTH_SHORT).show(); + } + } else { + // 显示错误信息 + Toast.makeText(this, "报告生成失败: " + outputpath, Toast.LENGTH_LONG).show(); + } + + +// 检查路径是否有效 + // 检查路径是否有效 + + }); + String outputpath2 = getExternalFilesDir(null).getAbsolutePath(); + btnReadReport.setOnClickListener( v->{if (outputpath2 != null && !outputpath2.startsWith("Error")) { + // 获取文件夹中的所有文件 + File folder = new File(outputpath2); + if (folder.exists() && folder.isDirectory()) { + File[] files = folder.listFiles(); + if (files != null && files.length > 0) { + // 显示文件列表对话框 + showFileListDialog(files); + } else { + Toast.makeText(this, "文件夹为空,没有可显示的文件", Toast.LENGTH_SHORT).show(); + } + } else { + Toast.makeText(this, "路径无效或不是文件夹", Toast.LENGTH_SHORT).show(); + } + } else { + // 显示错误信息 + Toast.makeText(this, "报告生成失败: " + outputpath2, Toast.LENGTH_LONG).show(); + }} ); + dataManager= SharedDataManager.getInstance(); tvStatus = findViewById(R.id.tv_status); reportGenerator = new ReportGeneratorHelper(this); - TempText=findViewById(R.id.tem_text); - HumText=findViewById(R.id.hum_text); - WeatText=findViewById(R.id.weather_text); + 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(); - initFloatingActionButton(); setupButtonListeners(); setupTimedUpload(); + if(!haveInitalFloating) + initFloatingActionButton(); SharedPreferences sharedPreferences = getSharedPreferences("LoginPrefs", Context.MODE_PRIVATE); token=sharedPreferences.getString("auth_token", "-1"); - name=sharedPreferences.getString("name","-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); @@ -335,7 +423,6 @@ public class MainActivity extends AppCompatActivity { nameToIdMap.put(key, part.getPartId()); } - } catch (IOException e) { runOnUiThread(() -> showToast("网络错误: " + e.getMessage())); } catch (PartListFetcher.ApiException e) { @@ -357,10 +444,11 @@ public class MainActivity extends AppCompatActivity { String key = selectedItem.get("key"); ChooseImageSource=key; + dataManager.setChooseImageSource(ChooseImageSource); Toast.makeText(MainActivity.this, "选择了: " + value + " (Key: " + key + ")", Toast.LENGTH_SHORT).show(); - Log.d("ImageSource", "Selected: " + key + " - " + value); + } } @@ -369,7 +457,251 @@ public class MainActivity extends AppCompatActivity { // 未选择任何项 } }); + + } + // 在Activity中 + private void showFileListDialog(File[] files) { + // 构建文件名列表和文件对象列表 + List fileNames = new ArrayList<>(); + final List fileList = new ArrayList<>(); // 用于存储文件对象,保持与显示列表一致 + + for (File file : files) { + if (file.isFile()) { // 只显示文件,不显示子文件夹 + fileNames.add(file.getName()); + fileList.add(file); + } + } + + // 创建对话框 + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle("文件操作"); + + // 设置对话框选项 + String[] dialogItems = {"查看文件", "删除选中文件", "删除所有文件"}; + builder.setItems(dialogItems, (dialog, which) -> { + switch (which) { + case 0: // 查看文件 + showFileSelectionDialog(fileList, false); + break; + case 1: // 删除选中文件 + showMultiSelectDeleteDialog(fileList); + break; + case 2: // 删除所有文件 + showDeleteAllConfirmationDialog(fileList); + break; + } + }); + + builder.setNegativeButton("取消", null); + builder.show(); + } + + // 显示文件选择对话框(用于查看或单选删除) + private void showFileSelectionDialog(List fileList, boolean isDeleteMode) { + CharSequence[] items = new CharSequence[fileList.size()]; + for (int i = 0; i < fileList.size(); i++) { + items[i] = fileList.get(i).getName(); + } + + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(isDeleteMode ? "选择要删除的文件" : "选择要查看的文件"); + + builder.setItems(items, (dialog, which) -> { + File selectedFile = fileList.get(which); + if (isDeleteMode) { + showDeleteConfirmationDialog(selectedFile); + } else { + openFile(selectedFile.getAbsolutePath()); + } + }); + + builder.setNegativeButton("取消", null); + builder.show(); + } + + // 显示多选删除对话框 + private void showMultiSelectDeleteDialog(List fileList) { + if (fileList.isEmpty()) { + Toast.makeText(this, "没有可删除的文件", Toast.LENGTH_SHORT).show(); + return; + } + + CharSequence[] items = new CharSequence[fileList.size()]; + final boolean[] checkedItems = new boolean[fileList.size()]; + + for (int i = 0; i < fileList.size(); i++) { + items[i] = fileList.get(i).getName(); + checkedItems[i] = false; // 初始状态未选中 + } + + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle("选择要删除的文件"); + + builder.setMultiChoiceItems(items, checkedItems, (dialog, which, isChecked) -> { + checkedItems[which] = isChecked; + }); + + builder.setPositiveButton("删除选中", (dialog, which) -> { + int deleteCount = 0; + for (int i = 0; i < checkedItems.length; i++) { + if (checkedItems[i]) { + File fileToDelete = fileList.get(i); + if (fileToDelete.delete()) { + deleteCount++; + } + } + } + Toast.makeText(this, "已删除 " + deleteCount + " 个文件", Toast.LENGTH_SHORT).show(); + // 刷新文件列表 + refreshFileList(); + }); + + builder.setNegativeButton("取消", null); + builder.show(); + } + + // 显示删除所有文件确认对话框 + private void showDeleteAllConfirmationDialog(List fileList) { + if (fileList.isEmpty()) { + Toast.makeText(this, "没有可删除的文件", Toast.LENGTH_SHORT).show(); + return; + } + + new AlertDialog.Builder(this) + .setTitle("确认删除所有文件") + .setMessage("确定要删除所有 " + fileList.size() + " 个文件吗?此操作不可恢复!") + .setPositiveButton("删除全部", (dialog, which) -> { + int deleteCount = 0; + for (File file : fileList) { + if (file.delete()) { + deleteCount++; + } + } + Toast.makeText(this, "已删除 " + deleteCount + " 个文件", Toast.LENGTH_SHORT).show(); + // 刷新文件列表 + refreshFileList(); + }) + .setNegativeButton("取消", null) + .show(); + } + + // 显示单个文件删除确认对话框 + private void showDeleteConfirmationDialog(File fileToDelete) { + new AlertDialog.Builder(this) + .setTitle("确认删除") + .setMessage("确定要删除 " + fileToDelete.getName() + " 吗?") + .setPositiveButton("删除", (dialog, which) -> { + if (fileToDelete.delete()) { + Toast.makeText(this, "文件已删除", Toast.LENGTH_SHORT).show(); + // 刷新文件列表 + refreshFileList(); + } else { + Toast.makeText(this, "删除失败", Toast.LENGTH_SHORT).show(); + } + }) + .setNegativeButton("取消", null) + .show(); + } + + // 刷新文件列表 + private void refreshFileList() { + // 重新获取文件列表并刷新对话框 + File directory = getFilesDir(); // 或者您原来的文件目录 + File[] files = directory.listFiles(); + if (files != null) { + showFileListDialog(files); + } + } + + + private void openFile(String filePath) { + File file = new File(filePath); + if (!file.exists()) { + Toast.makeText(this, "文件不存在", Toast.LENGTH_SHORT).show(); + return; + } + + Intent intent = new Intent(Intent.ACTION_VIEW); + Uri uri = FileProvider.getUriForFile(this, getPackageName() + ".provider", file); + + // 根据文件类型设置 MIME 类型 + String mimeType = getMimeType(file.getName()); + if (mimeType == null) { + Toast.makeText(this, "无法识别文件类型", Toast.LENGTH_SHORT).show(); + return; + } + + intent.setDataAndType(uri, mimeType); + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + + try { + startActivity(intent); + } catch (ActivityNotFoundException e) { + Toast.makeText(this, "无法打开文件,请安装支持的应用程序", Toast.LENGTH_SHORT).show(); + } + } + + // 根据文件扩展名获取 MIME 类型 + + private void saveWeatherInfo() { + Weather = etWeather.getText().toString().trim(); + Temperator = etTemperature.getText().toString().trim(); + Humidity = etHumidity.getText().toString().trim(); + + // 简单验证(可选) + if (Weather.isEmpty()) { + Toast.makeText(this, "请输入天气", Toast.LENGTH_SHORT).show(); + return; + } + + // 保存到 SharedPreferences + SharedPreferences prefs = getSharedPreferences("WeatherPrefs", MODE_PRIVATE); + SharedPreferences.Editor editor = prefs.edit(); + editor.putString("weather", Weather); + editor.putString("temperature", Temperator); + editor.putString("humidity", Humidity); + editor.apply(); + + // 更新显示 + updateWeatherDisplay(); + + // 提示保存成功 + Toast.makeText(this, "天气信息已保存", Toast.LENGTH_SHORT).show(); + } + + private void updateWeatherDisplay() { + tvWeatherDisplay.setText("天气: " + (Weather.isEmpty() ? "未设置" : Weather)); + tvTemperatureDisplay.setText("温度: " + (Temperator.isEmpty() ? "未设置" : Temperator + "℃")); + tvHumidityDisplay.setText("湿度: " + (Humidity.isEmpty() ? "未设置" : Humidity + "%")); + } + private void startMonitoring() + { + String[] requiredPermissions = { + Manifest.permission.CAMERA, + Manifest.permission.FOREGROUND_SERVICE_CAMERA, + Manifest.permission.READ_EXTERNAL_STORAGE + }; + + if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED || + ContextCompat.checkSelfPermission(this, Manifest.permission.FOREGROUND_SERVICE_CAMERA) != PackageManager.PERMISSION_GRANTED) { + + ActivityCompat.requestPermissions(this, requiredPermissions, PERMISSION_REQUEST_StartMonitor_CODE); + } + else { + Intent serviceIntent = new Intent(this, PhotoMonitoringService.class); + serviceIntent.putExtra("command", "start_monitoring"); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + startForegroundService(serviceIntent); + statusText.setText("当前状态:正在监听相册变化"); + switchMonitor.setChecked(true); + } else { + startService(serviceIntent); + statusText.setText("当前状态:正在监听相册变化"); + switchMonitor.setChecked(true); + } + } + } private void loadImageSources() { fetcher.fetchDynamicDataAsync(new DynamicDataFetcher.DynamicDataCallback() { @Override @@ -517,7 +849,7 @@ public class MainActivity extends AppCompatActivity { String encryptedNewPassword = encryptPassword(username, newPassword); // 创建修改密码请求体 - LoginActivity.ChangePasswordRequest request = new LoginActivity.ChangePasswordRequest( + ChangePasswordRequest request = new ChangePasswordRequest( username, encryptedNewPassword, encryptedOldPassword @@ -673,19 +1005,7 @@ public class MainActivity extends AppCompatActivity { // 分享文件方法 - private void shareGeneratedFile(File file) { - Uri fileUri = FileProvider.getUriForFile(this, - getPackageName()+".provider", - file); - Intent shareIntent = new Intent(Intent.ACTION_SEND); - String mimeType = getMimeType(file.getAbsolutePath()); - shareIntent.setDataAndType(fileUri, mimeType); - shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - - - startActivity(Intent.createChooser(shareIntent, "分享报告")); - } // 删除文件方法 @@ -705,7 +1025,6 @@ public class MainActivity extends AppCompatActivity { // 从文件名中提取日期部分 String dateStr1 = f1.getName().substring(start.length(), start.length() + 10); String dateStr2 = f2.getName().substring(start.length(), start.length() + 10); - // 比较日期,返回逆序结果实现从大到小排序 return dateStr2.compareTo(dateStr1); }); @@ -876,10 +1195,62 @@ public class MainActivity extends AppCompatActivity { shareGeneratedFile(filesToShare.get(0)); } else { // 多个文件需要先压缩或使用其他方式分享 - Toast.makeText(this, "正在准备分享多个文件...", Toast.LENGTH_SHORT).show(); - // 这里可以添加多文件分享逻辑 + shareMultipleFiles(filesToShare); } } + + private void shareGeneratedFile(File file) { + try { + Uri fileUri = FileProvider.getUriForFile(this, + getApplicationContext().getPackageName() + ".provider", + file); + + Intent shareIntent = new Intent(Intent.ACTION_SEND); + shareIntent.setType(getMimeType(file.getAbsolutePath())); + shareIntent.putExtra(Intent.EXTRA_STREAM, fileUri); + shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + + // 验证是否有应用可以处理这个Intent + if (shareIntent.resolveActivity(getPackageManager()) != null) { + startActivity(Intent.createChooser(shareIntent, "分享报告")); + } else { + Toast.makeText(this, "没有找到可以处理此文件的应用程序", Toast.LENGTH_SHORT).show(); + } + } catch (IllegalArgumentException e) { + showErrorDialog("分享错误:",e.getMessage()); + e.printStackTrace(); + } + } + + private void shareMultipleFiles(List files) { + try { + ArrayList uris = new ArrayList<>(); + String packageName = getApplicationContext().getPackageName(); + + for (File file : files) { + Uri fileUri = FileProvider.getUriForFile(this, + packageName + ".provider", + file); + uris.add(fileUri); + } + + Intent shareIntent = new Intent(Intent.ACTION_SEND_MULTIPLE); + shareIntent.setType("*/*"); + shareIntent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris); + shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + + // 验证是否有应用可以处理这个Intent + if (shareIntent.resolveActivity(getPackageManager()) != null) { + startActivity(Intent.createChooser(shareIntent, "分享多个报告")); + } else { + Toast.makeText(this, "没有找到可以处理这些文件的应用程序", Toast.LENGTH_SHORT).show(); + } + } catch (IllegalArgumentException e) { + Toast.makeText(this, "分享文件失败: " + e.getMessage(), Toast.LENGTH_SHORT).show(); + e.printStackTrace(); + } + } + // 删除全部报告的方法 private void deleteAllReports(List reports, String start, String end) { int successCount = 0; @@ -899,9 +1270,14 @@ public class MainActivity extends AppCompatActivity { 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)) { @@ -1073,19 +1449,19 @@ public class MainActivity extends AppCompatActivity { .setTitle(title) .setMessage(message) .setPositiveButton("确定", null) - .setNegativeButton("复制错误", (dialog, which) -> { - // 将错误复制到剪贴板 - android.content.ClipboardManager clipboard = - (android.content.ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); - android.content.ClipData clip = - android.content.ClipData.newPlainText("错误信息", message); + .setNegativeButton("复制", (dialog, which) -> { + ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); + ClipData clip = ClipData.newPlainText("复制信息", message); clipboard.setPrimaryClip(clip); - showToast("已复制错误信息"); + // Optional: Show a toast to confirm the copy + Toast.makeText(this, "信息已复制", Toast.LENGTH_SHORT).show(); }) .show(); }); } + + // 获取Python错误详情(优化版) private String getPythonErrorDetails(PyException e) { try { @@ -1173,46 +1549,28 @@ public class MainActivity extends AppCompatActivity { mainHandler.post(() -> Toast.makeText(this, message, Toast.LENGTH_SHORT).show()); } - private String getMimeType(String filePath) { - String extension = filePath.substring(filePath.lastIndexOf(".") + 1).toLowerCase(); - switch (extension) { - case "pdf": - return "application/pdf"; - case "doc": - case "docx": - return "application/vnd.openxmlformats-officedocument.wordprocessingml.document"; - case "txt": - return "text/plain"; - default: - return "*/*"; // 通用类型 + 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 showPermissionExplanationDialog2() { - mainHandler.post(() -> { - new AlertDialog.Builder(this) - .setTitle("需要权限") - .setMessage("此功能需要存储权限以保存生成的文件") - .setPositiveButton("去授权", (d, w) -> - PermissionUtils.checkAndRequestPermissions(this)) - .setNegativeButton("取消", null) - .show(); - }); - } - - - - - - - - private void initializeViews() { - lastPhotoId = getLastPhotoId(); selectTimeButton = findViewById(R.id.selectTimeButton); startTimeText = findViewById(R.id.startTimeText); endTimeText = findViewById(R.id.endTimeText); @@ -1264,6 +1622,12 @@ public class MainActivity extends AppCompatActivity { 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); } }; @@ -1433,11 +1797,16 @@ public class MainActivity extends AppCompatActivity { }); findViewById(R.id.btnShowFloating).setOnClickListener(v -> { - if (checkOverlayPermission2()) { - startFloatingService(); - } - else { - BackgroundToast.show(MainActivity.this,"需要悬浮窗权限"); + if (FloatingWindowService.isFloatingWindowShowing) { + // 如果悬浮窗正在显示,则关闭它 + FloatingWindowService.closeFloatingWindow(MainActivity.this); + } else { + // 如果悬浮窗没有显示,则检查权限并启动 + if (checkOverlayPermission2()) { + startFloatingService(); + } else { + BackgroundToast.show(MainActivity.this, "需要悬浮窗权限"); + } } }); @@ -1487,12 +1856,22 @@ public class MainActivity extends AppCompatActivity { } private void showSuccessAudios() { new Thread(() -> { - List successAudios = db2.AudioDao().getAllAudios(); // 假设 AudioDao 中有 getSuccessAudios() 方法 + List successAudios = db2.AudioDao().getAllAudios(); runOnUiThread(() -> { - AlertDialog.Builder builder = new AlertDialog.Builder(this); + // 使用自定义主题的 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); @@ -1500,31 +1879,103 @@ public class MainActivity extends AppCompatActivity { return; } - List displayItems = new ArrayList<>(); + // 创建自定义适配器 + ArrayAdapter adapter = new ArrayAdapter(this, + R.layout.audio_item_layout, R.id.audio_item_text) { + @Override + public View getView(int position, View convertView, ViewGroup parent) { + View view = super.getView(position, convertView, parent); + TextView textView = view.findViewById(R.id.audio_item_text); + + // 设置item样式 + textView.setTextColor(ContextCompat.getColor(MainActivity.this, R.color.textColor)); + textView.setTextSize(16); + textView.setPadding(30, 30, 30, 30); + + return view; + } + }; + + // 添加数据到适配器 for (AudioEntity audio : successAudios) { - displayItems.add(String.format("录音路径: %s\n图片路径: %s\n时间: %s", - audio.getAudioPath(), - audio.getImagePath(), + adapter.add(String.format("录音名称: %s\n时间: %s", + getFileNameFromPath(audio.getAudioPath()), 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) -> { - // 可以添加点击单个项目的操作 - }); + builder.setAdapter(adapter, (dialog, which) -> { + // 点击item打开录音文件 + AudioEntity selectedAudio = successAudios.get(which); + openAudioFile(selectedAudio.getAudioPath()); + }); // 添加删除按钮 builder.setNegativeButton("一键删除", (dialog, which) -> deleteAllSuccessAudios(successAudios)); - builder.show(); + // 添加关闭按钮 + 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); @@ -1571,13 +2022,7 @@ executor.execute(()->{ uploadAudio(audio.ImagePath,audio.AudioPath,audio.time); - public void checkAndshowNotifation() - { - if (checkAndRequestNotificationPermission()) { - // 权限已授予,可以显示通知 - showNotification("准备录音..."); - } - } + public void updateValues(String unit, int blade,String unitName) { runOnUiThread(() -> { @@ -2020,18 +2465,9 @@ executor.execute(()->{ uploadAudio(audio.ImagePath,audio.AudioPath,audio.time); floatingActionButton.setOnClickListener(v -> { if (isRecording) { stopVoiceRecording(); - floatingActionButton.setImageResource(R.drawable.ic_mic_off); - showNotification("录音已停止"); } else { if (checkRecordPermission1()) { - currentAudioPath = startVoiceRecording(); - if (currentAudioPath != null) { - - isRecording = true; - floatingActionButton.setImageResource(R.drawable.ic_mic_on); - showNotification("录音中..."); - Toast.makeText(this,"开始录音了"+currentAudioPath,Toast.LENGTH_SHORT).show(); - } + startVoiceRecording(); } else { requestRecordPermission(); } @@ -2052,32 +2488,7 @@ executor.execute(()->{ uploadAudio(audio.ImagePath,audio.AudioPath,audio.time); } return true; } - private void showNotification(String message) { - // 确保通知渠道已创建(Android 8.0+ 必须) - createNotificationChannel(); - // 检查通知权限(Android 13+) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) - != PackageManager.PERMISSION_GRANTED) { - Toast.makeText(this, "请授予通知权限以显示录音状态", Toast.LENGTH_SHORT).show(); - return; // 不显示通知,但不崩溃 - } - } - - try { - NotificationCompat.Builder builder = new NotificationCompat.Builder(this, "recording_channel") - .setSmallIcon(R.drawable.ic_mic_on) // 确保图标有效 - .setContentTitle("录音状态") - .setContentText(message) - .setPriority(NotificationCompat.PRIORITY_LOW); - - NotificationManagerCompat.from(this).notify(1, builder.build()); - } catch (Exception e) { - e.printStackTrace(); - Toast.makeText(this, "通知显示失败: " + e.getMessage(), Toast.LENGTH_SHORT).show(); - } - } private void createNotificationChannel() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { NotificationChannel channel = new NotificationChannel( @@ -2108,8 +2519,7 @@ executor.execute(()->{ uploadAudio(audio.ImagePath,audio.AudioPath,audio.time); super.onActivityResult(requestCode, resultCode, data); if (requestCode == OVERLAY_PERMISSION_REQUEST_CODE) { if (checkOverlayPermission()) { - // Permission granted, initialize floating button - initFloatingActionButton(); + } else { Toast.makeText(this, "悬浮窗权限被拒绝,无法显示悬浮球", Toast.LENGTH_SHORT).show(); } @@ -2125,64 +2535,29 @@ executor.execute(()->{ uploadAudio(audio.ImagePath,audio.AudioPath,audio.time); new String[]{Manifest.permission.RECORD_AUDIO}, AUDIO_PERMISSION_REQUEST_CODE); } - private String startVoiceRecording() { - if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) - != PackageManager.PERMISSION_GRANTED) { - return null; - } - - // 启动前台服务 + 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); } - String audioFilePath = Objects.requireNonNull(getExternalCacheDir()).getAbsolutePath() - + "/" + System.currentTimeMillis() + ".3gp"; - - try { - mediaRecorder = new MediaRecorder(); - mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); - mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP); - mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); - mediaRecorder.setOutputFile(audioFilePath); - // 添加异常捕获 - try { - mediaRecorder.prepare(); - } catch (IOException e) { - Toast.makeText(this,"AudioRecordprepare() failed: " + e.getMessage(),Toast.LENGTH_SHORT).show(); - return null; - } - mediaRecorder.start(); - return audioFilePath; - } catch (Exception e) { - e.printStackTrace(); - Toast.makeText(this, "录音初始化失败: " + e.getMessage(), Toast.LENGTH_SHORT).show(); - return null; - } + isRecording = true; + dataManager.setIsRecording(isRecording); + floatingActionButton.setImageResource(R.drawable.ic_mic_on); } private void stopVoiceRecording() { - if (mediaRecorder != null) { - try { - mediaRecorder.stop(); - mediaRecorder.release(); - mediaRecorder = null; - isRecording = false; - // 停止服务 - stopService(new Intent(this, RecordingService.class)); - BackgroundToast.show(this, "录音已保存"+currentAudioPath); -executor.execute(()->{ uploadAudio(imgpath,currentAudioPath,currentUsername); - currentAudioPath=null;}); + Intent serviceIntent = new Intent(this, RecordingService.class); + serviceIntent.setAction("stop_recording"); + startService(serviceIntent); - } catch (IllegalStateException e) { - e.printStackTrace(); - } - } + isRecording = false; + dataManager.setIsRecording(isRecording); + floatingActionButton.setImageResource(R.drawable.ic_mic_off); } private String getPartName(int b) @@ -2288,10 +2663,6 @@ private String getPartName(int b) count++; if (thumbnail != null) { - - - - lastPhotoId = Math.max(lastPhotoId, id); @@ -2301,21 +2672,20 @@ private String getPartName(int b) String finalproject=getProject(); String finalunit=getUnit(); String partname=getPartName(blade); - - String partid=getPartIdByNameFast(unit,partname); if(partid==null) partid=partname; // 使用另一个线程执行上传或保存操作 String finalPartid = partid; + String finalPartid1 = partid; Executors.newSingleThreadExecutor().execute(() -> { if (isNetworkAvailable()) { - uploadPhoto(path, time, lastKnownLocation, user, "0",finalproject,finalunit,blade, finalPartid,ChooseImageSource,unitName); + 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)); + finalAltitude, user, "0",finalproject,finalunit,blade,false,unitName,Temperator,Humidity,Weather,ChooseImageSource, finalPartid1)); } }); } @@ -2495,12 +2865,6 @@ private String getPartName(int b) } - - - - - - // 设置上传间隔 private void setUploadInterval() { try { @@ -2583,66 +2947,105 @@ private String getPartName(int b) } - 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) { + 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 { - MultipartBody.Builder builder = new MultipartBody.Builder() - .setType(MultipartBody.FORM) - .addFormDataPart("file", file.getName(), - RequestBody.create(file, MediaType.parse("image/*"))) - .addFormDataPart("time", String.valueOf(time)) - .addFormDataPart("latitude", String.valueOf(latitude)) - .addFormDataPart("longitude", String.valueOf(longitude)) - .addFormDataPart("altitude", String.valueOf(altitude)) - .addFormDataPart("uploadUser", user) - .addFormDataPart("projectId",project) - .addFormDataPart("turbineId",unit) - .addFormDataPart("partId",partId); + // 构建请求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 = builder.build(); + // 创建请求体 + 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("http://pms.dtyx.net:9158/common/upload-image/file") - .post(requestBody) - .addHeader("Authorization",token) - .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()) { - BackgroundToast.show( - MainActivity.this, - "上传成功: " + response.code() + filePath - ); - executor.execute(() -> { + 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); + } - db.imageDao().insert(new ImageEntity(filePath, time, - latitude, longitude, altitude, user, audioPath, project, unit, blade, true,unitName,Temperator,Humidity,Weather,imageSource));}); - } else { - BackgroundToast.show(MainActivity.this, - "上传失败: " + response.message() - ); + 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 (IOException e) { + } catch (Exception e) { if (isUploading) { // 只有主动上传时才重新加入队列 - db2.imageDao().insert(new ImageEntity(filePath, time, - latitude, longitude, altitude, user, audioPath, project, unit, blade, false,unitName,Temperator,Humidity,Weather,imageSource)); + 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) { // 这里会中断当前上传 @@ -2679,7 +3082,7 @@ private String getPartName(int b) if (lastLocation != null) { lastKnownLocation = lastLocation; updateLocationText(lastLocation); - Log.d("LocationUpdate", "最新已知位置: " + locationToString(lastLocation)); + dataManager.setLocation(lastLocation); } } catch (SecurityException e) { e.printStackTrace(); @@ -2695,7 +3098,7 @@ private String getPartName(int b) "位置更新: " + location.getLatitude() + ", " + location.getLongitude(), Toast.LENGTH_SHORT).show(); // 打印到Logcat - Log.d("LocationUpdate", "新位置: " + locationToString(location)); + dataManager.setLocation(lastKnownLocation); updateLocationText(lastKnownLocation); } @@ -2754,13 +3157,24 @@ private String getPartName(int b) @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); - - if (requestCode == PERMISSIONS_REQUEST_CODE) { + 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; } } @@ -2768,6 +3182,8 @@ private String getPartName(int b) if (allPermissionsGranted) { // 所有权限都已授予,继续初始化位置管理器 initLocationManager(); + haveInitalFloating=true; + initFloatingActionButton(); } else { // 处理权限被拒绝的情况 handlePermissionDenial(); @@ -2793,10 +3209,10 @@ private String getPartName(int b) } + //通知权限 else if (requestCode == NOTIFICATION_PERMISSION_REQUEST_CODE) { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - showNotification("通知权限已授予"); - initFloatingActionButton(); + BackgroundToast.show(this,"通知权限已开启"); } else { Toast.makeText(this, "通知权限被拒绝,无法显示录音状态", Toast.LENGTH_SHORT).show(); } @@ -2824,6 +3240,7 @@ private String getPartName(int b) Toast.makeText(this, "需要录音权限才能录制语音", Toast.LENGTH_SHORT).show(); } } + //图片权限 else if (requestCode == PERMISSION_REQUEST_CODE) { boolean allGranted=true; @@ -2836,8 +3253,7 @@ private String getPartName(int b) if (allGranted) { Toast.makeText(this, "媒体权限已授与", Toast.LENGTH_SHORT).show(); - checkAndshowNotifation(); - lastPhotoId=getLastPhotoId(); + startMonitoring(); } else { switchMonitor.setChecked(false); @@ -2931,7 +3347,12 @@ private String getPartName(int b) } if (checkAnyPermissionGranted(permissions)) { - startMonitoring(); + try{startMonitoring();} + catch (Exception e) + { + dataManager.setOthermsg(e.toString()); + BackgroundToast.show(this,"监听失败"); + } } else { requestPermissions(permissions); } @@ -3269,7 +3690,8 @@ private String getPartName(int b) "参与人名: %s\n"+ "项目名字:%s\n"+ "机组号:%s\n"+ - "叶片号:%d\n", + "叶片号:%d\n"+ + "图片来源:%s\n", sdf.format(new Date(item.time)), // 时间 item.path, // 路径 @@ -3279,7 +3701,8 @@ private String getPartName(int b) item.user,// 用户信息 projectName, item.unitName, - item.blade + item.blade, + item.imageSource )); itemView.setOnClickListener(v -> openFullImage(item.path)); @@ -3336,56 +3759,6 @@ private String getPartName(int b) currentDialog2.dismiss(); } } - - - private void startMonitoring() { - synchronized (monitoringLock) { - if (isMonitoring) return; - - if (isNetworkAvailable()) { - executor.execute(() -> { - // 使用上传锁保护数据库操作 - // 在后台线程中执行数据库操作 - List history = db2.imageDao().getAll(); - - - if (history != null && !history.isEmpty()) { - synchronized (uploadLock) { - for (ImageEntity image : history) { - String partname=getPartName(image.blade); - String partid=getPartIdByNameFast(unit,partname); - if(partid==null) - partid=partname; - uploadPhoto(image.path, image.time, lastKnownLocation, image.user, image.audioPath,image.project,image.unit,image.blade,partid,image.imageSource,image.unitName); - } - } - } - - // 删除所有记录(仍在后台线程) - db2.imageDao().deleteAll(); - - }); - } - - // 创建内容观察者 - Uri uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; - - contentObserver = new ContentObserver(new Handler()) { - @SuppressLint("SuspiciousIndentation") - @Override - public void onChange(boolean selfChange) { - super.onChange(selfChange); - if(!isRecording) - checkNewPhotos(); - } - }; - - // 注册观察者 - getContentResolver().registerContentObserver(uri, true, contentObserver); - isMonitoring = true; - runOnUiThread(() -> statusText.setText("当前状态:正在监听相册变化")); - } - } private boolean isNetworkAvailable() { ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); if (cm == null) return false; @@ -3397,15 +3770,10 @@ private String getPartName(int b) capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)); } private void stopMonitoring() { - if (!isMonitoring) return; - - // 注销观察者 - if (contentObserver != null) { - getContentResolver().unregisterContentObserver(contentObserver); - contentObserver = null; - } - isMonitoring = false; + Intent serviceIntent = new Intent(this, PhotoMonitoringService.class); + stopService(serviceIntent); statusText.setText("当前状态:未监听"); + switchMonitor.setChecked(false); } private void startUploadQueue() { if (isUploading) { @@ -3415,6 +3783,7 @@ private String getPartName(int b) executor.execute(() -> { isUploading = true; + dataManager.setUploading(isUploading); runOnUiThread(() -> { btnUploadQueue.setEnabled(false); btnStopUpload.setEnabled(true); @@ -3431,7 +3800,7 @@ private String getPartName(int b) for (ImageEntity image : queue) { if (!isUploading) break; String partname=getPartName(image.blade); - String partid=getPartIdByNameFast(unit,partname); + if(partid==null) partid=partname; synchronized (uploadLock) { @@ -3459,6 +3828,7 @@ private String getPartName(int b) statusText.setText(status); }); isUploading = false; + dataManager.setUploading(isUploading); } }); } @@ -3489,158 +3859,6 @@ private String getPartName(int b) } - // - private void checkNewPhotos() { - - String[] projection = { - MediaStore.Images.Media._ID, - MediaStore.Images.Media.DATA, - MediaStore.Images.Media.DATE_ADDED, - MediaStore.Images.Media.DATE_TAKEN, // 拍摄时间(相机照片才有) - MediaStore.Images.Media.LATITUDE, - MediaStore.Images.Media.LONGITUDE - }; - -// 查询条件:ID > lastPhotoId 且 DATE_TAKEN > 0(确保是相机拍摄的照片) - String selection = MediaStore.Images.Media._ID + " > ? AND " + - MediaStore.Images.Media.DATE_TAKEN + " > 0 AND " + - "(" + MediaStore.Images.Media.DATA + " LIKE '%/DCIM/%' OR " + - MediaStore.Images.Media.DATA + " LIKE '%/Camera/%')"; - String[] selectionArgs = new String[]{ - String.valueOf(lastPhotoId), - - }; - Cursor cursor = getContentResolver().query( - MediaStore.Images.Media.EXTERNAL_CONTENT_URI, - projection, - selection, - selectionArgs, - MediaStore.Images.Media._ID + " DESC" - ); - - if (cursor != null) { - while (cursor.moveToNext()) { - long id = cursor.getLong(0); - String path = cursor.getString(1); - imgpath=path; - long time = cursor.getLong(2) * 1000; - double latitude = cursor.getDouble(3); - double longitude = cursor.getDouble(4); - double altitude = 0.0; // 默认值 - if(locationManager!=null) { - if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) { - - break; - } - else { - Location lastLocation = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER); - if (lastLocation != null) { - lastKnownLocation = lastLocation; - updateLocationText(lastLocation); - } - } - - } - 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) { - altitude = Double.parseDouble(altitudeStr); - } - } catch (Exception e) { - e.printStackTrace(); - } - - String user = getCurrentUser(); - Log.e("用户是", user); - - // 生成缩略图 - Bitmap bitmap = null; - try { - bitmap = BitmapFactory.decodeFile(path); - } catch (Exception e) { - e.printStackTrace(); - } - - if (bitmap != null) { - Bitmap thumbnail = Bitmap.createScaledBitmap(bitmap, THUMBNAIL_SIZE, THUMBNAIL_SIZE, true); - String audioPath = currentAudioPath ==null?"0":currentAudioPath; - ImageInfo imageInfo = new ImageInfo(path, time, thumbnail, latitude, longitude, altitude, user, audioPath); - - // 保护imageList和lastPhotoId的修改 - - lastPhotoId = Math.max(lastPhotoId, id); - - - double finalAltitude = altitude; - double finalLongitude = longitude; - double finalLatitude = latitude; - - if(isNetworkAvailable()&&!isRecording) { - - executor.execute(() -> { - List history = db2.imageDao().getAll(); - - if (history != null && !history.isEmpty()) { - - synchronized (uploadLock) { - for (ImageEntity image : history) { - String partname=getPartName(image.blade); - String partid=getPartIdByNameFast(unit,partname); - if(partid==null) - partid=partname; - uploadPhoto(image.path, image.time, lastKnownLocation, - image.user, image.audioPath, image.project, - image.unit, image.blade,partid,image.imageSource,image.unitName); - } - } - } - db2.imageDao().deleteAll(); - }); - executor.execute(() -> { - String partname=getPartName(blade); - String partid=getPartIdByNameFast(unit,partname); - if(partid==null) - partid=partname; - - uploadPhoto(path, time, lastKnownLocation, user, - audioPath, projectId, unit, blade,partid,ChooseImageSource,unitName); - }); - - } - else { - String partname=getPartName(blade); - String partid=getPartIdByNameFast(unit,partname); - if(partid==null) - partid=partname; - Log.e("partid:************",partid); - BackgroundToast.show(MainActivity.this,"没有网路"); - executor.execute(() -> { - db2.imageDao().insert(new ImageEntity(path, time,finalLatitude,finalLongitude,finalAltitude, user, audioPath,projectId,unit,blade,false,unitName,Temperator,Humidity,Weather,ChooseImageSource)); - - }); - } - - }; - - - currentAudioPath=null; - } - cursor.close(); - } - - - - } - - private String getCurrentUser() { String user = name; // 如果用户没有输入信息,则使用设备型号作为默认值 @@ -3676,273 +3894,8 @@ private String getPartName(int b) } - private void uploadPhoto(String filePath, long time, Location lastKnownLocation, String user, - String audioPath, String project, String unit, int blade, String partid,String imageSource,String unitName) { - synchronized (uploadLock) { - if (isUploading) { - // 如果正在上传,直接加入队列 - double latitude; - double longitude; - double altitude; - if (lastKnownLocation != null) { - latitude = lastKnownLocation.getLatitude(); - longitude = lastKnownLocation.getLongitude(); - altitude = lastKnownLocation.getAltitude(); - } else { - altitude = 0; - longitude = 0; - latitude = 0; - } - executor.execute(() -> { - db2.imageDao().insert(new ImageEntity(filePath, time, latitude, longitude, altitude, - user, audioPath, project, unit, blade, false, unitName, Temperator, Humidity, Weather,ChooseImageSource)); - }); - return; - } - } - - // 在后台线程执行上传逻辑 - executor.execute(() -> { - File imageFile = new File(filePath); - - double latitude; - double longitude; - double altitude; - if (lastKnownLocation != null) { - latitude = lastKnownLocation.getLatitude(); - longitude = lastKnownLocation.getLongitude(); - altitude = lastKnownLocation.getAltitude(); - } else { - altitude = 0; - longitude = 0; - latitude = 0; - } - - // 构建 Multipart 请求体 - MultipartBody.Builder builder = new MultipartBody.Builder() - .setType(MultipartBody.FORM) - .addFormDataPart("image", imageFile.getName(), - RequestBody.create(imageFile, MediaType.parse("image/*"))) - .addFormDataPart("time", String.valueOf(time)) - .addFormDataPart("latitude", String.valueOf(latitude)) - .addFormDataPart("longitude", String.valueOf(longitude)) - .addFormDataPart("altitude", String.valueOf(altitude)) - .addFormDataPart("uploadUser", user) - .addFormDataPart("projectId", project) - .addFormDataPart("turbineId", unit) - .addFormDataPart("partId", partid) - .addFormDataPart("imageSource", imageSource); - RequestBody requestBody = builder.build(); - - // 构建请求 - Request request = new Request.Builder() - .url("http://pms.dtyx.net:9158/common/upload-image/file") - .post(requestBody) - .addHeader("Authorization", token) - .build(); - - // 使用 try-with-resources 确保 Response 关闭 - try (Response response = httpClient.newCall(request).execute()) { - if (response.isSuccessful()) { - // 上传成功 - db.imageDao().insert(new ImageEntity(filePath, time, latitude, longitude, altitude, - user, audioPath, project, unit, blade, true, unitName, Temperator, Humidity, Weather,ChooseImageSource)); - BackgroundToast.show(MainActivity.this, "上传成功: " + filePath); - } else { - // 上传失败(HTTP 状态码非 2xx) - db2.imageDao().insert(new ImageEntity(filePath, time, latitude, longitude, altitude, - user, audioPath, project, unit, blade, false, unitName, Temperator, Humidity, Weather,ChooseImageSource)); - BackgroundToast.show(MainActivity.this, "上传失败: " + response.code() + " " + response.message() + ",已存入缓冲队列"); - } - } catch (IOException e) { - // 网络异常 - db2.imageDao().insert(new ImageEntity(filePath, time, latitude, longitude, altitude, - user, audioPath, project, unit, blade, false, unitName, Temperator, Humidity, Weather,ChooseImageSource)); - BackgroundToast.show(MainActivity.this, "上传失败: " + e.getMessage() + ",已存入缓冲队列"); - } - }); - } -// private void uploadPhoto(String filePath, long time, Location lastKnownLocation, String user, String audioPath, String project, String unit, int blade,String partid) { -// synchronized (uploadLock) { -// if (isUploading) { -// // 如果正在执行队列上传,则直接加入队列不单独上传 -// double latitude = 0; -// double longitude = 0; -// double altitude = 0; -// if (lastKnownLocation != null) { -// latitude = lastKnownLocation.getLatitude(); -// longitude = lastKnownLocation.getLongitude(); -// altitude = lastKnownLocation.getAltitude(); -// } -// -// db2.imageDao().insert(new ImageEntity(filePath, time, latitude, longitude, altitude, user, audioPath, project, unit, blade, false,unitName,Temperator,Humidity,Weather)); -// return; -// } -// } -// -// File imageFile = new File(filePath); -// -// double latitude; -// double longitude; -// double altitude; -// if (lastKnownLocation != null) { -// latitude = lastKnownLocation.getLatitude(); -// longitude = lastKnownLocation.getLongitude(); -// altitude = lastKnownLocation.getAltitude(); -// } else { -// altitude = 0; -// longitude = 0; -// latitude = 0; -// } -// -// // 创建包含图片文件、录音文件和元数据的请求体 -// MultipartBody.Builder builder = new MultipartBody.Builder() -// .setType(MultipartBody.FORM) -// .addFormDataPart("image", imageFile.getName(), -// RequestBody.create(imageFile, MediaType.parse("image/*"))) -// .addFormDataPart("time", String.valueOf(time)) -// .addFormDataPart("latitude", String.valueOf(latitude)) -// .addFormDataPart("longitude", String.valueOf(longitude)) -// .addFormDataPart("altitude", String.valueOf(altitude)) -// .addFormDataPart("uploadUser", user) -// .addFormDataPart("projectId", project) -// .addFormDataPart("turbineId", unit) -// .addFormDataPart("partId", partid); -// -// -// -// -// RequestBody requestBody = builder.build(); -// -// // 构建请求 -// Request request = new Request.Builder() -// .url("http://pms.dtyx.net:9158/common/upload-image/file") // 修改为你的实际URL -// .post(requestBody) -// .addHeader("Authorization",token) -// .build(); -// -// httpClient.newCall(request).enqueue(new Callback() { -// @Override -// public void onFailure(Call call, IOException e) { -// -// BackgroundToast.show( MainActivity.this, -// "上传失败: " + e.getMessage() + "已存入缓冲队列" -// ); -// executor.execute(() -> { -// db2.imageDao().insert(new ImageEntity(filePath, time, latitude, longitude, altitude, user, audioPath, project, unit, blade, false,unitName,Temperator,Humidity,Weather)); -// }); -// } -// -// @Override -// public void onResponse(Call call, Response response) { -// runOnUiThread(() -> { -// if (response.isSuccessful()) { -// 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)); -// -// -// }); -// -// -// -// } else { -// BackgroundToast.show( -// MainActivity.this, -// "上传失败: " + response.code() +response.message()+ "已存入缓冲队列" -// ); -// executor.execute(() -> { -// db2.imageDao().insert(new ImageEntity(filePath, time, latitude, longitude, altitude, user, audioPath, project, unit, blade, false,unitName,Temperator,Humidity,Weather)); -// }); -// } -// }); -// } -// }); -// } -// private void uploadAudio(String imageId, String audioPath, String token) { -// if (audioPath == null || audioPath.equals("0")) { -// BackgroundToast.show(MainActivity.this,"文件路径为空"); -// -// } -// -// File audioFile = new File(audioPath); -// if (!audioFile.exists()) { -// BackgroundToast.show(MainActivity.this,"文件不存在"); -// -// } -// -// RequestBody requestBody = new MultipartBody.Builder() -// .setType(MultipartBody.FORM) -// .addFormDataPart("file", audioFile.getName(), -// RequestBody.create(audioFile, MediaType.parse("audio/*"))) -// .build(); -// -// Request.Builder requestBuilder = new Request.Builder() -// .url( "http://pms.dtyx.net:9158/audio/upload/" + imageId) -// .addHeader("Authorization",token) -// .post(requestBody); -// -// if (token != null && !token.isEmpty()) { -// requestBuilder.addHeader("Authorization", token); -// } -// -// httpClient.newCall(requestBuilder.build()).enqueue(new Callback() { -// @Override -// public void onFailure(Call call, IOException e) { -// -// BackgroundToast.show( -// MainActivity.this, -// "录音上传失败: " + e.getMessage() -// ); -// String currentTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()) -// .format(new Date()); -// -// executor.execute(() -> { -// db.AudioDao().insert(new AudioEntity(audioPath,imageId,currentTime)); -// }); -// -// } -// -// @Override -// public void onResponse(Call call, Response response) { -// // Handle response -// if (!response.isSuccessful()) { -// // Handle upload failure -// -// BackgroundToast.show( -// MainActivity.this, -// "录音上传失败: " + response.code() + response.message() -// ); -// String currentTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()) -// .format(new Date()); -// -// executor.execute(() -> { -// db.AudioDao().insert(new AudioEntity(audioPath,imageId,currentTime)); -// }); -// } -// else { -// BackgroundToast.show( -// MainActivity.this, -// "录音上传成功: " + response.code() + imageId -// ); -// String currentTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()) -// .format(new Date()); -// executor.execute(() -> { -// db.AudioDao().delete(new AudioEntity(audioPath,imageId,currentTime)); -// }); -// executor.execute(() -> { -// db2.AudioDao().insert(new AudioEntity(audioPath,imageId,currentTime)); -// }); -// -// } -// response.close(); -// } -// }); -// } private void uploadAudio(String imageId, String audioPath, String token) { // 1. 参数校验(在调用线程执行,避免不必要的后台任务) if (audioPath == null || audioPath.equals("0")) { @@ -3981,12 +3934,12 @@ private void uploadAudio(String imageId, String audioPath, String token) { if (response.isSuccessful()) { // 上传成功 - db.AudioDao().delete(new AudioEntity(audioPath, imageId, currentTime)); - db2.AudioDao().insert(new AudioEntity(audioPath, imageId, currentTime)); + 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)); + db.AudioDao().insert(new AudioEntity(audioPath, imageId, currentTime,false)); BackgroundToast.show(MainActivity.this, "录音上传失败: " + response.code() + " " + response.message()); } @@ -3994,7 +3947,7 @@ private void uploadAudio(String imageId, String audioPath, String token) { // 网络异常 String currentTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()) .format(new Date()); - db.AudioDao().insert(new AudioEntity(audioPath, imageId, currentTime)); + db.AudioDao().insert(new AudioEntity(audioPath, imageId, currentTime,false)); BackgroundToast.show(MainActivity.this, "录音上传失败: " + e.getMessage()); } }); diff --git a/PhotoMonitoringService.java b/PhotoMonitoringService.java index f53777e..41044a8 100644 --- a/PhotoMonitoringService.java +++ b/PhotoMonitoringService.java @@ -1,5 +1,4 @@ package com.example.myapplication.Service; - import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; @@ -7,6 +6,7 @@ import android.app.PendingIntent; import android.app.Service; import android.content.Context; import android.content.Intent; +import android.content.SharedPreferences; import android.database.ContentObserver; import android.database.Cursor; import android.location.Location; @@ -23,6 +23,7 @@ import android.provider.MediaStore; import android.util.Log; import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; import androidx.core.app.NotificationCompat; import androidx.room.Room; @@ -33,6 +34,8 @@ import com.example.myapplication.Tool.NotificationReceiver; import com.example.myapplication.model.ImageEntity; import com.example.myapplication.model.SharedDataManager; +import org.json.JSONObject; + import java.io.File; import java.io.IOException; import java.util.List; @@ -40,6 +43,7 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; +import okhttp3.HttpUrl; import okhttp3.MediaType; import okhttp3.MultipartBody; import okhttp3.OkHttpClient; @@ -55,17 +59,19 @@ public class PhotoMonitoringService extends Service implements SharedDataManager // 锁对象 private final Object monitoringLock = new Object(); private final Object uploadLock = new Object(); + private long lastSuccessToastTime = 0; + private static final long TOAST_THROTTLE_INTERVAL = 1500; // 状态标志 private boolean isMonitoring = false; private boolean isUploading = false; private boolean isRecording = false; private int successCount = 0; - private int failureCount = 0; + private int totalUploadedCount = 0; // 新增:总上传成功计数 // 组件 private ContentObserver contentObserver; - private final ExecutorService executor = Executors.newFixedThreadPool(6); + private final ExecutorService executor = Executors.newFixedThreadPool(5); private final OkHttpClient httpClient = new OkHttpClient.Builder() .connectTimeout(30, TimeUnit.SECONDS) // 连接超时 .readTimeout(60, TimeUnit.SECONDS) // 读取超时 @@ -94,85 +100,94 @@ public class PhotoMonitoringService extends Service implements SharedDataManager // 其他状态 private long lastPhotoId = 0; - private String currentAudioPath="0"; + private String currentAudioPath="-1"; private String imgpath="-1"; private String partid="-1"; + private String UserId; - // 回调接口 - private MonitoringCallback callback; @Override - public void onImagePathChanged(String newPath) { + public void onImageIdChanged(String newId) { } @Override public void onTokenChanged(String newToken) { - } @Override public void onUserChanged(String newUser) { - user=newUser; + user=newUser; } @Override public void onAudioPathChanged(String newAudioPath) { - currentAudioPath=newAudioPath; + currentAudioPath=newAudioPath; } @Override public void onProjectIdChanged(String newProjectId) { - projectId=newProjectId; + projectId=newProjectId; } @Override public void onUnitChanged(String newUnit) { - unit=newUnit; + unit=newUnit; } @Override public void onBladeChanged(int newBlade) { - blade=newBlade; + blade=newBlade; } @Override public void onPartidChanged(String newPartid) { - partid=newPartid; + partid=newPartid; } @Override public void onChooseImageSourceChanged(String newChooseImageSource) { - ChooseImageSource=newChooseImageSource; + ChooseImageSource=newChooseImageSource; } @Override public void onUnitNameChanged(String newUnitName) { - unitName=newUnitName; + unitName=newUnitName; } @Override public void onIsRecordingChanged(boolean newisRecording) { - isRecording=newisRecording; + isRecording=newisRecording; } @Override public void onUploadingStatusChanged(boolean isUploading) { - this.isUploading=isUploading; + this.isUploading=isUploading; } - public interface MonitoringCallback { - void onUploadSuccess(String filePath); - void onUploadFailure(String filePath, String error); - void onStatusChanged(boolean isMonitoring); + @Override + public void onLocationChanged(Location newLocation) { + lastKnownLocation=newLocation; } - SharedDataManager dataManager ; + + @Override + public void onOtherMsgChanged(String msg) { + + } + + SharedDataManager dataManager; + @Override public void onCreate() { super.onCreate(); createNotificationChannel(); - + SharedPreferences prefs = getSharedPreferences("WeatherPrefs", MODE_PRIVATE); + Weather = prefs.getString("weather", "0"); + Temperator = prefs.getString("temperature", "0"); + Humidity = prefs.getString("humidity", "0"); + SharedPreferences sharedPreferences = getSharedPreferences("LoginPrefs", Context.MODE_PRIVATE); // 初始化数据库 + UserId=sharedPreferences.getString("userid","-1"); db = Room.databaseBuilder(getApplicationContext(), AppDatabase.class, "image-database").build(); db2 = Room.databaseBuilder(getApplicationContext(), @@ -180,13 +195,20 @@ public class PhotoMonitoringService extends Service implements SharedDataManager // 初始化位置管理器 locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE); -dataManager=SharedDataManager.getInstance(); + dataManager=SharedDataManager.getInstance(); dataManager.addListener(this); updateFromSharedData(); + + // 初始化时从数据库获取已上传成功的数量 + + totalUploadedCount = 0; + updateNotification("partid"+partid); + } + private void updateFromSharedData() { - dataManager = SharedDataManager.getInstance(); + dataManager = SharedDataManager.getInstance(); this.token = dataManager.getToken(); this.user = dataManager.getUser(); this.currentAudioPath = dataManager.getAudioPath(); @@ -197,10 +219,11 @@ dataManager=SharedDataManager.getInstance(); this.unitName = dataManager.getUnitName(); this.partid=dataManager.getPartid(); this.isUploading=dataManager.getisUploading(); + this.lastKnownLocation=dataManager.getLocation(); } + @Override public int onStartCommand(Intent intent, int flags, int startId) { - // 从intent中获取配置参数 if (intent != null && "start_monitoring".equals(intent.getStringExtra("command"))) { startMonitoring(); } @@ -222,9 +245,7 @@ dataManager=SharedDataManager.getInstance(); super.onDestroy(); } - // 公共接口方法 public void startMonitoring() { - synchronized (monitoringLock) { if (isMonitoring) return; @@ -247,11 +268,7 @@ dataManager=SharedDataManager.getInstance(); ); isMonitoring = true; - if (callback != null) { - callback.onStatusChanged(true); - } - // 检查已有照片 executor.execute(() -> { List history = db2.imageDao().getAll(); if (history != null && !history.isEmpty()) { @@ -278,25 +295,17 @@ dataManager=SharedDataManager.getInstance(); getContentResolver().unregisterContentObserver(contentObserver); contentObserver = null; } - if (callback != null) { - callback.onStatusChanged(false); - } + isMonitoring=false; } } - private String getPartName(int b) - { + + private String getPartName(int b) { return "叶片" +b; } - public void setCallback(MonitoringCallback callback) { - this.callback = callback; - } - public boolean isMonitoring() { - return isMonitoring; - } - // 私有方法 + private void checkNewPhotos() { String[] projection = { MediaStore.Images.Media._ID, @@ -334,21 +343,12 @@ dataManager=SharedDataManager.getInstance(); long id = cursor.getLong(0); String path = cursor.getString(1); imgpath = path; - dataManager.setImagePath(imgpath); + long time = cursor.getLong(2) * 1000; double latitude = cursor.getDouble(3); double longitude = cursor.getDouble(4); double altitude = 0.0; - try { - Location lastLocation = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER); - if (lastLocation != null) { - lastKnownLocation = lastLocation; - } - } catch (SecurityException e) { - Log.w("PhotoMonitoringService", "Location permission not granted"); - } - if (lastKnownLocation != null) { latitude = lastKnownLocation.getLatitude(); longitude = lastKnownLocation.getLongitude(); @@ -365,20 +365,17 @@ dataManager=SharedDataManager.getInstance(); Log.e("PhotoMonitoringService", "Error reading EXIF data", e); } - String audioPath = currentAudioPath == null ? "0" : currentAudioPath; + String audioPath = currentAudioPath; lastPhotoId = Math.max(lastPhotoId, id); if (isNetworkAvailable() && !isRecording) { - uploadNewPhoto(path, time, audioPath, latitude, longitude, altitude); + uploadNewPhoto(path, time, audioPath); } else { queuePhotoForLater(path, time, audioPath, latitude, longitude, altitude); } - - currentAudioPath = null; } - private void uploadNewPhoto(String path, long time, String audioPath, - double latitude, double longitude, double altitude) { + private void uploadNewPhoto(String path, long time, String audioPath) { executor.execute(() -> { List history = db2.imageDao().getAll(); if (history != null && !history.isEmpty()) { @@ -396,7 +393,6 @@ dataManager=SharedDataManager.getInstance(); db2.imageDao().deleteAll(); String partname = getPartName(blade); - if (partid == null) partid = partname; uploadPhoto(path, time, lastKnownLocation, user, @@ -408,20 +404,16 @@ dataManager=SharedDataManager.getInstance(); double latitude, double longitude, double altitude) { executor.execute(() -> { String partname = getPartName(blade); - if (partid == null) partid = partname; - db2.imageDao().insert(new ImageEntity(path, time, latitude, longitude, altitude, user, audioPath, projectId, unit, blade, false, unitName, Temperator, Humidity, Weather, ChooseImageSource,partid)); - - }); } private void uploadPhoto(String filePath, long time, Location location, String user, String audioPath, String project, String unit, int blade, - String partid, String imageSource, String unitName) { + String partId, String imageSource, String unitName) { synchronized (uploadLock) { if (isUploading) { queuePhotoForLater(filePath, time, audioPath, @@ -430,16 +422,12 @@ dataManager=SharedDataManager.getInstance(); location != null ? location.getAltitude() : 0); return; } - } executor.execute(() -> { try { File imageFile = new File(filePath); if (!imageFile.exists()) { - if (callback != null) { - callback.onUploadFailure(filePath, "File does not exist"); - } return; } @@ -447,63 +435,81 @@ dataManager=SharedDataManager.getInstance(); double longitude = location != null ? location.getLongitude() : 0; double altitude = location != null ? location.getAltitude() : 0; - MultipartBody.Builder builder = new MultipartBody.Builder() - .setType(MultipartBody.FORM) - .addFormDataPart("image", imageFile.getName(), - RequestBody.create(imageFile, MediaType.parse("image/*"))) - .addFormDataPart("time", String.valueOf(time)) - .addFormDataPart("latitude", String.valueOf(latitude)) - .addFormDataPart("longitude", String.valueOf(longitude)) - .addFormDataPart("altitude", String.valueOf(altitude)) - .addFormDataPart("uploadUser", user) - .addFormDataPart("projectId", project) - .addFormDataPart("turbineId", unit) - .addFormDataPart("partId", partid) - .addFormDataPart("imageSource", imageSource); + // 构建请求URL + HttpUrl.Builder urlBuilder = HttpUrl.parse("http://pms.dtyx.net:9158/image/" + imageSource + "/upload/" + 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", imageFile.getName(), + RequestBody.create(imageFile, MediaType.parse("image/*"))) + .build(); - RequestBody requestBody = builder.build(); Request request = new Request.Builder() - .url("http://pms.dtyx.net:9158/common/upload-image/file") + .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"); + System.out.println(responseData); + String imageId = data != null ? data.optString("imageId") : ""; + dataManager.setImageId(imageId); + dataManager.setOthermsg("partid:"+partid+"\n"+"imageid:"+imageId+"\n"+"response:"+responseData); db.imageDao().insert(new ImageEntity(filePath, time, latitude, longitude, altitude, user, audioPath, project, unit, blade, true, unitName, - Temperator, Humidity, Weather, ChooseImageSource,partid)); + Temperator, Humidity, Weather, ChooseImageSource, partId)); - if (callback != null) { - successCount++; - Log.d("上传成功:","已经上传成功"+successCount+"张图片"); - BackgroundToast.show(this,"已经上传成功"+successCount+"张图片"); + totalUploadedCount++; + updateNotification(imageId); + Intent intent = new Intent("RESPONSE_ACTION"); + intent.putExtra("response", response.toString()); + sendBroadcast(intent); + long currentTime = System.currentTimeMillis(); + if (currentTime - lastSuccessToastTime > TOAST_THROTTLE_INTERVAL) { + BackgroundToast.show(this, "图片上传成功,已上传" + totalUploadedCount + "张"); + lastSuccessToastTime = currentTime; } } else { db2.imageDao().insert(new ImageEntity(filePath, time, latitude, longitude, altitude, user, audioPath, project, unit, blade, false, unitName, - Temperator, Humidity, Weather, ChooseImageSource,partid)); - - if (callback != null) { - failureCount++; - BackgroundToast.show(this,"上传失败"+response.code()+response.message()); - } + Temperator, Humidity, Weather, ChooseImageSource, partId)); + dataManager.setImageId("-1"); } } - } catch (IOException e) { + } catch (Exception e) { db2.imageDao().insert(new ImageEntity(filePath, time, 0, 0, 0, user, audioPath, project, unit, blade, false, unitName, - Temperator, Humidity, Weather, ChooseImageSource,partid)); - - if (callback != null) { - BackgroundToast.show(this,"io错误"+e.getMessage()); - } - } finally { - + Temperator, Humidity, Weather, ChooseImageSource, partId)); + dataManager.setImageId("-1"); } }); } + private void updateNotification(String msg) { + String content = "已成功上传 " + totalUploadedCount + " 张照片"+msg; + Notification notification = buildNotification("照片上传服务运行中", content); + NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); + if (manager != null) { + manager.notify(NOTIFICATION_ID, notification); + } + } + private boolean isNetworkAvailable() { ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); if (cm == null) return false; @@ -515,46 +521,44 @@ dataManager=SharedDataManager.getInstance(); capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)); } - - private void createNotificationChannel() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { NotificationChannel channel = new NotificationChannel( CHANNEL_ID, "照片上传服务", - NotificationManager.IMPORTANCE_HIGH // 使用高优先级确保通知显示 + NotificationManager.IMPORTANCE_HIGH ); channel.setDescription("显示照片上传状态和进度"); NotificationManager manager = getSystemService(NotificationManager.class); manager.createNotificationChannel(channel); } } + private Notification buildNotification(String title, String content) { return new NotificationCompat.Builder(this, CHANNEL_ID) .setContentTitle(title) .setContentText(content) - .setSmallIcon(R.drawable.ic_upload) // 使用上传图标 + .setSmallIcon(R.drawable.ic_upload) .setPriority(NotificationCompat.PRIORITY_HIGH) - .setOngoing(true) // 设置为持续通知 - .setOnlyAlertOnce(true) // 更新时不重复提醒 + .setOngoing(true) + .setOnlyAlertOnce(true) .addAction(createStopAction()) .build(); } + private NotificationCompat.Action createStopAction() { Intent stopIntent = new Intent(this, NotificationReceiver.class); stopIntent.setAction("ACTION_STOP_SERVICE"); - // 修改这一行,添加 FLAG_IMMUTABLE 或 FLAG_MUTABLE PendingIntent stopPendingIntent = PendingIntent.getBroadcast( this, 0, stopIntent, - PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT); // 添加 FLAG_IMMUTABLE + PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT); return new NotificationCompat.Action( R.drawable.ic_stop, "停止", stopPendingIntent); } - private long getLastPhotoId() { long id = -1; Cursor cursor = getContentResolver().query( @@ -566,10 +570,9 @@ dataManager=SharedDataManager.getInstance(); ); if (cursor != null && cursor.moveToFirst()) { - id = cursor.getLong(0); // MAX(_ID) 的结果在索引 0 + id = cursor.getLong(0); cursor.close(); } return id; } - } \ No newline at end of file diff --git a/RecordingService.java b/RecordingService.java index 7f941e2..de69bb9 100644 --- a/RecordingService.java +++ b/RecordingService.java @@ -7,6 +7,7 @@ import android.app.Service; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ServiceInfo; +import android.location.Location; import android.media.MediaRecorder; import android.os.Build; import android.os.Handler; @@ -59,7 +60,7 @@ public class RecordingService extends Service implements SharedDataManager.DataC private AppDatabase db; private AppDatabase db2; private final ExecutorService executor = Executors.newFixedThreadPool(4); - private String currentImagePath="-1"; + private String ImageId="-1"; private String currentToken="-1"; private SharedDataManager dataManager; @@ -110,16 +111,17 @@ public class RecordingService extends Service implements SharedDataManager.DataC dataManager=SharedDataManager.getInstance(); dataManager.addListener(this); - currentImagePath = dataManager.getImagePath(); + currentToken = dataManager.getToken(); + ImageId=dataManager.getImageId(); // 在onCreate()中改为使用成员变量观察 } + @Override - public void onImagePathChanged(String newPath) { - currentImagePath = newPath; - Log.d("Service", "ImagePath updated: " + newPath); + public void onImageIdChanged(String newId) { + ImageId=newId; } @Override @@ -177,6 +179,16 @@ public class RecordingService extends Service implements SharedDataManager.DataC public void onUploadingStatusChanged(boolean isUploading) { + } + + @Override + public void onLocationChanged(Location newLocation) { + + } + + @Override + public void onOtherMsgChanged(String msg) { + } @@ -192,9 +204,10 @@ public class RecordingService extends Service implements SharedDataManager.DataC } return START_REDELIVER_INTENT; } - private void startRecording() { + try { + dataManager.setIsRecording(true); // 1. 设置录音文件路径 currentAudioPath = getExternalCacheDir().getAbsolutePath() + "/" + System.currentTimeMillis() + ".3gp"; @@ -283,13 +296,14 @@ public class RecordingService extends Service implements SharedDataManager.DataC if (response.isSuccessful()) { // 上传成功 - db.AudioDao().delete(new AudioEntity(audioPath, imageId, currentTime)); - db2.AudioDao().insert(new AudioEntity(audioPath, imageId, currentTime)); + executor.execute(()->{ db.AudioDao().deleteByPath(audioPath); + + db2.AudioDao().insert(new AudioEntity(audioPath, imageId, currentTime,true));}); BackgroundToast.show(this, "录音上传成功: " + imageId); stopSelf(); } else { // 上传失败(HTTP 错误) - db.AudioDao().insert(new AudioEntity(audioPath, imageId, currentTime)); + executor.execute(()->{ db.AudioDao().insert(new AudioEntity(audioPath, imageId, currentTime,false));}); BackgroundToast.show(this, "录音上传失败: " + response.code() + " " + response.message()); stopSelf(); @@ -298,7 +312,10 @@ public class RecordingService extends Service implements SharedDataManager.DataC // 网络异常 String currentTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()) .format(new Date()); - db.AudioDao().insert(new AudioEntity(audioPath, imageId, currentTime)); + + executor.execute(() -> { + db.AudioDao().insert(new AudioEntity(currentAudioPath, ImageId, currentToken, false)); + }); BackgroundToast.show(this, "录音上传失败: " + e.getMessage()); Log.e("录音上传失败:",e.toString()); stopSelf(); @@ -314,18 +331,23 @@ public class RecordingService extends Service implements SharedDataManager.DataC mediaRecorder.release(); mediaRecorder = null; } - + dataManager.setIsRecording(false); // 2. 停止计时器 if (handler != null && updateTimerRunnable != null) { handler.removeCallbacks(updateTimerRunnable); } - // 3. 更新通知 updateNotification("录音已保存"); - // 4. 上传录音文件(如果有需要) - if (currentAudioPath != null) { - uploadAudio(currentImagePath, currentAudioPath, currentToken); + if (currentAudioPath != null && ImageId != "-1") { + BackgroundToast.show(this,"开始上传录音了"); + uploadAudio(ImageId, currentAudioPath, currentToken); + } else + { + BackgroundToast.show(this,"Imageid不存在,上传录音失败"); + executor.execute(() -> { + db.AudioDao().insert(new AudioEntity(currentAudioPath, ImageId, currentToken, false)); + }); } // 5. 不立即停止服务,等待上传完成 @@ -333,6 +355,7 @@ public class RecordingService extends Service implements SharedDataManager.DataC } catch (Exception e) { Toast.makeText(this, "停止录音失败: " + e.getMessage(), Toast.LENGTH_SHORT).show(); + dataManager.setIsRecording(false); stopSelf(); } }