AndroidApp/FloatingWindowService.java

545 lines
22 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;
}
}