Files
notiMessage/app/src/main/java/com/miraclegarden/smsmessage/service/NotificationService.java
wchino 1951b76a7c 通知服务核心更新
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-04-05 22:28:53 +08:00

340 lines
13 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.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<UploadNotificationResponse>() {
@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));
}
}
}