feat(mmp): 领取人ID/模块版本检测,并修正状态条误报红

展示 receiverId 与 activityPoolId;检测 Hook 是否最新;按真实时间排序;有心跳与定时拉取时不再因久无新数据爆红。
This commit is contained in:
mars
2026-08-05 15:09:51 +08:00
parent a4aed01b29
commit 293979122d
24 changed files with 1641 additions and 186 deletions

View File

@@ -4,11 +4,18 @@ public final class HookBridge {
public static final String TARGET_APP_PACKAGE = "com.miraclegarden.smsmessage";
public static final String ACTION_HOOK_MESSAGE = "com.miraclegarden.smsmessage.action.HOOK_MESSAGE";
public static final String ACTION_HOOK_STATUS = "com.miraclegarden.smsmessage.action.HOOK_STATUS";
public static final String EXTRA_PACKAGE_NAME = "packageName";
public static final String EXTRA_TITLE = "title";
public static final String EXTRA_CONTENT = "content";
public static final String EXTRA_TIMESTAMP = "timestamp";
public static final String EXTRA_SOURCE = "source";
public static final String EXTRA_MODULE_VERSION_CODE = "moduleVersionCode";
public static final String EXTRA_MODULE_VERSION_NAME = "moduleVersionName";
public static final String EXTRA_HOST_PACKAGE = "hostPackage";
/** 与 xposed-module/build.gradle 保持同步 */
public static final int MODULE_VERSION_CODE = 3;
public static final String MODULE_VERSION_NAME = "1.2.0";
public static final String SOURCE_XPOSED_SQLITE = "xposed_sqlite";
public static final String SOURCE_XPOSED_WECHAT = "xposed_wechat";
public static final String SOURCE_XPOSED_TELEGRAM = "xposed_telegram";

View File

@@ -33,4 +33,23 @@ public final class HookForwarder {
XposedBridge.log(TAG + " sendBroadcast failed: " + t.getMessage());
}
}
/** 向主 App 汇报当前进程内 Hook 模块版本(用于检测是否最新) */
public static void reportStatus(Context context, String hostPackage, String source) {
if (context == null) {
return;
}
try {
Intent intent = new Intent(HookBridge.ACTION_HOOK_STATUS);
intent.setPackage(HookBridge.TARGET_APP_PACKAGE);
intent.putExtra(HookBridge.EXTRA_HOST_PACKAGE, hostPackage);
intent.putExtra(HookBridge.EXTRA_SOURCE, source);
intent.putExtra(HookBridge.EXTRA_MODULE_VERSION_CODE, HookBridge.MODULE_VERSION_CODE);
intent.putExtra(HookBridge.EXTRA_MODULE_VERSION_NAME, HookBridge.MODULE_VERSION_NAME);
intent.putExtra(HookBridge.EXTRA_TIMESTAMP, System.currentTimeMillis());
context.sendBroadcast(intent);
} catch (Throwable t) {
XposedBridge.log(TAG + " reportStatus failed: " + t.getMessage());
}
}
}

View File

@@ -77,9 +77,17 @@ public final class TngMoneyPacketHook {
private static volatile long sPacketDedupMs = DEFAULT_PACKET_DEDUP_MS;
private static volatile long sDetailGapMs = DEFAULT_DETAIL_GAP_MS;
private static volatile boolean sOpenHistoryIfNoTemplate = true;
/** 断线/拉历史失败时:自动进一下历史页再返回,重建模板与连接 */
private static volatile boolean sAutoBounceHistoryOnDisconnect = true;
private static volatile long sLastSettingsPollAt;
private static volatile long sLastAutoHistoryAt;
private static volatile boolean sAutoHistoryRunning;
private static volatile long sLastHistoryBounceAt;
private static volatile boolean sBounceHistoryPending;
private static volatile long sLastHookStatusAt;
private static final long HISTORY_BOUNCE_COOLDOWN_MS = 45_000L;
private static final long HISTORY_BOUNCE_FINISH_DELAY_MS = 2_200L;
private static final long HOOK_STATUS_COOLDOWN_MS = 20_000L;
/** 曾拿到过 session之后变空 / RPC 鉴权失败 → 视为断线,恢复后强制重拉 */
private static volatile boolean sHadSession;
private static volatile boolean sSessionDisconnected;
@@ -119,7 +127,37 @@ public final class TngMoneyPacketHook {
hookLoginStorage(lpparam);
hookActivityResumeForAutoHistory(lpparam);
startSettingsPoller();
XposedBridge.log(TAG + " installed for " + lpparam.packageName);
startAutoHistoryPoller();
reportHookAlive("install");
XposedBridge.log(TAG + " installed for " + lpparam.packageName
+ " mod=" + HookBridge.MODULE_VERSION_NAME
+ "/" + HookBridge.MODULE_VERSION_CODE);
}
/** 按历史冷却定时拉列表 + 心跳,避免无新红包时状态条误报「久无数据」 */
private static void startAutoHistoryPoller() {
Thread t = new Thread(() -> {
while (true) {
try {
reportHookAlive("poll");
scheduleAutoHistory("poll");
long sleepMs = Math.max(5_000L, sHistoryCooldownMs);
Thread.sleep(sleepMs);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
return;
} catch (Throwable ignored) {
try {
Thread.sleep(Math.max(5_000L, sHistoryCooldownMs));
} catch (InterruptedException ie2) {
Thread.currentThread().interrupt();
return;
}
}
}
}, "TngMmp-auto-hist-poll");
t.setDaemon(true);
t.start();
}
/** 后台轮询领取台 /api/mmp/settings约 10 秒内生效 */
@@ -186,20 +224,24 @@ public final class TngMoneyPacketHook {
long dedupMs = Math.max(0, json.optInt("dedupSec", 3)) * 1000L;
long gapMs = Math.max(0, json.optInt("detailGapMs", 80));
boolean openHistory = json.optBoolean("openHistoryIfNoTemplate", true);
boolean bounceHistory = json.optBoolean("autoBounceHistoryOnDisconnect", true);
boolean changed = historyMs != sHistoryCooldownMs
|| detailMs != sDetailCooldownMs
|| dedupMs != sPacketDedupMs
|| gapMs != sDetailGapMs
|| openHistory != sOpenHistoryIfNoTemplate;
|| openHistory != sOpenHistoryIfNoTemplate
|| bounceHistory != sAutoBounceHistoryOnDisconnect;
sHistoryCooldownMs = historyMs;
sDetailCooldownMs = detailMs;
sPacketDedupMs = dedupMs;
sDetailGapMs = gapMs;
sOpenHistoryIfNoTemplate = openHistory;
sAutoBounceHistoryOnDisconnect = bounceHistory;
if (changed) {
XposedBridge.log(TAG + " settings applied hist=" + (historyMs / 1000)
+ "s detail=" + (detailMs / 1000) + "s dedup=" + (dedupMs / 1000)
+ "s gap=" + gapMs + "ms openHist=" + openHistory
+ " bounceHist=" + bounceHistory
+ " from " + urlStr);
}
return true;
@@ -400,7 +442,7 @@ public final class TngMoneyPacketHook {
}
MmpSnapshot probe = parseSnapshot(json);
enrichSnapshotFromJavaResult(result, probe);
if (probe != null && !probe.claims.isEmpty()) {
if (probe != null && (!probe.claims.isEmpty() || snapshotHasPacketMeta(probe))) {
if (detailReq != null) {
sTemplateDetailRequest = cloneRequestShallow(detailReq);
sLoginDonorRequest = detailReq;
@@ -979,16 +1021,20 @@ public final class TngMoneyPacketHook {
}
private static void rememberPacketData(String packetId, MmpSnapshot snapshot, Object result) {
if (snapshot == null || snapshot.claims == null || snapshot.claims.isEmpty()) {
if (snapshot == null) {
return;
}
boolean hasClaims = snapshot.claims != null && !snapshot.claims.isEmpty();
if (!hasClaims && !snapshotHasPacketMeta(snapshot)) {
return;
}
String id = !TextUtils.isEmpty(packetId) ? packetId : snapshot.packetId;
if (TextUtils.isEmpty(id) || !looksLikeActivityId(id)) {
// 指纹 id 也记,避免反复刷
if (!TextUtils.isEmpty(id)) {
if (!TextUtils.isEmpty(id) && hasClaims) {
PACKETS_WITH_DATA.add(id);
}
} else {
} else if (hasClaims) {
PACKETS_WITH_DATA.add(id);
}
if (result != null
@@ -1000,7 +1046,7 @@ public final class TngMoneyPacketHook {
if (!snapshot.finished) {
snapshot.finished = evaluateFinished(snapshot);
}
if (snapshot.finished && !TextUtils.isEmpty(id)) {
if (snapshot.finished && hasClaims && !TextUtils.isEmpty(id)) {
if (COMPLETED_PACKETS.add(id)) {
XposedBridge.log(TAG + " packet DONE stop-refresh id=" + id
+ " claims=" + snapshot.claims.size()
@@ -1068,7 +1114,13 @@ public final class TngMoneyPacketHook {
}
private static void notePossibleDisconnectFromEmpty(Object result, String activityId) {
// totalCount:0 + 无领取人 常见于缺登录态
// 新发红包无人领取时 activityPoolInfos 为空、totalCount/claimedCount 也常为 0不是断线
if (looksLikeValidEmptyPacket(result, activityId)) {
XposedBridge.log(TAG + " empty claims but valid summary id=" + activityId
+ " → keep (0 claimants)");
return;
}
// totalCount:0 + 无领取人 且无有效摘要时,才怀疑缺登录态
String totalCnt = stringField(result, "totalCount", "getTotalCount");
if ("0".equals(totalCnt) && sHadSession) {
XposedBridge.log(TAG + " empty claims+totalCount0 id=" + activityId
@@ -1078,6 +1130,64 @@ public final class TngMoneyPacketHook {
}
}
/** 详情有 summaryACTIVE / 有总额)但领取名单为空:刚发出去、还没人领 */
private static boolean looksLikeValidEmptyPacket(Object result, String activityId) {
if (result == null) {
return false;
}
try {
Object summary = null;
try {
summary = XposedHelpers.callMethod(result, "getSummaryInfo");
} catch (Throwable ignored) {
}
if (summary == null) {
try {
summary = XposedHelpers.getObjectField(result, "summaryInfo");
} catch (Throwable ignored) {
}
}
if (summary == null) {
return false;
}
String id = firstNonEmpty(
stringField(summary, "activityId", "getActivityId"),
activityId);
if (!looksLikeActivityId(id)) {
return false;
}
String status = stringField(summary, "activityStatus", "getActivityStatus");
if (!TextUtils.isEmpty(status)) {
String up = status.toUpperCase(Locale.US);
if (up.contains("ACTIVE") || up.contains("OPEN") || up.contains("PROGRESS")
|| up.contains("FINISH") || up.contains("COMPLETE")
|| up.contains("EXPIRE")) {
return true;
}
}
String total = null;
try {
Object money = XposedHelpers.callMethod(summary, "getTotalAmount");
if (money != null) {
total = normalizeMoneyText(String.valueOf(
XposedHelpers.callMethod(money, "getAmount")));
}
} catch (Throwable ignored) {
}
return !TextUtils.isEmpty(total);
} catch (Throwable ignored) {
return false;
}
}
private static boolean snapshotHasPacketMeta(MmpSnapshot snapshot) {
return snapshot != null
&& looksLikeActivityId(snapshot.packetId)
&& (!TextUtils.isEmpty(snapshot.totalAmount)
|| !TextUtils.isEmpty(snapshot.activityStatus)
|| !TextUtils.isEmpty(snapshot.senderName));
}
private static boolean fetchDetailByActivityId(String activityId, String senderUserId) {
ClassLoader cl = sAppClassLoader;
ensureRpcTaskAndLogin(cl);
@@ -1131,13 +1241,31 @@ public final class TngMoneyPacketHook {
}
MmpSnapshot probe = parseSnapshot(json);
enrichSnapshotFromJavaResult(result, probe);
if (probe == null || probe.claims.isEmpty()) {
if (probe == null) {
XposedBridge.log(TAG + " auto-detail parse null id=" + activityId);
return false;
}
if (TextUtils.isEmpty(probe.packetId)) {
probe.packetId = activityId;
}
if (probe.claims.isEmpty()) {
String snippet = json.length() > 240 ? json.substring(0, 240) + "..." : json;
XposedBridge.log(TAG + " auto-detail empty claims id=" + activityId
+ " body=" + snippet
+ " userId=" + stringField(req, "userId", "getUserId"));
notePossibleDisconnectFromEmpty(result, activityId);
return false;
if (!snapshotHasPacketMeta(probe) && !looksLikeValidEmptyPacket(result, activityId)) {
notePossibleDisconnectFromEmpty(result, activityId);
return false;
}
// 刚发出、还没人领:照样入库,方便列表看到「领取中 0人」
rememberPacketData(probe.packetId, probe, result);
forwardSnapshot(probe, "auto:" + activityId, "rpc");
XposedBridge.log(TAG + " auto-detail ok(empty) activityId=" + activityId
+ " status=" + probe.activityStatus
+ " total=" + probe.totalAmount
+ " issued=" + probe.issueTime);
noteSessionAlive();
return true;
}
rememberPacketData(probe.packetId != null ? probe.packetId : activityId, probe, result);
forwardSnapshot(probe, "auto:" + activityId, "rpc");
@@ -1329,6 +1457,12 @@ public final class TngMoneyPacketHook {
if (!PACKAGE.equals(pkg)) {
return;
}
String actName = act.getClass().getName();
if (actName.contains("MoneyPacketHistoryActivity")
&& sBounceHistoryPending) {
scheduleHistoryBounceFinish(act);
}
reportHookAlive("activity-resume");
if (sSessionDisconnected) {
tryRefreshLogin(sAppClassLoader);
scheduleAutoHistoryForced("reconnect-resume");
@@ -1373,6 +1507,7 @@ public final class TngMoneyPacketHook {
if (ok) {
sLastAutoHistoryAt = System.currentTimeMillis();
noteSessionAlive();
reportHookAlive("auto-history-ok");
}
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
@@ -1389,6 +1524,7 @@ public final class TngMoneyPacketHook {
Object task = sMoneyPacketRpcTask;
if (cl == null || task == null) {
XposedBridge.log(TAG + " auto-history skip: no rpc task (" + reason + ")");
maybeBounceHistoryPage("no-rpc-task");
return false;
}
if (TextUtils.isEmpty(sCachedSessionId)) {
@@ -1402,6 +1538,7 @@ public final class TngMoneyPacketHook {
if (sHadSession) {
sSessionDisconnected = true;
}
maybeBounceHistoryPage("no-session");
return false;
}
try {
@@ -1422,24 +1559,33 @@ public final class TngMoneyPacketHook {
} finally {
AUTO_HISTORY_SELF.remove();
}
if (result != null && historyHasJobs(result)) {
XposedBridge.log(TAG + " auto-history ok(template) → schedule details");
scheduleAutoDetailsFromHistory(result);
if (result != null) {
if (historyHasJobs(result)) {
XposedBridge.log(TAG + " auto-history ok(template) → schedule details");
scheduleAutoDetailsFromHistory(result);
} else {
XposedBridge.log(TAG + " auto-history ok(template) but empty list");
}
return true;
}
// 有模板但 RPC 失败:不要误当成「无模板」反复乱跳;按断线处理并短进历史页
XposedBridge.log(TAG + " auto-history template rpc failed (" + reason + ")");
maybeBounceHistoryPage("template-rpc-fail");
return false;
}
// 2) 无模板时:自造请求易「非法参数」,直接拉起官方历史页让 App 发正确 RPC
if (!sOpenHistoryIfNoTemplate) {
if (!sOpenHistoryIfNoTemplate && !sAutoBounceHistoryOnDisconnect) {
XposedBridge.log(TAG + " auto-history no template, openHistory disabled ("
+ reason + ")");
return false;
}
XposedBridge.log(TAG + " auto-history no template → open HistoryActivity (" + reason + ")");
openMoneyPacketHistoryActivity();
openMoneyPacketHistoryActivity(true);
return false;
} catch (Throwable t) {
XposedBridge.log(TAG + " auto-history fail: " + t.getMessage());
markMaybeDisconnected(t, "auto-history");
maybeBounceHistoryPage("history-exception");
return false;
}
}
@@ -1448,11 +1594,32 @@ public final class TngMoneyPacketHook {
return !extractHistoryJobs(historyResult).isEmpty();
}
/** 拉起官方历史页App 自己的 moneyPacketHistoryList 成功后 Hook 会缓存模板并级联拉详情 */
private static void openMoneyPacketHistoryActivity() {
/** 断线/失败时短进历史页再建连(有冷却,避免刷屏) */
private static void maybeBounceHistoryPage(String reason) {
if (!sAutoBounceHistoryOnDisconnect) {
XposedBridge.log(TAG + " bounce history disabled (" + reason + ")");
return;
}
openMoneyPacketHistoryActivity(true);
XposedBridge.log(TAG + " bounce history for " + reason);
}
/** 拉起官方历史页bounceBack=true 时约 2 秒后自动 finish 返回 */
private static void openMoneyPacketHistoryActivity(boolean bounceBack) {
try {
long now = System.currentTimeMillis();
if (bounceBack) {
if (now - sLastHistoryBounceAt < HISTORY_BOUNCE_COOLDOWN_MS) {
XposedBridge.log(TAG + " bounce history cooldown skip ageMs="
+ (now - sLastHistoryBounceAt));
return;
}
sLastHistoryBounceAt = now;
sBounceHistoryPending = true;
}
Context ctx = getContext();
if (ctx == null) {
sBounceHistoryPending = false;
return;
}
android.content.Intent intent = new android.content.Intent();
@@ -1461,17 +1628,43 @@ public final class TngMoneyPacketHook {
intent.addFlags(android.content.Intent.FLAG_ACTIVITY_NEW_TASK
| android.content.Intent.FLAG_ACTIVITY_NO_ANIMATION);
ctx.startActivity(intent);
XposedBridge.log(TAG + " started MoneyPacketHistoryActivity");
XposedBridge.log(TAG + " started MoneyPacketHistoryActivity bounce=" + bounceBack);
} catch (Throwable t) {
sBounceHistoryPending = false;
XposedBridge.log(TAG + " start HistoryActivity fail: " + t.getMessage());
}
}
private static void scheduleHistoryBounceFinish(final Object activity) {
if (!(activity instanceof android.app.Activity)) {
return;
}
final android.app.Activity act = (android.app.Activity) activity;
MAIN_HANDLER.postDelayed(() -> {
try {
if (!sBounceHistoryPending) {
return;
}
if (act.isFinishing()) {
sBounceHistoryPending = false;
return;
}
act.finish();
XposedBridge.log(TAG + " bounce history finished → back");
} catch (Throwable t) {
XposedBridge.log(TAG + " bounce history finish fail: " + t.getMessage());
} finally {
sBounceHistoryPending = false;
}
}, HISTORY_BOUNCE_FINISH_DELAY_MS);
}
private static Object callHistoryListRpc(Object task, Object req) {
try {
return XposedHelpers.callMethod(task, "moneyPacketHistoryList", req);
} catch (Throwable t) {
XposedBridge.log(TAG + " history rpc fail: " + t.getMessage());
markMaybeDisconnected(t, "history-rpc");
return null;
}
}
@@ -1688,7 +1881,10 @@ public final class TngMoneyPacketHook {
}
private static void forwardSnapshot(MmpSnapshot snapshot, String sourceHint, String channel) {
if (snapshot == null || snapshot.claims.isEmpty()) {
if (snapshot == null) {
return;
}
if (snapshot.claims.isEmpty() && !snapshotHasPacketMeta(snapshot)) {
return;
}
dedupeClaims(snapshot);
@@ -1739,6 +1935,11 @@ public final class TngMoneyPacketHook {
continue;
}
String name = stringField(info, "receiverName", "getReceiverName");
String receiverId = firstNonEmpty(
stringField(info, "receiverId", "getReceiverId"),
stringField(info, "userId", "getUserId"),
stringField(info, "receiverUserId", "getReceiverUserId"));
String poolId = stringField(info, "activityPoolId", "getActivityPoolId");
String claimTime = firstNonEmpty(
stringField(info, "gmtModified", "getGmtModified"),
stringField(info, "gmtCreate", "getGmtCreate"),
@@ -1755,21 +1956,36 @@ public final class TngMoneyPacketHook {
if (TextUtils.isEmpty(amount)) {
amount = normalizeMoneyText(stringField(info, "amount", "getAmount"));
}
if (TextUtils.isEmpty(name)) {
if (TextUtils.isEmpty(name) && TextUtils.isEmpty(receiverId)) {
continue;
}
if (TextUtils.isEmpty(name)) {
name = "用户";
}
boolean matched = false;
for (ClaimLine line : snapshot.claims) {
if (name.equals(line.nickname)) {
boolean sameId = !TextUtils.isEmpty(receiverId)
&& receiverId.equals(line.receiverId);
boolean sameName = TextUtils.isEmpty(receiverId)
&& name.equals(line.nickname);
if (sameId || sameName) {
if (TextUtils.isEmpty(line.claimTime) && !TextUtils.isEmpty(claimTime)) {
line.claimTime = claimTime;
}
if (TextUtils.isEmpty(line.receiverId) && !TextUtils.isEmpty(receiverId)) {
line.receiverId = receiverId;
}
if (TextUtils.isEmpty(line.poolId) && !TextUtils.isEmpty(poolId)) {
line.poolId = poolId;
}
matched = true;
}
}
if (!matched && !TextUtils.isEmpty(amount)) {
ClaimLine line = new ClaimLine();
line.nickname = name;
line.receiverId = receiverId;
line.poolId = poolId;
line.amount = amount;
line.claimTime = claimTime;
snapshot.claims.add(line);
@@ -1860,30 +2076,43 @@ public final class TngMoneyPacketHook {
}
java.util.LinkedHashMap<String, ClaimLine> map = new java.util.LinkedHashMap<>();
for (ClaimLine line : snapshot.claims) {
if (line == null || TextUtils.isEmpty(line.nickname)) {
if (line == null) {
continue;
}
ClaimLine old = map.get(line.nickname);
if (TextUtils.isEmpty(line.nickname) && TextUtils.isEmpty(line.receiverId)) {
continue;
}
String key = !TextUtils.isEmpty(line.receiverId)
? ("id:" + line.receiverId)
: ("n:" + line.nickname);
ClaimLine old = map.get(key);
if (old == null) {
map.put(line.nickname, line);
map.put(key, line);
continue;
}
// 同昵称保留金额更大的;时间有则补上
try {
double a = Double.parseDouble(old.amount);
double b = Double.parseDouble(line.amount);
if (b > a) {
if (TextUtils.isEmpty(line.claimTime)) {
line.claimTime = old.claimTime;
}
map.put(line.nickname, line);
} else if (TextUtils.isEmpty(old.claimTime) && !TextUtils.isEmpty(line.claimTime)) {
old.claimTime = line.claimTime;
double oldAmt = parseMoneyDouble(old.amount) != null ? parseMoneyDouble(old.amount) : -1;
double newAmt = parseMoneyDouble(line.amount) != null ? parseMoneyDouble(line.amount) : -1;
if (newAmt >= oldAmt) {
if (TextUtils.isEmpty(line.claimTime) && !TextUtils.isEmpty(old.claimTime)) {
line.claimTime = old.claimTime;
}
} catch (Throwable ignored) {
if (TextUtils.isEmpty(line.receiverId)) {
line.receiverId = old.receiverId;
}
if (TextUtils.isEmpty(line.poolId)) {
line.poolId = old.poolId;
}
map.put(key, line);
} else {
if (TextUtils.isEmpty(old.claimTime) && !TextUtils.isEmpty(line.claimTime)) {
old.claimTime = line.claimTime;
}
if (TextUtils.isEmpty(old.receiverId) && !TextUtils.isEmpty(line.receiverId)) {
old.receiverId = line.receiverId;
}
if (TextUtils.isEmpty(old.poolId) && !TextUtils.isEmpty(line.poolId)) {
old.poolId = line.poolId;
}
}
}
snapshot.claims.clear();
@@ -2094,6 +2323,18 @@ public final class TngMoneyPacketHook {
ClaimLine line = new ClaimLine();
line.nickname = name.trim();
line.amount = amount;
line.receiverId = firstNonEmpty(
readFieldText(obj, "receiverId"),
readFieldText(obj, "receiverUserId"),
readFieldText(obj, "userId"),
readFieldText(obj, "participantId"));
// userName 字段误当 id 时:纯数字/长串才留
if (!TextUtils.isEmpty(line.receiverId) && line.receiverId.equals(name)) {
line.receiverId = null;
}
line.poolId = firstNonEmpty(
readFieldText(obj, "activityPoolId"),
readFieldText(obj, "poolId"));
line.claimTime = firstNonEmpty(
readFieldText(obj, "claimTime"),
readFieldText(obj, "claimedTime"),
@@ -2369,6 +2610,21 @@ public final class TngMoneyPacketHook {
return null;
}
private static void reportHookAlive(String reason) {
long now = System.currentTimeMillis();
if (!"install".equals(reason) && now - sLastHookStatusAt < HOOK_STATUS_COOLDOWN_MS) {
return;
}
Context ctx = getContext();
if (ctx == null) {
return;
}
sLastHookStatusAt = now;
HookForwarder.reportStatus(ctx, PACKAGE, HookBridge.SOURCE_XPOSED_TNG_MMP);
XposedBridge.log(TAG + " hook status reported (" + reason + ") mod="
+ HookBridge.MODULE_VERSION_NAME);
}
private static Context getContext() {
try {
Class<?> activityThread = XposedHelpers.findClass("android.app.ActivityThread", null);
@@ -2461,6 +2717,8 @@ public final class TngMoneyPacketHook {
sb.append(" | done=1");
}
sb.append(" | via=").append(sanitizeMeta(channel));
sb.append(" | mod=").append(HookBridge.MODULE_VERSION_NAME)
.append('/').append(HookBridge.MODULE_VERSION_CODE);
if (!TextUtils.isEmpty(sourceHint)) {
String src = sourceHint.length() > 120
? sourceHint.substring(0, 120) + "..." : sourceHint;
@@ -2472,6 +2730,12 @@ public final class TngMoneyPacketHook {
if (!TextUtils.isEmpty(line.claimTime)) {
sb.append(" (").append(line.claimTime).append(')');
}
if (!TextUtils.isEmpty(line.receiverId)) {
sb.append(" #rid=").append(sanitizeMeta(line.receiverId));
}
if (!TextUtils.isEmpty(line.poolId)) {
sb.append(" #pool=").append(sanitizeMeta(line.poolId));
}
sb.append('\n');
}
return sb.toString().trim();
@@ -2488,6 +2752,8 @@ public final class TngMoneyPacketHook {
private static final class ClaimLine {
String nickname;
String receiverId;
String poolId;
String amount;
String claimTime;
}