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