上传文件至 /
This commit is contained in:
parent
e38555f627
commit
191a64eb9a
|
@ -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<Turbine> 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<Turbine>(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<Turbine> 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<Turbine>) 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<Turbine> 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<Turbine> 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<ApiResponse<List<Turbine>>> call = service.getTurbineList(projectID);
|
||||||
|
|
||||||
|
call.enqueue(new Callback<ApiResponse<List<Turbine>>>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(Call<ApiResponse<List<Turbine>>> call, Response<ApiResponse<List<Turbine>>> response) {
|
||||||
|
if (response.isSuccessful() && response.body() != null) {
|
||||||
|
ApiResponse<List<Turbine>> apiResponse = response.body();
|
||||||
|
|
||||||
|
if (apiResponse.getCode() == 200 && apiResponse.getData() != null) {
|
||||||
|
List<Turbine> 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<ApiResponse<List<Turbine>>> 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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 + ")";
|
||||||
|
}
|
||||||
|
}
|
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
|
@ -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省略
|
||||||
|
}
|
Loading…
Reference in New Issue