AndroidApp/FloatingWindowService.java

545 lines
22 KiB
Java
Raw Normal View History

2025-07-11 18:31:33 +08:00
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;
}
}