package com.miraclegarden.smsmessage.service; import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.content.pm.ServiceInfo; import android.os.Build; import android.os.Handler; import android.os.Looper; import android.os.PowerManager; import android.provider.Settings; import android.service.notification.NotificationListenerService; import android.service.notification.StatusBarNotification; import android.text.TextUtils; import android.util.Log; import android.widget.Toast; import androidx.annotation.NonNull; import androidx.core.app.NotificationCompat; import com.miraclegarden.smsmessage.Activity.NotificationActivity; import com.miraclegarden.smsmessage.App; import com.miraclegarden.smsmessage.MessageInfo; import com.miraclegarden.smsmessage.R; import com.miraclegarden.smsmessage.model.ApiError; import com.miraclegarden.smsmessage.model.UploadNotificationResponse; import com.miraclegarden.smsmessage.network.ApiService; import com.miraclegarden.smsmessage.network.TokenManager; import org.json.JSONException; import org.json.JSONObject; import java.io.IOException; import java.util.concurrent.TimeUnit; import okhttp3.Call; import okhttp3.Callback; import okhttp3.MediaType; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.Response; public class NotificationService extends NotificationListenerService { private static final String TAG = "NotificationService"; private static final int FOREGROUND_NOTIFICATION_ID = 9001; private static final String CHANNEL_ID = "notification_listener_channel"; private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8"); private static final String WAKELOCK_TAG = "NotificationService:WakeLock"; private static final OkHttpClient HTTP_CLIENT = new OkHttpClient.Builder() .connectTimeout(15, TimeUnit.SECONDS) .writeTimeout(15, TimeUnit.SECONDS) .readTimeout(15, TimeUnit.SECONDS) .build(); private RetryManager retryManager; private PowerManager.WakeLock wakeLock; private static boolean isMonitoring = false; private ApiService apiService; private TokenManager tokenManager; @Override public void onCreate() { super.onCreate(); createNotificationChannel(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { startForeground(FOREGROUND_NOTIFICATION_ID, buildForegroundNotification(0, false), ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE); } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { startForeground(FOREGROUND_NOTIFICATION_ID, buildForegroundNotification(0, false), ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE); } else { startForeground(FOREGROUND_NOTIFICATION_ID, buildForegroundNotification(0, false)); } apiService = new ApiService(this); tokenManager = TokenManager.getInstance(this); retryManager = new RetryManager(this); retryManager.setUploadCallback(this::performUpload); retryManager.restoreFromPersistence(); } @Override public int onStartCommand(Intent intent, int flags, int startId) { if (intent != null && "ACTION_START_MONITORING".equals(intent.getAction())) { startMonitoring(); } NotificationActivity.sendMessage("监听服务成功!"); return START_STICKY; } public static boolean isMonitoring() { return isMonitoring; } private void startMonitoring() { if (isMonitoring) return; isMonitoring = true; toggleNotificationListenerService(this); updateForegroundNotification(retryManager != null ? retryManager.getUploadedCount() : 0); NotificationActivity.sendMessage("已开启持续监听模式"); NotificationActivity.updateUI(); } public static void stopMonitoring(Context context) { isMonitoring = false; NotificationActivity.sendMessage("已停止监听服务"); NotificationActivity.updateUI(); Toast.makeText(context, "监听服务已停止", Toast.LENGTH_SHORT).show(); } private void acquireWakeLockForUpload() { if (wakeLock != null && wakeLock.isHeld()) return; PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE); if (pm != null) { wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_TAG); wakeLock.acquire(30_000); } } private void releaseWakeLockAfterUpload() { if (wakeLock != null && wakeLock.isHeld()) { wakeLock.release(); } } public static void requestStartMonitoring(Context context) { toggleNotificationListenerService(context); try { Intent intent = new Intent(context, NotificationService.class); intent.setAction("ACTION_START_MONITORING"); context.startService(intent); } catch (Exception e) { Log.e(TAG, "启动监听失败", e); } } @Override public void onNotificationPosted(StatusBarNotification sbn) { if (sbn == null) return; if (sbn.getPackageName().equals(getPackageName())) return; MessageInfo messageInfo = App.getMessageByNotiList(this, sbn.getPackageName()); if (messageInfo == null) return; NotificationExtractor.Result result = NotificationExtractor.extract(sbn); if (TextUtils.isEmpty(result.title) && TextUtils.isEmpty(result.content)) { NotificationActivity.sendMessage("[" + messageInfo.getAppName() + "] 通知内容为空,跳过"); return; } if (result.title.contains("正在运行") || result.title.contains("正在监听")) { return; } NotificationActivity.sendMessage(result.title + " " + result.content); submitNotification(messageInfo, result); } private void submitNotification(MessageInfo messageInfo, NotificationExtractor.Result result) { // 检查是否已登录 if (!tokenManager.isLoggedIn()) { NotificationActivity.sendMessage("未登录,请先登录"); return; } // 检查是否关联了银行账户 if (TextUtils.isEmpty(messageInfo.getBankInfoId())) { NotificationActivity.sendMessage("该应用未关联银行账户,请先配置"); return; } try { JSONObject jsonObject = new JSONObject(); // 根据文档,bankInfoId 是建议字段 jsonObject.put("bankInfoId", messageInfo.getBankInfoId()); // data 字段包含通知内容 JSONObject dataObject = new JSONObject(); dataObject.put("title", result.title); dataObject.put("context", result.content); dataObject.put("timestamp", result.timestamp); jsonObject.put("data", dataObject); // 保留原有字段用于兼容性 jsonObject.put("name", messageInfo.getName()); jsonObject.put("code", messageInfo.getCode()); jsonObject.put("remark", messageInfo.getRemark()); jsonObject.put("appName", messageInfo.getAppName()); jsonObject.put("packageName", messageInfo.getPackageName()); String payload = jsonObject.toString(); NotificationActivity.sendMessage("准备发送服务器:成功"); retryManager.enqueue(payload); } catch (JSONException e) { Log.e(TAG, "JSON构建失败", e); NotificationActivity.sendMessage("JSON构建失败: " + e.getMessage()); } } private void performUpload(String jsonPayload, RetryManager.UploadResultListener listener) { acquireWakeLockForUpload(); // 使用新的API服务上传 apiService.uploadNotification(jsonPayload, new ApiService.ApiCallback() { @Override public void onSuccess(UploadNotificationResponse result) { releaseWakeLockAfterUpload(); String message = "提交成功"; if (result.getId() != null) { message += " (ID: " + result.getId().substring(0, Math.min(8, result.getId().length())) + "...)"; } if (result.getDuplicateOfEmailSyncRecordId() != null) { message += " [已去重]"; } NotificationActivity.sendMessage(message); listener.onSuccess(); updateForegroundNotification(retryManager.getUploadedCount()); } @Override public void onError(ApiError error) { releaseWakeLockAfterUpload(); String errorMsg = error.getMessage(); if (error.isTokenExpired()) { errorMsg = "登录已失效,请重新登录"; tokenManager.clear(); } NotificationActivity.sendMessage("提交失败: " + errorMsg); listener.onFailure(errorMsg); } }); } @Override public void onListenerDisconnected() { Log.w(TAG, "通知监听断开,尝试恢复"); NotificationActivity.sendMessage("通知监听断开,尝试恢复..."); toggleNotificationListenerService(this); new Handler(Looper.getMainLooper()).postDelayed(() -> { if (!isNotificationListenerEnabled()) { notifyNlsDisabled(); } }, 2000); } private boolean isNotificationListenerEnabled() { String flat = Settings.Secure.getString( getContentResolver(), "enabled_notification_listeners"); if (TextUtils.isEmpty(flat)) { return false; } return flat.contains(getPackageName()); } private void notifyNlsDisabled() { Log.w(TAG, "通知监听已被禁用,向用户发出警告"); NotificationActivity.sendMessage("警告:通知监听已被禁用,请重新开启!"); Intent intent = new Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(intent); } @Override public void onDestroy() { super.onDestroy(); isMonitoring = false; NotificationActivity.updateUI(); if (wakeLock != null && wakeLock.isHeld()) { wakeLock.release(); wakeLock = null; } if (retryManager != null) { retryManager.destroy(); } } public static void toggleNotificationListenerService(Context context) { ComponentName thisComponent = new ComponentName(context, NotificationService.class); PackageManager pm = context.getPackageManager(); pm.setComponentEnabledSetting(thisComponent, PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP); pm.setComponentEnabledSetting(thisComponent, PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP); } private void createNotificationChannel() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { NotificationChannel channel = new NotificationChannel( CHANNEL_ID, "通知监听服务", NotificationManager.IMPORTANCE_LOW ); channel.setDescription("保持通知监听服务运行"); channel.setShowBadge(false); NotificationManager nm = getSystemService(NotificationManager.class); if (nm != null) { nm.createNotificationChannel(channel); } } } private Notification buildForegroundNotification(int uploadedCount, boolean monitoring) { String title = monitoring ? "持续监听中" : "通知监听服务"; String text = monitoring ? "已上传 " + uploadedCount + " 条 · 持续运行中" : "已上传 " + uploadedCount + " 条"; return new NotificationCompat.Builder(this, CHANNEL_ID) .setContentTitle(title) .setContentText(text) .setSmallIcon(R.mipmap.app_logo) .setOngoing(true) .setPriority(NotificationCompat.PRIORITY_LOW) .setCategory(NotificationCompat.CATEGORY_SERVICE) .build(); } private void updateForegroundNotification(int uploadedCount) { NotificationManager nm = getSystemService(NotificationManager.class); if (nm != null) { nm.notify(FOREGROUND_NOTIFICATION_ID, buildForegroundNotification(uploadedCount, isMonitoring)); } } }