diff --git a/FloatingWindowService.java b/FloatingWindowService.java new file mode 100644 index 0000000..a1de064 --- /dev/null +++ b/FloatingWindowService.java @@ -0,0 +1,545 @@ + +package com.example.myapplication.Service; + +import android.annotation.SuppressLint; +import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ServiceInfo; +import android.graphics.Color; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.net.ConnectivityManager; +import android.net.NetworkCapabilities; +import android.os.Build; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.text.Editable; +import android.text.TextUtils; +import android.text.TextWatcher; +import android.util.Log; +import android.view.Gravity; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.view.inputmethod.InputMethodManager; +import android.widget.ArrayAdapter; +import android.widget.AutoCompleteTextView; +import android.widget.Filter; +import android.widget.RadioButton; +import android.widget.RadioGroup; +import android.widget.TextView; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.app.NotificationCompat; + +import com.example.myapplication.DataBase.AppDatabase; +import com.example.myapplication.DataBase.TurbineDao; +import com.example.myapplication.R; +import com.example.myapplication.api.TurbineApiService; +import com.example.myapplication.model.ApiResponse; +import com.example.myapplication.model.Turbine; + +import java.io.IOException; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; +import retrofit2.Retrofit; +import retrofit2.converter.gson.GsonConverterFactory; + +public class FloatingWindowService extends Service { + private static final String CHANNEL_ID = "FloatingWindowChannel"; + private static final int NOTIFICATION_ID = 1; + private Handler mainHandler = new Handler(Looper.getMainLooper()); + + private WindowManager windowManager; + private View floatingView; + + private RadioGroup radioGroup; + private static final String EXTRA_PROJECT_ID = "PROJECT_ID"; + private String lastUnit = ""; + private int lastBlade = -1; + private String lastUnitName = ""; + private AppDatabase database; + private TurbineDao turbineDao; + private AutoCompleteTextView actvUnit; // 替换原来的EditText + private ArrayAdapter turbineAdapter; + private String projectID; + @SuppressLint("SetTextI18n") + @Override + public void onCreate() { + super.onCreate(); + 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( + CHANNEL_ID, + "悬浮窗服务通道", + NotificationManager.IMPORTANCE_LOW + ); + NotificationManager manager = getSystemService(NotificationManager.class); + manager.createNotificationChannel(channel); + } + } + + private void showFloatingWindow() { + // 创建前台服务通知 + Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID) + .setContentTitle("悬浮窗服务运行中") + .setContentText("正在显示悬浮提示框") + .setSmallIcon(R.mipmap.ic_launcher) + .setPriority(NotificationCompat.PRIORITY_LOW) + .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); + } + + // 创建悬浮窗视图 + floatingView = LayoutInflater.from(this).inflate(R.layout.floating_window, null); + + // 设置窗口参数 + final WindowManager.LayoutParams params = new WindowManager.LayoutParams( + WindowManager.LayoutParams.WRAP_CONTENT, + WindowManager.LayoutParams.WRAP_CONTENT, + Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ? + WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY : + WindowManager.LayoutParams.TYPE_PHONE, + WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | + WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH, + PixelFormat.TRANSLUCENT); + + params.gravity = Gravity.TOP | Gravity.START; + params.x = 100; + params.y = 100; + + windowManager = (WindowManager) getSystemService(WINDOW_SERVICE); + windowManager.addView(floatingView, params); + + // 初始化视图 + actvUnit = floatingView.findViewById(R.id.actvUnit); + setupTurbineDropdown(); + radioGroup = floatingView.findViewById(R.id.radioGroup); + + setupDragListener(params); + setupValueChangeListeners(); + + setupAutoCompleteBehavior(); + + } + private void setupAutoCompleteBehavior() { + actvUnit.setFocusableInTouchMode(true); // 允许触摸获取焦点 + actvUnit.setFocusable(true); + actvUnit.setOnClickListener(v -> { + if (turbineAdapter.getCount() > 0) { + actvUnit.showDropDown(); + } else { + actvUnit.requestFocus(); + showSoftInputDelayed(actvUnit); + } + }); + + actvUnit.setOnFocusChangeListener((v, hasFocus) -> { + if (hasFocus && !TextUtils.isEmpty(actvUnit.getText()) && turbineAdapter.getCount() > 0) { + actvUnit.postDelayed(() -> actvUnit.showDropDown(), 100); + } + }); + + actvUnit.setOnKeyListener((v, keyCode, event) -> { + if (keyCode == KeyEvent.KEYCODE_ENTER && event.getAction() == KeyEvent.ACTION_UP) { + if (turbineAdapter.getCount() > 0) { + actvUnit.showDropDown(); + } else { + hideSoftInput(actvUnit); + } + return true; + } + return false; + }); + } + private void showSoftInputDelayed(View view) { + view.postDelayed(() -> { + InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE); + if (imm != null) { + imm.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT); + } + }, 200); + } + + private void hideSoftInput(View view) { + InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE); + if (imm != null) { + imm.hideSoftInputFromWindow(view.getWindowToken(), 0); + } + } +// 移除 setupInputMethod() 方法,因为功能已整合到上面的方法中 + + + private void setupDragListener(WindowManager.LayoutParams params) { + floatingView.findViewById(R.id.floatingContainer).setOnTouchListener(new View.OnTouchListener() { + private int initialX, initialY; + private float initialTouchX, initialTouchY; + + @Override + public boolean onTouch(View v, MotionEvent event) { + 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) { + @NonNull + @Override + public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) { + View view = super.getView(position, convertView, parent); + 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); + } + return view; + } + + @Override + public Filter getFilter() { + return new Filter() { + @Override + protected FilterResults performFiltering(CharSequence constraint) { + 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 + protected void publishResults(CharSequence constraint, FilterResults results) { + clear(); + if (results.count > 0) { + addAll((List) results.values); + notifyDataSetChanged(); + if (actvUnit.hasFocus() && !TextUtils.isEmpty(constraint)) { + actvUnit.post(() -> actvUnit.showDropDown()); + } + } else { + notifyDataSetInvalidated(); + } + } + }; + } + }; + + actvUnit.setAdapter(turbineAdapter); + actvUnit.setThreshold(0); // 输入1个字符后开始搜索 + actvUnit.setDropDownHeight(ViewGroup.LayoutParams.WRAP_CONTENT); + 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) {} + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + String input=s.toString().trim(); + lastUnit = 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)) { + lastUnit = turbine.turbineId; + lastUnitName = turbine.turbineName; + break; + } + } + } + checkAndSendUpdate(); // 实时触发检查 + } + }); + + loadTurbines(); + } + + private void loadTurbines() { + 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) { + Log.e("TurbineLoad", "加载机组数据失败", e); + } + }); + } + private void updateTurbineList(List turbines) { + turbineAdapter.clear(); + if (!turbines.isEmpty()) { + turbineAdapter.addAll(turbines); + } + + // 如果输入框有内容,重新过滤 + if (!TextUtils.isEmpty(actvUnit.getText())) { + turbineAdapter.getFilter().filter(actvUnit.getText()); + } + } + + private boolean isNetworkAvailable() { + ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); + if (cm == null) return false; + + NetworkCapabilities capabilities = cm.getNetworkCapabilities(cm.getActiveNetwork()); + return capabilities != null && + (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) || + capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) || + capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)); + } + + private void fetchTurbinesFromApi() { + Retrofit retrofit = new Retrofit.Builder() + .baseUrl("http://pms.dtyx.net:9158/") + .addConverterFactory(GsonConverterFactory.create()) + .build(); + + TurbineApiService service = retrofit.create(TurbineApiService.class); + Call>> call = service.getTurbineList(projectID); + + call.enqueue(new Callback>>() { + @Override + public void onResponse(Call>> call, Response>> response) { + if (response.isSuccessful() && response.body() != null) { + ApiResponse> apiResponse = response.body(); + + if (apiResponse.getCode() == 200 && apiResponse.getData() != null) { + 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 正确 + } + turbineDao.insertAll(turbines); + + // 使用 mainHandler 更新 UI(主线程) + mainHandler.post(() -> { + updateTurbineList(turbines); + showToast("数据加载成功"); + }); + } catch (Exception e) { + Log.e("DB_ERROR", "保存数据失败", e); + 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) { + Log.e("API_ERROR", "解析错误信息失败", e); + } + } + Log.e("API_ERROR", errorMsg); + String finalErrorMsg = errorMsg; + mainHandler.post(() -> showToast(finalErrorMsg)); + } + } + + @Override + public void onFailure(Call>> call, Throwable t) { + // 网络请求失败(如无网络、超时) + String errorMsg = "网络错误: " + t.getMessage(); + Log.e("NETWORK_ERROR", errorMsg, t); + mainHandler.post(() -> showToast(errorMsg)); + } + }); + } + + // 显示 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) {} + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) {} + + @Override + 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); // 显示名称 + checkAndSendUpdate(); + } + }); + // 监听叶片号选择变化 + radioGroup.setOnCheckedChangeListener((group, checkedId) -> { + int currentBlade = 0; + if (checkedId != -1) { + RadioButton radioButton = floatingView.findViewById(checkedId); + currentBlade = Integer.parseInt(radioButton.getText().toString()); + } + + if (currentBlade != lastBlade) { + lastBlade = currentBlade; + checkAndSendUpdate(); + } + }); + } + + private void checkAndSendUpdate() { + // 只有当两个值都有有效变化时才发送广播 + if (!lastUnit.isEmpty() && lastBlade != -1) { + sendUpdateBroadcast(); + highlightChanges(); + } + } + + private void sendUpdateBroadcast() { + Intent intent = new Intent("com.example.myapplication.UPDATE_VALUES"); + intent.putExtra("unit", lastUnit); + intent.putExtra("blade", lastBlade); + intent.putExtra("unitName", lastUnitName); // 发送机组名称 + intent.setPackage(getPackageName()); + sendBroadcast(intent); + } + + private void highlightChanges() { + // 高亮效果 - 改变背景色然后恢复 + int originalColor = actvUnit.getSolidColor(); + actvUnit.setBackgroundColor(Color.YELLOW); + radioGroup.setBackgroundColor(Color.YELLOW); + + new Handler().postDelayed(() -> { + actvUnit.setBackgroundColor(originalColor); + radioGroup.setBackgroundColor(Color.TRANSPARENT); + }, 300); // 0.3秒后恢复 + } + + @Override + public void onDestroy() { + super.onDestroy(); + hideSoftInput(actvUnit); + actvUnit.dismissDropDown(); + if (floatingView != null && windowManager != null) { + windowManager.removeView(floatingView); + } + mainHandler.removeCallbacksAndMessages(null); + } + + @Override + public IBinder onBind(Intent intent) { + return null; + } +} \ No newline at end of file diff --git a/ForegroundService.java b/ForegroundService.java new file mode 100644 index 0000000..34a611c --- /dev/null +++ b/ForegroundService.java @@ -0,0 +1,54 @@ +package com.example.myapplication.Service; + +import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.app.Service; +import android.content.Intent; +import android.os.Build; +import android.os.IBinder; + +import androidx.annotation.Nullable; +import androidx.core.app.NotificationCompat; + +import com.example.myapplication.R; + +public class ForegroundService extends Service { + @Override + public void onCreate() { + super.onCreate(); + startForeground(1, createNotification()); + } + + @Nullable + @Override + public IBinder onBind(Intent intent) { + return null; + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + return START_STICKY; + } + + + + private Notification createNotification() { + NotificationChannel channel = null; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + channel = new NotificationChannel( + "channel_id", + "Foreground Service", + NotificationManager.IMPORTANCE_LOW + ); + NotificationManager manager = getSystemService(NotificationManager.class); + manager.createNotificationChannel(channel); + } + + return new NotificationCompat.Builder(this, "channel_id") + .setContentTitle("录音服务运行中") + .setContentText("悬浮球正在运行") + .setSmallIcon(R.drawable.ic_mic_off) + .build(); + } +} diff --git a/Project.java b/Project.java new file mode 100644 index 0000000..404c234 --- /dev/null +++ b/Project.java @@ -0,0 +1,239 @@ +package com.example.myapplication.model; + +import com.google.gson.annotations.SerializedName; + +// Project.java +public class Project { + @SerializedName("projectId") + private String projectId; + + @SerializedName("projectName") + private String projectName; + + @SerializedName("status") + private int status; + + @SerializedName("client") + private String client; + + @SerializedName("clientContact") + private String clientContact; + + @SerializedName("clientPhone") + private String clientPhone; + + @SerializedName("constructorIds") + private String constructorIds; + + @SerializedName("constructorName") + private String constructorName; + + @SerializedName("coverUrl") + private String coverUrl; + + @SerializedName("createTime") + private String createTime; + + @SerializedName("farmAddress") + private String farmAddress; + + @SerializedName("farmName") + private String farmName; + + @SerializedName("inspectionContact") + private String inspectionContact; + + @SerializedName("inspectionPhone") + private String inspectionPhone; + + @SerializedName("inspectionUnit") + private String inspectionUnit; + + @SerializedName("projectManagerId") + private String projectManagerId; + + @SerializedName("projectManagerName") + private String projectManagerName; + + @SerializedName("scale") + private String scale; + + @SerializedName("statusLabel") + private String statusLabel; + + @SerializedName("turbineModel") + private String turbineModel; + + public String getProjectName() { + return projectName; + } + + public void setProjectName(String projectName) { + this.projectName = projectName; + } + + public int getStatus() { + return status; + } + + public void setStatus(int status) { + this.status = status; + } + + public String getClient() { + return client; + } + + public void setClient(String client) { + this.client = client; + } + + public String getClientPhone() { + return clientPhone; + } + + public void setClientPhone(String clientPhone) { + this.clientPhone = clientPhone; + } + + public String getConstructorIds() { + return constructorIds; + } + + public void setConstructorIds(String constructorIds) { + this.constructorIds = constructorIds; + } + + public String getClientContact() { + return clientContact; + } + + public void setClientContact(String clientContact) { + this.clientContact = clientContact; + } + + public String getCoverUrl() { + return coverUrl; + } + + public void setCoverUrl(String coverUrl) { + this.coverUrl = coverUrl; + } + + public String getConstructorName() { + return constructorName; + } + + public void setConstructorName(String constructorName) { + this.constructorName = constructorName; + } + + public String getCreateTime() { + return createTime; + } + + public void setCreateTime(String createTime) { + this.createTime = createTime; + } + + public String getFarmName() { + return farmName; + } + + public void setFarmName(String farmName) { + this.farmName = farmName; + } + + public String getFarmAddress() { + return farmAddress; + } + + public void setFarmAddress(String farmAddress) { + this.farmAddress = farmAddress; + } + + public String getInspectionPhone() { + return inspectionPhone; + } + + public void setInspectionPhone(String inspectionPhone) { + this.inspectionPhone = inspectionPhone; + } + + public String getInspectionContact() { + return inspectionContact; + } + + public void setInspectionContact(String inspectionContact) { + this.inspectionContact = inspectionContact; + } + + public String getInspectionUnit() { + return inspectionUnit; + } + + public void setInspectionUnit(String inspectionUnit) { + this.inspectionUnit = inspectionUnit; + } + + public String getProjectManagerId() { + return projectManagerId; + } + + public void setProjectManagerId(String projectManagerId) { + this.projectManagerId = projectManagerId; + } + + public String getScale() { + return scale; + } + + public void setScale(String scale) { + this.scale = scale; + } + + public String getProjectManagerName() { + return projectManagerName; + } + + public void setProjectManagerName(String projectManagerName) { + this.projectManagerName = projectManagerName; + } + + public String getStatusLabel() { + return statusLabel; + } + + public void setStatusLabel(String statusLabel) { + this.statusLabel = statusLabel; + } + + public String getTurbineModel() { + return turbineModel; + } + + public void setTurbineModel(String turbineModel) { + this.turbineModel = turbineModel; + } + + // Getters and Setters + public String getProjectId() { + return projectId; + } + + public void setProjectId(String projectId) { + this.projectId = projectId; + } + public Project(String projectId, String projectName) { + this.projectId = projectId; + this.projectName = projectName; + } + // 其他字段的 getter/setter 按需添加... + // 建议使用 Android Studio 的 "Generate" -> "Getter and Setter" 自动生成 + + + @Override + public String toString() { + return projectName + " (" + projectId + ")"; + } +} \ No newline at end of file diff --git a/RecordingService.java b/RecordingService.java new file mode 100644 index 0000000..05eee74 --- /dev/null +++ b/RecordingService.java @@ -0,0 +1,95 @@ +package com.example.myapplication.Service; + +import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.app.Service; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ServiceInfo; +import android.os.Build; +import android.os.IBinder; +import android.widget.Toast; + +import androidx.annotation.RequiresApi; +import androidx.core.app.ActivityCompat; +import androidx.core.app.NotificationCompat; +import androidx.core.app.NotificationManagerCompat; + +import com.example.myapplication.R; + +public class RecordingService extends Service { + private static final String CHANNEL_ID = "recording_channel"; + private static final int NOTIFICATION_ID = 1; + + @RequiresApi(api = Build.VERSION_CODES.R) + @Override + public void onCreate() { + super.onCreate(); + try { + createNotificationChannel(); + // 启动前台服务(必须5秒内调用startForeground) + Notification notification = buildNotification("录音服务运行中"); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + // Android 10+ 需要指定类型 + startForeground(NOTIFICATION_ID, notification, + ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE); + } else { + // Android 8.0-9.0 + startForeground(NOTIFICATION_ID, notification); + } + } catch (Exception e) { + Toast.makeText(this,"错误:"+e,Toast.LENGTH_LONG).show(); + stopSelf(); + } + } + + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + + return START_STICKY; + } + + @Override + public IBinder onBind(Intent intent) { + return null; + } + + private void createNotificationChannel() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + NotificationChannel channel = new NotificationChannel( + "recording_channel", + "Recording Service", + NotificationManager.IMPORTANCE_LOW); + NotificationManager manager = getSystemService(NotificationManager.class); + if (manager != null) { + manager.createNotificationChannel(channel); + } + } + } + + private Notification buildNotification(String text) { + // 确保图标有效(R.drawable.ic_mic_on必须存在) + return new NotificationCompat.Builder(this, CHANNEL_ID) + .setSmallIcon(R.drawable.ic_mic_on) + .setContentTitle("录音服务") + .setContentText(text) + .setPriority(NotificationCompat.PRIORITY_LOW) + .build(); + } + + public void updateNotification(String text) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + if (ActivityCompat.checkSelfPermission(this, + android.Manifest.permission.POST_NOTIFICATIONS) + != PackageManager.PERMISSION_GRANTED) { + // 无权限时不显示通知(避免崩溃) + return; + } + } + NotificationManagerCompat.from(this) + .notify(NOTIFICATION_ID, buildNotification(text)); + } +} \ No newline at end of file diff --git a/Turbine.java b/Turbine.java new file mode 100644 index 0000000..7f6ca4c --- /dev/null +++ b/Turbine.java @@ -0,0 +1,25 @@ +package com.example.myapplication.model; + +import androidx.annotation.NonNull; +import androidx.room.Entity; +import androidx.room.PrimaryKey; + +@Entity(tableName = "turbines") +public class Turbine { + @PrimaryKey + @NonNull + public String turbineId; + public String turbineName; + public String projectId; + + + + @Override + public String toString() { + return + + turbineName +'('+ turbineId + ')'; + + } + // 构造方法、getter/setter省略 +}