feat: MariBank 风控 bypass、澳洲银行 Hook 与 reverse 逆向工作区
新增 MariBank/SeaBank PH Root 与 SHPSSDK bypass、riskToken 净化及 Up/Suncorp/ubank 消息 Hook;整理 reverse/ 脚本与 Frida 工具链,并补充当日工作说明文档。
This commit is contained in:
@@ -0,0 +1,172 @@
|
||||
package com.miraclegarden.smsmessage.Activity;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.miraclegarden.library.app.MiracleGardenActivity;
|
||||
import com.miraclegarden.smsmessage.KeepAliveSettings;
|
||||
import com.miraclegarden.smsmessage.R;
|
||||
import com.miraclegarden.smsmessage.databinding.ActivityKeepAliveSettingsBinding;
|
||||
import com.miraclegarden.smsmessage.service.MonitoredAppActivator;
|
||||
import com.miraclegarden.smsmessage.service.MonitoredAppKeepAliveScheduler;
|
||||
import com.miraclegarden.smsmessage.service.NotificationService;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class KeepAliveSettingsActivity extends MiracleGardenActivity<ActivityKeepAliveSettingsBinding> {
|
||||
|
||||
private boolean loadingUi;
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
binding.ivBack.setOnClickListener(v -> finish());
|
||||
setupSpinners();
|
||||
loadFromPrefs();
|
||||
bindListeners();
|
||||
}
|
||||
|
||||
private void setupSpinners() {
|
||||
binding.spinnerInterval.setAdapter(buildAdapter(formatIntervalOptions()));
|
||||
binding.spinnerCooldown.setAdapter(buildAdapter(formatCooldownOptions()));
|
||||
}
|
||||
|
||||
private ArrayAdapter<String> buildAdapter(List<String> labels) {
|
||||
ArrayAdapter<String> adapter = new ArrayAdapter<>(
|
||||
this, android.R.layout.simple_spinner_item, labels);
|
||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
return adapter;
|
||||
}
|
||||
|
||||
private List<String> formatIntervalOptions() {
|
||||
List<String> labels = new ArrayList<>();
|
||||
for (int minutes : KeepAliveSettings.INTERVAL_OPTIONS) {
|
||||
labels.add(minutes + " 分钟");
|
||||
}
|
||||
return labels;
|
||||
}
|
||||
|
||||
private List<String> formatCooldownOptions() {
|
||||
List<String> labels = new ArrayList<>();
|
||||
for (int minutes : KeepAliveSettings.COOLDOWN_OPTIONS) {
|
||||
labels.add(minutes + " 分钟");
|
||||
}
|
||||
return labels;
|
||||
}
|
||||
|
||||
private void loadFromPrefs() {
|
||||
loadingUi = true;
|
||||
binding.switchEnabled.setChecked(KeepAliveSettings.isEnabled(this));
|
||||
binding.switchStealthMode.setChecked(KeepAliveSettings.isStealthMode(this));
|
||||
binding.switchPreferRoot.setChecked(KeepAliveSettings.preferRoot(this));
|
||||
binding.switchReturnHome.setChecked(KeepAliveSettings.returnHome(this));
|
||||
binding.switchKillBeforeLaunch.setChecked(KeepAliveSettings.killBeforeLaunch(this));
|
||||
binding.spinnerInterval.setSelection(
|
||||
KeepAliveSettings.indexOfInterval(KeepAliveSettings.getIntervalMinutes(this)));
|
||||
binding.spinnerCooldown.setSelection(
|
||||
KeepAliveSettings.indexOfCooldown(KeepAliveSettings.getCooldownMinutes(this)));
|
||||
loadingUi = false;
|
||||
updateControlsEnabled();
|
||||
}
|
||||
|
||||
private void bindListeners() {
|
||||
binding.switchEnabled.setOnCheckedChangeListener((buttonView, isChecked) -> {
|
||||
if (loadingUi) {
|
||||
return;
|
||||
}
|
||||
KeepAliveSettings.setEnabled(this, isChecked);
|
||||
updateControlsEnabled();
|
||||
applySchedule();
|
||||
});
|
||||
|
||||
binding.switchStealthMode.setOnCheckedChangeListener((buttonView, isChecked) -> {
|
||||
if (!loadingUi) {
|
||||
KeepAliveSettings.setStealthMode(this, isChecked);
|
||||
}
|
||||
});
|
||||
|
||||
binding.switchPreferRoot.setOnCheckedChangeListener((buttonView, isChecked) -> {
|
||||
if (!loadingUi) {
|
||||
KeepAliveSettings.setPreferRoot(this, isChecked);
|
||||
}
|
||||
});
|
||||
|
||||
binding.switchReturnHome.setOnCheckedChangeListener((buttonView, isChecked) -> {
|
||||
if (!loadingUi) {
|
||||
KeepAliveSettings.setReturnHome(this, isChecked);
|
||||
}
|
||||
});
|
||||
|
||||
binding.switchKillBeforeLaunch.setOnCheckedChangeListener((buttonView, isChecked) -> {
|
||||
if (!loadingUi) {
|
||||
KeepAliveSettings.setKillBeforeLaunch(this, isChecked);
|
||||
}
|
||||
});
|
||||
|
||||
binding.spinnerInterval.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||
if (loadingUi) {
|
||||
return;
|
||||
}
|
||||
KeepAliveSettings.setIntervalMinutes(
|
||||
KeepAliveSettingsActivity.this, KeepAliveSettings.INTERVAL_OPTIONS[position]);
|
||||
applySchedule();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> parent) {
|
||||
}
|
||||
});
|
||||
|
||||
binding.spinnerCooldown.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||
if (!loadingUi) {
|
||||
KeepAliveSettings.setCooldownMinutes(
|
||||
KeepAliveSettingsActivity.this, KeepAliveSettings.COOLDOWN_OPTIONS[position]);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> parent) {
|
||||
}
|
||||
});
|
||||
|
||||
binding.btnWakeNow.setOnClickListener(v -> {
|
||||
if (!KeepAliveSettings.isEnabled(this)) {
|
||||
Toast.makeText(this, R.string.keep_alive_wake_disabled, Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
MonitoredAppActivator.activateAllAsync(this);
|
||||
Toast.makeText(this, R.string.keep_alive_wake_started, Toast.LENGTH_SHORT).show();
|
||||
});
|
||||
}
|
||||
|
||||
private void applySchedule() {
|
||||
if (NotificationService.isMonitoringActive(this)) {
|
||||
if (KeepAliveSettings.isEnabled(this)) {
|
||||
MonitoredAppKeepAliveScheduler.schedule(this);
|
||||
} else {
|
||||
MonitoredAppKeepAliveScheduler.cancel(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateControlsEnabled() {
|
||||
boolean enabled = binding.switchEnabled.isChecked();
|
||||
binding.spinnerInterval.setEnabled(enabled);
|
||||
binding.spinnerCooldown.setEnabled(enabled);
|
||||
binding.switchStealthMode.setEnabled(enabled);
|
||||
binding.switchPreferRoot.setEnabled(enabled);
|
||||
binding.switchKillBeforeLaunch.setEnabled(enabled);
|
||||
binding.switchReturnHome.setEnabled(enabled);
|
||||
binding.btnWakeNow.setEnabled(enabled);
|
||||
}
|
||||
}
|
||||
@@ -145,7 +145,7 @@ public class NotificationActivity extends MiracleGardenActivity<ActivityNotifica
|
||||
return;
|
||||
}
|
||||
|
||||
if (NotificationService.isMonitoring()) {
|
||||
if (NotificationService.isMonitoringActive(this)) {
|
||||
NotificationService.stopMonitoring(this);
|
||||
sendMessage("正在停止监听...");
|
||||
} else {
|
||||
@@ -165,7 +165,7 @@ public class NotificationActivity extends MiracleGardenActivity<ActivityNotifica
|
||||
return;
|
||||
}
|
||||
|
||||
if (NotificationService.isMonitoring()) {
|
||||
if (NotificationService.isMonitoringActive(activity)) {
|
||||
activity.binding.btnToggleMonitor.setText("停止监听");
|
||||
activity.binding.btnToggleMonitor.setEnabled(true);
|
||||
activity.binding.btnToggleMonitor.setBackgroundTintList(
|
||||
|
||||
@@ -56,6 +56,10 @@ public class SettingActivity extends MiracleGardenActivity<ActivitySettingBindin
|
||||
binding.btnAddApp.setOnClickListener(v -> {
|
||||
startActivity(new Intent(this, AppListActivity.class));
|
||||
});
|
||||
|
||||
binding.rowKeepAliveSettings.setOnClickListener(v -> {
|
||||
startActivity(new Intent(this, KeepAliveSettingsActivity.class));
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -18,6 +18,21 @@ public final class AppConfig {
|
||||
*/
|
||||
public static final String DEBUG_SERVER_URL = "http://127.0.0.1:8765";
|
||||
|
||||
/** 是否定期唤醒监听列表中的 App(保持进程 / Hook 加载) */
|
||||
public static final boolean ENABLE_MONITORED_APP_KEEP_ALIVE = true;
|
||||
|
||||
/** 保活间隔(分钟,WorkManager 最小 15) */
|
||||
public static final int MONITORED_APP_KEEP_ALIVE_MINUTES = 15;
|
||||
|
||||
/** 同一 App 两次唤醒最短间隔(分钟) */
|
||||
public static final int MONITORED_APP_WAKE_COOLDOWN_MINUTES = 10;
|
||||
|
||||
/** 优先使用 Root(monkey 唤醒 + 电池白名单),失败则普通 startActivity */
|
||||
public static final boolean KEEP_ALIVE_PREFER_ROOT = true;
|
||||
|
||||
/** Root 唤醒后是否自动返回桌面(减少停留在银行/TG 界面) */
|
||||
public static final boolean KEEP_ALIVE_RETURN_HOME = true;
|
||||
|
||||
private AppConfig() {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
package com.miraclegarden.smsmessage;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
/**
|
||||
* App 保活相关设置(SharedPreferences,可在设置页修改)。
|
||||
*/
|
||||
public final class KeepAliveSettings {
|
||||
|
||||
private static final String PREF = "keep_alive_settings";
|
||||
|
||||
private static final String KEY_ENABLED = "enabled";
|
||||
private static final String KEY_INTERVAL_MINUTES = "interval_minutes";
|
||||
private static final String KEY_COOLDOWN_MINUTES = "cooldown_minutes";
|
||||
private static final String KEY_PREFER_ROOT = "prefer_root";
|
||||
private static final String KEY_RETURN_HOME = "return_home";
|
||||
private static final String KEY_KILL_BEFORE_LAUNCH = "kill_before_launch";
|
||||
private static final String KEY_STEALTH_MODE = "stealth_mode";
|
||||
|
||||
public static final int MODE_STEALTH = 0;
|
||||
public static final int MODE_AGGRESSIVE = 1;
|
||||
|
||||
public static final int WORK_MANAGER_MIN_MINUTES = 15;
|
||||
|
||||
public static final int[] INTERVAL_OPTIONS = {1, 3, 5, 10, 15, 30, 45, 60};
|
||||
public static final int[] COOLDOWN_OPTIONS = {1, 3, 5, 10, 15, 20, 30};
|
||||
|
||||
private KeepAliveSettings() {
|
||||
}
|
||||
|
||||
private static SharedPreferences prefs(Context context) {
|
||||
return context.getApplicationContext().getSharedPreferences(PREF, Context.MODE_PRIVATE);
|
||||
}
|
||||
|
||||
public static boolean isEnabled(Context context) {
|
||||
return prefs(context).getBoolean(KEY_ENABLED, AppConfig.ENABLE_MONITORED_APP_KEEP_ALIVE);
|
||||
}
|
||||
|
||||
public static void setEnabled(Context context, boolean enabled) {
|
||||
prefs(context).edit().putBoolean(KEY_ENABLED, enabled).apply();
|
||||
}
|
||||
|
||||
public static int getIntervalMinutes(Context context) {
|
||||
int value = prefs(context).getInt(KEY_INTERVAL_MINUTES, AppConfig.MONITORED_APP_KEEP_ALIVE_MINUTES);
|
||||
return Math.max(1, value);
|
||||
}
|
||||
|
||||
public static void setIntervalMinutes(Context context, int minutes) {
|
||||
prefs(context).edit().putInt(KEY_INTERVAL_MINUTES, Math.max(1, minutes)).apply();
|
||||
}
|
||||
|
||||
public static int getCooldownMinutes(Context context) {
|
||||
int value = prefs(context).getInt(KEY_COOLDOWN_MINUTES, AppConfig.MONITORED_APP_WAKE_COOLDOWN_MINUTES);
|
||||
return Math.max(1, value);
|
||||
}
|
||||
|
||||
public static void setCooldownMinutes(Context context, int minutes) {
|
||||
prefs(context).edit().putInt(KEY_COOLDOWN_MINUTES, Math.max(1, minutes)).apply();
|
||||
}
|
||||
|
||||
public static boolean preferRoot(Context context) {
|
||||
return prefs(context).getBoolean(KEY_PREFER_ROOT, AppConfig.KEEP_ALIVE_PREFER_ROOT);
|
||||
}
|
||||
|
||||
public static void setPreferRoot(Context context, boolean preferRoot) {
|
||||
prefs(context).edit().putBoolean(KEY_PREFER_ROOT, preferRoot).apply();
|
||||
}
|
||||
|
||||
public static boolean returnHome(Context context) {
|
||||
return prefs(context).getBoolean(KEY_RETURN_HOME, AppConfig.KEEP_ALIVE_RETURN_HOME);
|
||||
}
|
||||
|
||||
public static void setReturnHome(Context context, boolean returnHome) {
|
||||
prefs(context).edit().putBoolean(KEY_RETURN_HOME, returnHome).apply();
|
||||
}
|
||||
|
||||
/** 启动前 force-stop,确保冷启动并重新加载 Hook(会稍慢) */
|
||||
public static boolean killBeforeLaunch(Context context) {
|
||||
return prefs(context).getBoolean(KEY_KILL_BEFORE_LAUNCH, false);
|
||||
}
|
||||
|
||||
public static void setKillBeforeLaunch(Context context, boolean killBeforeLaunch) {
|
||||
prefs(context).edit().putBoolean(KEY_KILL_BEFORE_LAUNCH, killBeforeLaunch).apply();
|
||||
}
|
||||
|
||||
/** 静默:进程在就不弹 App;激进:每次都唤起界面 */
|
||||
public static boolean isStealthMode(Context context) {
|
||||
return prefs(context).getInt(KEY_STEALTH_MODE, MODE_STEALTH) == MODE_STEALTH;
|
||||
}
|
||||
|
||||
public static void setStealthMode(Context context, boolean stealth) {
|
||||
prefs(context).edit()
|
||||
.putInt(KEY_STEALTH_MODE, stealth ? MODE_STEALTH : MODE_AGGRESSIVE)
|
||||
.apply();
|
||||
}
|
||||
|
||||
public static int indexOfInterval(int minutes) {
|
||||
for (int i = 0; i < INTERVAL_OPTIONS.length; i++) {
|
||||
if (INTERVAL_OPTIONS[i] == minutes) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static int indexOfCooldown(int minutes) {
|
||||
for (int i = 0; i < COOLDOWN_OPTIONS.length; i++) {
|
||||
if (COOLDOWN_OPTIONS[i] == minutes) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,9 @@ public class BootReceiver extends BroadcastReceiver {
|
||||
String action = intent.getAction();
|
||||
if (Intent.ACTION_BOOT_COMPLETED.equals(action)) {
|
||||
new Handler(Looper.getMainLooper()).postDelayed(() -> {
|
||||
NotificationService.requestStartMonitoring(context);
|
||||
if (NotificationService.isMonitoringActive(context)) {
|
||||
NotificationService.requestStartMonitoring(context);
|
||||
}
|
||||
}, BOOT_DELAY_MS);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,336 @@
|
||||
package com.miraclegarden.smsmessage.service;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import com.miraclegarden.smsmessage.App;
|
||||
import com.miraclegarden.smsmessage.KeepAliveSettings;
|
||||
import com.miraclegarden.smsmessage.Activity.NotificationActivity;
|
||||
import com.miraclegarden.smsmessage.MessageInfo;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 唤醒监听列表中的 App,使其进程启动、Xposed Hook 重新加载。
|
||||
* Root 设备:monkey 轻量启动 + 可选返回桌面 + 电池白名单。
|
||||
*/
|
||||
public final class MonitoredAppActivator {
|
||||
|
||||
private static final String TAG = "MonitoredAppActivator";
|
||||
private static final String PREF = "keep_alive";
|
||||
private static final String KEY_LAST_WAKE_PREFIX = "last_wake_";
|
||||
|
||||
private MonitoredAppActivator() {
|
||||
}
|
||||
|
||||
public static void activateAllAsync(Context context) {
|
||||
if (!KeepAliveSettings.isEnabled(context)) {
|
||||
return;
|
||||
}
|
||||
Context appContext = context.getApplicationContext();
|
||||
new Thread(() -> activateAll(appContext), "MonitoredAppActivator").start();
|
||||
}
|
||||
|
||||
public static void activateAll(Context context) {
|
||||
if (!KeepAliveSettings.isEnabled(context)) {
|
||||
return;
|
||||
}
|
||||
if (!NotificationService.isMonitoringActive(context)) {
|
||||
Log.d(TAG, "skip activate: monitoring off");
|
||||
return;
|
||||
}
|
||||
|
||||
List<MessageInfo> apps = App.getNotiList(context);
|
||||
if (apps.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Log.i(TAG, "activating " + apps.size() + " monitored app(s)");
|
||||
NotificationActivity.sendMessage("正在唤醒监听 App(" + apps.size() + " 个)...");
|
||||
|
||||
int ok = 0;
|
||||
for (MessageInfo info : apps) {
|
||||
if (info == null || TextUtils.isEmpty(info.getPackageName())) {
|
||||
continue;
|
||||
}
|
||||
if (context.getPackageName().equals(info.getPackageName())) {
|
||||
continue;
|
||||
}
|
||||
if (activateOne(context, info)) {
|
||||
ok++;
|
||||
}
|
||||
}
|
||||
|
||||
NotificationService.toggleNotificationListenerService(context);
|
||||
NotificationActivity.sendMessage("App 保活完成: " + ok + "/" + apps.size());
|
||||
}
|
||||
|
||||
private static boolean activateOne(Context context, MessageInfo info) {
|
||||
String pkg = info.getPackageName();
|
||||
String label = !TextUtils.isEmpty(info.getAppName()) ? info.getAppName() : pkg;
|
||||
|
||||
if (isInCooldown(context, pkg)) {
|
||||
Log.d(TAG, "skip cooldown: " + pkg);
|
||||
NotificationActivity.sendMessage("[保活] " + label + " 冷却中,稍后重试");
|
||||
return false;
|
||||
}
|
||||
|
||||
applyBatteryWhitelist(pkg);
|
||||
boolean wasAlive = isProcessAlive(context, pkg);
|
||||
|
||||
if (wasAlive && KeepAliveSettings.isStealthMode(context)) {
|
||||
if (!isProcessCached(context, pkg)) {
|
||||
Log.d(TAG, "stealth skip (active): " + pkg);
|
||||
return true;
|
||||
}
|
||||
Log.i(TAG, "stealth headless wake (cached): " + pkg);
|
||||
if (wakeHeadlessByRoot(pkg)) {
|
||||
markWake(context, pkg);
|
||||
NotificationActivity.sendMessage("[保活] " + label + " 已唤醒后台进程(无界面)");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (KeepAliveSettings.killBeforeLaunch(context) && KeepAliveSettings.preferRoot(context)) {
|
||||
forceStopByRoot(pkg);
|
||||
sleepQuietly(800);
|
||||
wasAlive = false;
|
||||
}
|
||||
|
||||
boolean success = false;
|
||||
if (KeepAliveSettings.preferRoot(context)) {
|
||||
success = wakeByRoot(context, pkg);
|
||||
}
|
||||
if (!success) {
|
||||
success = wakeByLaunchIntent(context, pkg);
|
||||
}
|
||||
|
||||
if (success) {
|
||||
markWake(context, pkg);
|
||||
if (KeepAliveSettings.returnHome(context) && KeepAliveSettings.preferRoot(context)) {
|
||||
long homeDelay = KeepAliveSettings.isStealthMode(context) ? 400L : 1500L;
|
||||
sleepQuietly(homeDelay);
|
||||
pressHomeByRoot();
|
||||
}
|
||||
NotificationActivity.sendMessage("[保活] " + label + " 已启动");
|
||||
Log.i(TAG, "woke " + pkg);
|
||||
} else {
|
||||
NotificationActivity.sendMessage("[保活] " + label + " 唤起失败(检查 Root / Magisk 授权)");
|
||||
Log.w(TAG, "wake failed " + pkg);
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
private static void forceStopByRoot(String packageName) {
|
||||
try {
|
||||
Runtime.getRuntime().exec(new String[]{
|
||||
"su", "-c", "am force-stop " + packageName
|
||||
}).waitFor();
|
||||
} catch (Throwable t) {
|
||||
Log.w(TAG, "force-stop failed " + packageName + ": " + t.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isInCooldown(Context context, String packageName) {
|
||||
long last = context.getSharedPreferences(PREF, Context.MODE_PRIVATE)
|
||||
.getLong(KEY_LAST_WAKE_PREFIX + packageName, 0L);
|
||||
long cooldownMs = TimeUnit.MINUTES.toMillis(KeepAliveSettings.getCooldownMinutes(context));
|
||||
return System.currentTimeMillis() - last < cooldownMs;
|
||||
}
|
||||
|
||||
private static void markWake(Context context, String packageName) {
|
||||
context.getSharedPreferences(PREF, Context.MODE_PRIVATE)
|
||||
.edit()
|
||||
.putLong(KEY_LAST_WAKE_PREFIX + packageName, System.currentTimeMillis())
|
||||
.apply();
|
||||
}
|
||||
|
||||
private static boolean isProcessAlive(Context context, String packageName) {
|
||||
if (isProcessAliveByRoot(packageName)) {
|
||||
return true;
|
||||
}
|
||||
ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
|
||||
if (am == null) {
|
||||
return false;
|
||||
}
|
||||
List<ActivityManager.RunningAppProcessInfo> processes = am.getRunningAppProcesses();
|
||||
if (processes == null) {
|
||||
return false;
|
||||
}
|
||||
for (ActivityManager.RunningAppProcessInfo info : processes) {
|
||||
if (packageName.equals(info.processName)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** 进程在但已被系统冻结(cached),此时 TG 可能不处理推送。 */
|
||||
private static boolean isProcessCached(Context context, String packageName) {
|
||||
ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
|
||||
if (am == null) {
|
||||
return false;
|
||||
}
|
||||
List<ActivityManager.RunningAppProcessInfo> processes = am.getRunningAppProcesses();
|
||||
if (processes == null) {
|
||||
return false;
|
||||
}
|
||||
for (ActivityManager.RunningAppProcessInfo info : processes) {
|
||||
if (!packageName.equals(info.processName)) {
|
||||
continue;
|
||||
}
|
||||
return info.importance >= ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** 无界面唤醒 TG 前台 Service,让后台继续收推送。 */
|
||||
private static boolean wakeHeadlessByRoot(String packageName) {
|
||||
if (!packageName.contains("telegram")) {
|
||||
return false;
|
||||
}
|
||||
String component = packageName + "/org.telegram.messenger.NotificationsService";
|
||||
try {
|
||||
Process process = Runtime.getRuntime().exec(new String[]{
|
||||
"su", "-c", "am start-foreground-service -n " + component
|
||||
});
|
||||
if (process.waitFor() == 0) {
|
||||
return true;
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
Log.w(TAG, "headless fgs wake failed " + packageName + ": " + t.getMessage());
|
||||
}
|
||||
try {
|
||||
Process process = Runtime.getRuntime().exec(new String[]{
|
||||
"su", "-c", "am startservice -n " + component
|
||||
});
|
||||
return process.waitFor() == 0;
|
||||
} catch (Throwable t) {
|
||||
Log.w(TAG, "headless service wake failed " + packageName + ": " + t.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isProcessAliveByRoot(String packageName) {
|
||||
try {
|
||||
Process process = Runtime.getRuntime().exec(new String[]{"su", "-c", "pidof " + packageName});
|
||||
try (BufferedReader reader = new BufferedReader(
|
||||
new InputStreamReader(process.getInputStream()))) {
|
||||
String line = reader.readLine();
|
||||
return process.waitFor() == 0 && line != null && !line.trim().isEmpty();
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean wakeByRoot(Context context, String packageName) {
|
||||
String component = resolveLauncherComponent(context, packageName);
|
||||
if (!TextUtils.isEmpty(component)) {
|
||||
try {
|
||||
Process process = Runtime.getRuntime().exec(new String[]{
|
||||
"su", "-c",
|
||||
"am start -n " + component
|
||||
+ " -a android.intent.action.MAIN"
|
||||
+ " -c android.intent.category.LAUNCHER"
|
||||
+ " --activity-brought-to-front"
|
||||
+ " --activity-no-animation"
|
||||
});
|
||||
if (process.waitFor() == 0) {
|
||||
return true;
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
Log.w(TAG, "am start failed " + packageName + ": " + t.getMessage());
|
||||
}
|
||||
}
|
||||
try {
|
||||
Process process = Runtime.getRuntime().exec(new String[]{
|
||||
"su", "-c",
|
||||
"monkey -p " + packageName + " -c android.intent.category.LAUNCHER 1"
|
||||
});
|
||||
return process.waitFor() == 0;
|
||||
} catch (Throwable t) {
|
||||
Log.w(TAG, "monkey wake failed " + packageName + ": " + t.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static String resolveLauncherComponent(Context context, String packageName) {
|
||||
try {
|
||||
PackageManager pm = context.getPackageManager();
|
||||
Intent intent = pm.getLaunchIntentForPackage(packageName);
|
||||
if (intent != null && intent.getComponent() != null) {
|
||||
return intent.getComponent().flattenToShortString();
|
||||
}
|
||||
Intent query = new Intent(Intent.ACTION_MAIN);
|
||||
query.addCategory(Intent.CATEGORY_LAUNCHER);
|
||||
query.setPackage(packageName);
|
||||
List<ResolveInfo> list = pm.queryIntentActivities(query, 0);
|
||||
if (!list.isEmpty()) {
|
||||
return list.get(0).activityInfo.packageName + "/"
|
||||
+ list.get(0).activityInfo.name;
|
||||
}
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static boolean wakeByLaunchIntent(Context context, String packageName) {
|
||||
try {
|
||||
PackageManager pm = context.getPackageManager();
|
||||
Intent intent = pm.getLaunchIntentForPackage(packageName);
|
||||
if (intent == null) {
|
||||
Intent query = new Intent(Intent.ACTION_MAIN);
|
||||
query.addCategory(Intent.CATEGORY_LAUNCHER);
|
||||
query.setPackage(packageName);
|
||||
List<ResolveInfo> list = pm.queryIntentActivities(query, 0);
|
||||
if (list.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
intent = new Intent(Intent.ACTION_MAIN);
|
||||
intent.addCategory(Intent.CATEGORY_LAUNCHER);
|
||||
intent.setClassName(list.get(0).activityInfo.packageName, list.get(0).activityInfo.name);
|
||||
}
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
| Intent.FLAG_ACTIVITY_CLEAR_TOP
|
||||
| Intent.FLAG_ACTIVITY_NO_ANIMATION);
|
||||
context.startActivity(intent);
|
||||
return true;
|
||||
} catch (Throwable t) {
|
||||
Log.w(TAG, "launch wake failed " + packageName + ": " + t.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static void applyBatteryWhitelist(String packageName) {
|
||||
try {
|
||||
Runtime.getRuntime().exec(new String[]{
|
||||
"su", "-c", "dumpsys deviceidle whitelist +" + packageName
|
||||
}).waitFor();
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
private static void pressHomeByRoot() {
|
||||
try {
|
||||
Runtime.getRuntime().exec(new String[]{"su", "-c", "input keyevent 3"}).waitFor();
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
private static void sleepQuietly(long ms) {
|
||||
try {
|
||||
Thread.sleep(ms);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package com.miraclegarden.smsmessage.service;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
|
||||
import com.miraclegarden.smsmessage.KeepAliveSettings;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 间隔 < 15 分钟时用前台服务 Handler 定时(WorkManager 最短 15 分钟)。
|
||||
*/
|
||||
public final class MonitoredAppKeepAliveLoop {
|
||||
|
||||
private static final Handler HANDLER = new Handler(Looper.getMainLooper());
|
||||
private static Context appContext;
|
||||
|
||||
private static final Runnable TICK = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (appContext == null) {
|
||||
return;
|
||||
}
|
||||
if (!NotificationService.isMonitoringActive(appContext)) {
|
||||
stop();
|
||||
return;
|
||||
}
|
||||
if (!KeepAliveSettings.isEnabled(appContext)) {
|
||||
stop();
|
||||
return;
|
||||
}
|
||||
if (!usesFastLoop(appContext)) {
|
||||
stop();
|
||||
return;
|
||||
}
|
||||
MonitoredAppActivator.activateAllAsync(appContext);
|
||||
scheduleNext();
|
||||
}
|
||||
};
|
||||
|
||||
private MonitoredAppKeepAliveLoop() {
|
||||
}
|
||||
|
||||
public static boolean usesFastLoop(Context context) {
|
||||
return KeepAliveSettings.isEnabled(context)
|
||||
&& KeepAliveSettings.getIntervalMinutes(context) < KeepAliveSettings.WORK_MANAGER_MIN_MINUTES;
|
||||
}
|
||||
|
||||
public static void start(Context context) {
|
||||
if (!usesFastLoop(context)) {
|
||||
stop();
|
||||
return;
|
||||
}
|
||||
appContext = context.getApplicationContext();
|
||||
HANDLER.removeCallbacks(TICK);
|
||||
// 先尽快执行一次,再按间隔循环
|
||||
HANDLER.postDelayed(TICK, 3000);
|
||||
}
|
||||
|
||||
public static void stop() {
|
||||
HANDLER.removeCallbacks(TICK);
|
||||
appContext = null;
|
||||
}
|
||||
|
||||
public static void restart(Context context) {
|
||||
stop();
|
||||
start(context);
|
||||
}
|
||||
|
||||
private static void scheduleNext() {
|
||||
if (appContext == null) {
|
||||
return;
|
||||
}
|
||||
long delayMs = TimeUnit.MINUTES.toMillis(KeepAliveSettings.getIntervalMinutes(appContext));
|
||||
HANDLER.postDelayed(TICK, delayMs);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package com.miraclegarden.smsmessage.service;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.work.ExistingPeriodicWorkPolicy;
|
||||
import androidx.work.PeriodicWorkRequest;
|
||||
import androidx.work.WorkManager;
|
||||
|
||||
import com.miraclegarden.smsmessage.KeepAliveSettings;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public final class MonitoredAppKeepAliveScheduler {
|
||||
|
||||
private static final String WORK_NAME = "monitored_app_keep_alive";
|
||||
|
||||
private MonitoredAppKeepAliveScheduler() {
|
||||
}
|
||||
|
||||
public static void schedule(Context context) {
|
||||
Context appContext = context.getApplicationContext();
|
||||
if (!KeepAliveSettings.isEnabled(appContext)) {
|
||||
cancel(appContext);
|
||||
return;
|
||||
}
|
||||
|
||||
int interval = KeepAliveSettings.getIntervalMinutes(appContext);
|
||||
if (interval < KeepAliveSettings.WORK_MANAGER_MIN_MINUTES) {
|
||||
WorkManager.getInstance(appContext).cancelUniqueWork(WORK_NAME);
|
||||
MonitoredAppKeepAliveLoop.start(appContext);
|
||||
return;
|
||||
}
|
||||
|
||||
MonitoredAppKeepAliveLoop.stop();
|
||||
PeriodicWorkRequest request = new PeriodicWorkRequest.Builder(
|
||||
MonitoredAppKeepAliveWorker.class, interval, TimeUnit.MINUTES)
|
||||
.build();
|
||||
WorkManager.getInstance(appContext).enqueueUniquePeriodicWork(
|
||||
WORK_NAME,
|
||||
ExistingPeriodicWorkPolicy.UPDATE,
|
||||
request);
|
||||
}
|
||||
|
||||
public static void cancel(Context context) {
|
||||
Context appContext = context.getApplicationContext();
|
||||
WorkManager.getInstance(appContext).cancelUniqueWork(WORK_NAME);
|
||||
MonitoredAppKeepAliveLoop.stop();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.miraclegarden.smsmessage.service;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.work.Worker;
|
||||
import androidx.work.WorkerParameters;
|
||||
|
||||
import com.miraclegarden.smsmessage.KeepAliveSettings;
|
||||
|
||||
public class MonitoredAppKeepAliveWorker extends Worker {
|
||||
|
||||
private static final String TAG = "MonitoredAppKeepAlive";
|
||||
|
||||
public MonitoredAppKeepAliveWorker(@NonNull Context context, @NonNull WorkerParameters params) {
|
||||
super(context, params);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Result doWork() {
|
||||
if (!KeepAliveSettings.isEnabled(getApplicationContext())) {
|
||||
return Result.success();
|
||||
}
|
||||
if (!NotificationService.isMonitoringActive(getApplicationContext())) {
|
||||
Log.d(TAG, "monitoring off, skip");
|
||||
return Result.success();
|
||||
}
|
||||
Log.i(TAG, "periodic keep-alive tick");
|
||||
MonitoredAppActivator.activateAll(getApplicationContext());
|
||||
return Result.success();
|
||||
}
|
||||
}
|
||||
@@ -2,24 +2,22 @@ package com.miraclegarden.smsmessage.service;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
import android.service.notification.StatusBarNotification;
|
||||
import android.text.TextUtils;
|
||||
|
||||
/**
|
||||
* @Author wchino
|
||||
* 创建时间 2026/03/30
|
||||
* 用途:通知内容多层提取工具,兼容不同厂商字段差异
|
||||
* 通知内容多层提取工具,兼容不同厂商字段差异。
|
||||
* Telegram 常用 InboxStyle / MessagingStyle,不能只读 EXTRA_TEXT。
|
||||
*/
|
||||
public class NotificationExtractor {
|
||||
|
||||
private static final String EXTRA_MESSAGES = "android.messages";
|
||||
private static final String EXTRA_CONVERSATION_TITLE = "android.conversationTitle";
|
||||
|
||||
private NotificationExtractor() {
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author wchino
|
||||
* 创建时间 2026/03/30
|
||||
* 用途:提取通知标题、内容和提取时间戳
|
||||
*/
|
||||
public static Result extract(StatusBarNotification sbn) {
|
||||
long timestamp = System.currentTimeMillis();
|
||||
if (sbn == null || sbn.getNotification() == null) {
|
||||
@@ -35,14 +33,18 @@ public class NotificationExtractor {
|
||||
if (extras != null) {
|
||||
title = pickFirstNonEmpty(
|
||||
extras.getCharSequence(Notification.EXTRA_TITLE),
|
||||
extras.getCharSequence(Notification.EXTRA_TITLE_BIG)
|
||||
extras.getCharSequence(Notification.EXTRA_TITLE_BIG),
|
||||
extras.getCharSequence(EXTRA_CONVERSATION_TITLE),
|
||||
extras.getCharSequence(Notification.EXTRA_SUMMARY_TEXT)
|
||||
);
|
||||
|
||||
content = pickFirstNonEmpty(
|
||||
extras.getCharSequence(Notification.EXTRA_TEXT),
|
||||
extras.getCharSequence(Notification.EXTRA_BIG_TEXT),
|
||||
extras.getCharSequence(Notification.EXTRA_INFO_TEXT),
|
||||
extras.getCharSequence(Notification.EXTRA_SUB_TEXT)
|
||||
extras.getCharSequence(Notification.EXTRA_SUB_TEXT),
|
||||
extractMessagingStyleContent(extras),
|
||||
extractInboxLines(extras)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -54,16 +56,61 @@ public class NotificationExtractor {
|
||||
content = normalized(notification.tickerText);
|
||||
}
|
||||
|
||||
if (title == null) {
|
||||
title = "";
|
||||
}
|
||||
if (content == null) {
|
||||
content = "";
|
||||
if (TextUtils.isEmpty(content) && !TextUtils.isEmpty(title)
|
||||
&& normalized(notification.tickerText).startsWith(title)) {
|
||||
String ticker = normalized(notification.tickerText);
|
||||
if (ticker.length() > title.length()) {
|
||||
content = ticker.substring(title.length()).trim();
|
||||
}
|
||||
}
|
||||
|
||||
return new Result(title, content, timestamp);
|
||||
}
|
||||
|
||||
/** Telegram 等 App 的 InboxStyle:最新消息在 textLines 末尾。 */
|
||||
private static String extractInboxLines(Bundle extras) {
|
||||
CharSequence[] lines = extras.getCharSequenceArray(Notification.EXTRA_TEXT_LINES);
|
||||
if (lines == null || lines.length == 0) {
|
||||
return "";
|
||||
}
|
||||
for (int i = lines.length - 1; i >= 0; i--) {
|
||||
String line = normalized(lines[i]);
|
||||
if (!TextUtils.isEmpty(line)) {
|
||||
return line;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/** MessagingStyle:从 android.messages 取最新一条正文。 */
|
||||
private static String extractMessagingStyleContent(Bundle extras) {
|
||||
Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES);
|
||||
if (messages == null || messages.length == 0) {
|
||||
return "";
|
||||
}
|
||||
for (int i = messages.length - 1; i >= 0; i--) {
|
||||
String text = extractMessagingMessageText(messages[i]);
|
||||
if (!TextUtils.isEmpty(text)) {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
private static String extractMessagingMessageText(Parcelable parcelable) {
|
||||
if (parcelable == null) {
|
||||
return "";
|
||||
}
|
||||
if (parcelable instanceof Bundle) {
|
||||
Bundle bundle = (Bundle) parcelable;
|
||||
return pickFirstNonEmpty(
|
||||
bundle.getCharSequence("text"),
|
||||
bundle.getCharSequence(Notification.EXTRA_TEXT)
|
||||
);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
private static String pickFirstNonEmpty(CharSequence... values) {
|
||||
if (values == null || values.length == 0) {
|
||||
return "";
|
||||
@@ -103,11 +150,6 @@ public class NotificationExtractor {
|
||||
return ticker;
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author wchino
|
||||
* 创建时间 2026/03/30
|
||||
* 用途:通知提取结果对象
|
||||
*/
|
||||
public static class Result {
|
||||
public String title;
|
||||
public String content;
|
||||
|
||||
@@ -27,6 +27,7 @@ import androidx.core.app.NotificationCompat;
|
||||
import com.miraclegarden.smsmessage.Activity.NotificationActivity;
|
||||
import com.miraclegarden.smsmessage.App;
|
||||
import com.miraclegarden.smsmessage.AppConfig;
|
||||
import com.miraclegarden.smsmessage.KeepAliveSettings;
|
||||
import com.miraclegarden.smsmessage.MessageInfo;
|
||||
import com.miraclegarden.smsmessage.R;
|
||||
import com.miraclegarden.smsmessage.model.ApiError;
|
||||
@@ -65,6 +66,8 @@ public class NotificationService extends NotificationListenerService {
|
||||
private RetryManager retryManager;
|
||||
private PowerManager.WakeLock wakeLock;
|
||||
private static boolean isMonitoring = false;
|
||||
private static final String PREF_SERVER = "server";
|
||||
private static final String KEY_MONITORING_ACTIVE = "monitoring_active";
|
||||
private ApiService apiService;
|
||||
private TokenManager tokenManager;
|
||||
|
||||
@@ -100,6 +103,8 @@ public class NotificationService extends NotificationListenerService {
|
||||
} else if (ACTION_HOOK_MESSAGE.equals(intent.getAction())) {
|
||||
handleHookMessageIntent(intent);
|
||||
}
|
||||
} else if (isMonitoringActive(this)) {
|
||||
MonitoredAppKeepAliveScheduler.schedule(this);
|
||||
}
|
||||
NotificationActivity.sendMessage("监听服务成功!");
|
||||
return START_STICKY;
|
||||
@@ -146,20 +151,41 @@ public class NotificationService extends NotificationListenerService {
|
||||
return isMonitoring;
|
||||
}
|
||||
|
||||
public static boolean isMonitoringActive(Context context) {
|
||||
if (isMonitoring) {
|
||||
return true;
|
||||
}
|
||||
return context.getSharedPreferences(PREF_SERVER, Context.MODE_PRIVATE)
|
||||
.getBoolean(KEY_MONITORING_ACTIVE, false);
|
||||
}
|
||||
|
||||
private static void setMonitoringActive(Context context, boolean active) {
|
||||
isMonitoring = active;
|
||||
context.getSharedPreferences(PREF_SERVER, Context.MODE_PRIVATE)
|
||||
.edit()
|
||||
.putBoolean(KEY_MONITORING_ACTIVE, active)
|
||||
.apply();
|
||||
}
|
||||
|
||||
private void startMonitoring() {
|
||||
if (isMonitoring) return;
|
||||
isMonitoring = true;
|
||||
setMonitoringActive(this, true);
|
||||
|
||||
toggleNotificationListenerService(this);
|
||||
MonitoredAppKeepAliveScheduler.schedule(this);
|
||||
if (KeepAliveSettings.isEnabled(this)) {
|
||||
MonitoredAppActivator.activateAllAsync(this);
|
||||
}
|
||||
updateForegroundNotification(retryManager != null
|
||||
? (AppConfig.ENABLE_SERVER_UPLOAD ? retryManager.getUploadedCount() : retryManager.getTotalCount())
|
||||
: 0);
|
||||
NotificationActivity.sendMessage("已开启持续监听模式");
|
||||
NotificationActivity.sendMessage("已开启持续监听模式(含 App 保活)");
|
||||
NotificationActivity.updateUI();
|
||||
}
|
||||
|
||||
public static void stopMonitoring(Context context) {
|
||||
isMonitoring = false;
|
||||
setMonitoringActive(context, false);
|
||||
MonitoredAppKeepAliveScheduler.cancel(context);
|
||||
NotificationActivity.sendMessage("已停止监听服务");
|
||||
NotificationActivity.updateUI();
|
||||
Toast.makeText(context, "监听服务已停止", Toast.LENGTH_SHORT).show();
|
||||
@@ -205,11 +231,13 @@ public class NotificationService extends NotificationListenerService {
|
||||
if (messageInfo == null) return;
|
||||
|
||||
NotificationExtractor.Result result = NotificationExtractor.extract(sbn);
|
||||
Log.d(TAG, "onNotificationPosted: pkg=" + sbn.getPackageName()
|
||||
Log.i(TAG, "onNotificationPosted: pkg=" + sbn.getPackageName()
|
||||
+ " title=" + result.title + " content=" + result.content);
|
||||
|
||||
if (TextUtils.isEmpty(result.title) && TextUtils.isEmpty(result.content)) {
|
||||
Log.d(TAG, "skip empty notification: " + sbn.getPackageName());
|
||||
Log.w(TAG, "skip empty notification: " + sbn.getPackageName()
|
||||
+ " extras=" + (sbn.getNotification().extras != null
|
||||
? sbn.getNotification().extras.keySet() : "null"));
|
||||
NotificationActivity.sendMessage("[" + messageInfo.getAppName() + "] 通知内容为空,跳过");
|
||||
return;
|
||||
}
|
||||
@@ -342,7 +370,6 @@ public class NotificationService extends NotificationListenerService {
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
isMonitoring = false;
|
||||
NotificationActivity.updateUI();
|
||||
if (wakeLock != null && wakeLock.isHeld()) {
|
||||
wakeLock.release();
|
||||
|
||||
Reference in New Issue
Block a user