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.os.VibrationEffect; import android.os.Vibrator; 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; private ArrayAdapter turbineAdapter; private String projectID; public static boolean isFloatingWindowShowing = false; @SuppressLint("SetTextI18n") @Override public void onCreate() { super.onCreate(); createNotificationChannel(); database = AppDatabase.getDatabase(this); turbineDao = database.turbineDao(); 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() { // 创建前台服务通知 isFloatingWindowShowing = true; 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) { startForeground(NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE); } else { 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); // 设置关闭按钮点击事件 View btnClose = floatingView.findViewById(R.id.btnClose); btnClose.setOnClickListener(v -> { // 震动反馈 Vibrator vibrator = (Vibrator) getSystemService(VIBRATOR_SERVICE); if (vibrator != null && vibrator.hasVibrator()) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { vibrator.vibrate(VibrationEffect.createOneShot(50, VibrationEffect.DEFAULT_AMPLITUDE)); } else { vibrator.vibrate(50); } } closeFloatingWindow(); }); setupDragListener(params); setupValueChangeListeners(); setupAutoCompleteBehavior(); } private void closeFloatingWindow() { if (floatingView != null && floatingView.getWindowToken() != null) { // 添加淡出动画 floatingView.animate() .alpha(0f) .setDuration(200) .withEndAction(() -> { removeFloatingView(); stopSelf(); }) .start(); } else { removeFloatingView(); stopSelf(); } isFloatingWindowShowing = false; } private synchronized void removeFloatingView() { try { if (windowManager != null && floatingView != null && floatingView.getWindowToken() != null) { windowManager.removeView(floatingView); } } catch (IllegalArgumentException e) { Log.e("FloatingWindow", "View already removed", e); } finally { floatingView = null; } } 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); } } private void setupDragListener(WindowManager.LayoutParams params) { View dragHandle = floatingView.findViewById(R.id.floatingContainer); dragHandle.setOnTouchListener(new View.OnTouchListener() { private int initialX, initialY; private float initialTouchX, initialTouchY; @Override public boolean onTouch(View v, MotionEvent event) { // 检查是否点击在关闭按钮上 if (!isViewAttached()) { return false; } Rect closeButtonRect = new Rect(); floatingView.findViewById(R.id.btnClose).getGlobalVisibleRect(closeButtonRect); if (closeButtonRect.contains((int)event.getRawX(), (int)event.getRawY())) { return false; } // 检查是否点击在输入区域 Rect inputRect = new Rect(); actvUnit.getGlobalVisibleRect(inputRect); if (inputRect.contains((int)event.getRawX(), (int)event.getRawY())) { return false; } switch (event.getAction()) { case MotionEvent.ACTION_DOWN: initialX = params.x; initialY = params.y; initialTouchX = event.getRawX(); initialTouchY = event.getRawY(); 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); 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()) { fetchTurbinesFromApi(); } else { 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; } turbineDao.insertAll(turbines); mainHandler.post(() -> { updateTurbineList(turbines); showToast("数据加载成功"); }); } catch (Exception e) { Log.e("DB_ERROR", "保存数据失败", e); mainHandler.post(() -> showToast("保存数据失败: " + e.getMessage())); } }).start(); } else { String errorMsg = "服务器错误: " + apiResponse.getCode() + " - " + apiResponse.getMsg(); Log.e("API_ERROR", errorMsg); mainHandler.post(() -> showToast(errorMsg)); } } else { 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)); } }); } 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); } @Override public void onDestroy() { super.onDestroy(); hideSoftInput(actvUnit); actvUnit.dismissDropDown(); isFloatingWindowShowing = false; // 直接调用移除方法,不再重复执行stopSelf() removeFloatingView(); mainHandler.removeCallbacksAndMessages(null); } private boolean isViewAttached() { return floatingView != null && floatingView.getWindowToken() != null && windowManager != null; } @Override public IBinder onBind(Intent intent) { return null; } public static void closeFloatingWindow(Context context) { isFloatingWindowShowing = false; Intent serviceIntent = new Intent(context, FloatingWindowService.class); context.stopService(serviceIntent); } }