x1ongzhu пре 1 година
родитељ
комит
ebbed14924

+ 34 - 1
app/src/main/java/com/example/modifier/MainActivity.java

@@ -1,12 +1,15 @@
 package com.example.modifier;
 
 import android.content.Intent;
+import android.content.SharedPreferences;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
 import android.provider.Settings;
+import android.text.InputFilter;
 import android.util.Log;
 import android.view.View;
+import android.widget.ArrayAdapter;
 import android.widget.Toast;
 
 import androidx.activity.EdgeToEdge;
@@ -33,7 +36,9 @@ import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.io.OutputStream;
 import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.TimeUnit;
@@ -89,7 +94,6 @@ public class MainActivity extends AppCompatActivity {
             e.printStackTrace();
         }
 
-
         mBinding.btnSave.setOnClickListener(v -> {
             onSave();
         });
@@ -108,6 +112,35 @@ public class MainActivity extends AppCompatActivity {
             sendBroadcast(intent);
         });
 
+        Set<String> defServers = new HashSet<>();
+        defServers.add("http://192.168.6.215:3000");
+        defServers.add("http://192.168.50.135:3000");
+        SharedPreferences prefs = getSharedPreferences(BuildConfig.APPLICATION_ID, MODE_PRIVATE);
+        Set<String> servers = new HashSet<>(prefs.getStringSet("servers", defServers));
+        mBinding.etServer.setSimpleItems(servers.toArray(new String[0]));
+        mBinding.etServer.setThreshold(1000);
+        if (prefs.contains("server")) {
+            mBinding.etServer.setText(prefs.getString("server", ""));
+        }
+        mBinding.btnServer.setOnClickListener(v -> {
+            String server = mBinding.etServer.getText().toString();
+            if (StringUtils.isEmpty(server)) {
+                Toast.makeText(this, "Server is required", Toast.LENGTH_SHORT).show();
+                return;
+            }
+            prefs.edit().putString("server", server).apply();
+            Toast.makeText(this, "Server saved", Toast.LENGTH_SHORT).show();
+
+            servers.add(server);
+            prefs.edit().putStringSet("servers", servers).apply();
+            mBinding.etServer.setSimpleItems(servers.toArray(new String[0]));
+
+            ModifierService modifierService = ModifierService.getInstance();
+            if (modifierService != null) {
+                modifierService.connect(server);
+            }
+        });
+
 
         handler.postDelayed(() -> {
             executor.execute(() -> {

+ 73 - 51
app/src/main/java/com/example/modifier/ModifierService.java

@@ -4,7 +4,6 @@ import android.accessibilityservice.AccessibilityServiceInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.graphics.PixelFormat;
-import android.graphics.Rect;
 import android.os.Build;
 import android.util.Log;
 import android.view.Gravity;
@@ -14,23 +13,17 @@ import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.FrameLayout;
 
+import androidx.annotation.NonNull;
 import androidx.appcompat.view.ContextThemeWrapper;
 
-import com.android.volley.Request;
-import com.android.volley.RequestQueue;
-import com.android.volley.toolbox.JsonObjectRequest;
-import com.android.volley.toolbox.StringRequest;
-import com.android.volley.toolbox.Volley;
 import com.example.modifier.databinding.FloatingWindowBinding;
 
 import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
 
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
+import java.net.URISyntaxException;
+import java.util.Optional;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.ScheduledFuture;
 import java.util.concurrent.ScheduledThreadPoolExecutor;
@@ -45,6 +38,8 @@ public class ModifierService extends android.accessibilityservice.AccessibilityS
 
     public static final String NAME = BuildConfig.APPLICATION_ID + ".ModifierService";
 
+    private static ModifierService instance;
+
     private ScheduledExecutorService mExecutor = new ScheduledThreadPoolExecutor(8);
 
     private Socket mSocket;
@@ -52,11 +47,18 @@ public class ModifierService extends android.accessibilityservice.AccessibilityS
     private boolean canSend = false;
     private FloatingWindowBinding binding;
 
-    public ModifierService() {
-        Log.i(TAG, "Creating ModifierService");
+    public static ModifierService getInstance() {
+        return instance;
+    }
+
+    public void connect(String server) {
+        if (mSocket != null) {
+            mSocket.disconnect();
+        }
         try {
+            canSend = getSharedPreferences(BuildConfig.APPLICATION_ID, MODE_PRIVATE).getBoolean("canSend", false);
             mSocketOpts.query = "model=" + Build.MANUFACTURER + " " + Build.MODEL + "&name=" + Build.DEVICE + "&id=" + Utils.getUniqueID() + "&canSend=" + canSend;
-            mSocket = IO.socket("http://192.168.50.135:3000", mSocketOpts);
+            mSocket = IO.socket(server, mSocketOpts);
             mSocket.on("message", this);
             mSocket.on(Socket.EVENT_CONNECT, args -> {
                 Log.i(TAG, "Connected to server");
@@ -71,6 +73,8 @@ public class ModifierService extends android.accessibilityservice.AccessibilityS
                     e.printStackTrace();
                 }
             });
+
+            mSocket.connect();
         } catch (Exception e) {
             e.printStackTrace();
         }
@@ -80,13 +84,13 @@ public class ModifierService extends android.accessibilityservice.AccessibilityS
     public void onCreate() {
         super.onCreate();
         Log.i(TAG, "Starting ModifierService");
-        mSocketOpts.query = "model=" + Build.MANUFACTURER + " " + Build.MODEL + "&name=" + Build.DEVICE + "&id=" + Utils.getUniqueID() + "&canSend=" + canSend;
-        canSend = getSharedPreferences(BuildConfig.APPLICATION_ID, MODE_PRIVATE).getBoolean("canSend", false);
-        mSocket.connect();
+        String server = getSharedPreferences(BuildConfig.APPLICATION_ID, MODE_PRIVATE).getString("server", "http://192.168.6.215:3000");
+        connect(server);
     }
 
     @Override
     public void onAccessibilityEvent(AccessibilityEvent event) {
+//        traverseNode(getRootInActiveWindow(), new TraverseResult());
     }
 
     @Override
@@ -112,35 +116,48 @@ public class ModifierService extends android.accessibilityservice.AccessibilityS
                     JSONArray data = json.optJSONArray("data");
                     String id = json.optString("id");
                     if (data != null) {
+                        JSONArray success = new JSONArray();
+                        JSONArray fail = new JSONArray();
                         for (int i = 0; i < data.length(); i++) {
                             JSONObject task = data.optJSONObject(i);
-                            if (task != null) {
-                                String to = task.optString("number");
-                                String body = task.optString("message");
-                                send(to, body);
-
+                            String to = task.optString("number");
+                            String body = task.optString("message");
+                            int taskId = task.optInt("id");
+                            try {
+                                if (send(to, body)) {
+                                    success.put(taskId);
+                                } else {
+                                    fail.put(taskId);
+                                }
+                            } catch (Exception e) {
+                                e.printStackTrace();
+                                fail.put(taskId);
                             }
                         }
                         JSONObject res = new JSONObject();
                         try {
+                            res.put("id", id);
                             res.put("status", 0);
+                            res.put("data", new JSONObject()
+                                    .put("success", success)
+                                    .put("fail", fail));
                         } catch (JSONException e) {
                         }
-                        mSocket.emit(id, res);
+                        mSocket.emit("callback", res);
                     }
                 }
             }
         }
     }
 
-    private void send(String to, String body) {
+    private boolean send(String to, String body) {
         Log.i(TAG, "Sending SMS to " + to + ": " + body);
         String cmd = "am start -a android.intent.action.SENDTO -d sms:" + to + " --es sms_body '" + body + "' --ez exit_on_sent true";
         try {
             Utils.runAsRoot(cmd);
 
             Log.i(TAG, "Command executed successfully, waiting for app to open...");
-            ScheduledFuture<?> f = mExecutor.schedule(() -> {
+            ScheduledFuture<Boolean> f = mExecutor.schedule(() -> {
                 Log.i(TAG, "Getting root node...");
                 AccessibilityNodeInfo root = getRootInActiveWindow();
                 String packageName = root.getPackageName().toString();
@@ -153,59 +170,66 @@ public class ModifierService extends android.accessibilityservice.AccessibilityS
                     e.printStackTrace();
                 }
                 Log.i(TAG, "App: " + appName + " (" + packageName + ")");
-                traverseNode(root);
-                Log.i(TAG, "!!!!!!!!!!!!!!!!");
+                TraverseResult result = new TraverseResult();
+                traverseNode(root, result);
+                if (result.isRcsCapable()) {
+                    if (result.getSendBtn() == null) {
+                        Log.i(TAG, "Send button not found");
+                    } else {
+                        Log.i(TAG, "Clicking send button");
+                        result.getSendBtn().performAction(AccessibilityNodeInfo.ACTION_CLICK);
+                        return true;
+                    }
+                } else {
+                    Log.i(TAG, "RCS not detected");
+                }
+                return false;
             }, 1, java.util.concurrent.TimeUnit.SECONDS);
             synchronized (f) {
                 Log.i(TAG, "Waiting for task to complete...");
-                f.get();
-                Log.i(TAG, "Task completed");
+                return f.get();
             }
         } catch (Exception e) {
             e.printStackTrace();
         }
+        return false;
     }
 
-    private Map<String, Object> traverseNode(AccessibilityNodeInfo node) {
+    private void traverseNode(AccessibilityNodeInfo node, @NonNull TraverseResult result) {
         if (node == null) {
-            return null;
+            return;
         }
-        Map<String, Object> map = new HashMap<>();
-        map.put("class", node.getClassName());
-        map.put("text", node.getText());
-        map.put("actions", node.getActionList().stream().map(AccessibilityNodeInfo.AccessibilityAction::getId).toArray());
-        Rect rect = new Rect();
-        node.getBoundsInScreen(rect);
-        map.put("bounds", rect);
 
+        String className = node.getClassName().toString();
         String name = node.getViewIdResourceName();
-        Log.i(TAG, "Node: class=" + node.getClassName() + ", text=" + node.getText() + ", name=" + name);
+        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);
 
         if ("Compose:Draft:Send".equals(name)) {
-            Log.i(TAG, "Found send button Node: class=" + node.getClassName() + ", text=" + node.getText() + ", name=" + name);
-            node.performAction(AccessibilityNodeInfo.ACTION_CLICK);
+            result.setSendBtn(node);
         }
 
-        node.getViewIdResourceName();
+        if (text != null && (text.contains("RCS 聊天") || text.contains("RCS chat"))) {
+            result.setRcsCapable(true);
+        }
 
         if (node.getChildCount() != 0) {
-            List<Map<String, Object>> children = new ArrayList<>();
             for (int i = 0; i < node.getChildCount(); i++) {
-                children.add(traverseNode(node.getChild(i)));
-                map.put("children", children);
+                traverseNode(node.getChild(i), result);
             }
         }
-        return map;
     }
 
     @Override
     protected void onServiceConnected() {
         super.onServiceConnected();
 
+        instance = this;
+
         AccessibilityServiceInfo info = new AccessibilityServiceInfo();
-        info.eventTypes = AccessibilityEvent.TYPE_VIEW_CLICKED |
-                AccessibilityEvent.TYPE_VIEW_FOCUSED |
-                AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED;
+        info.eventTypes = AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED;
         info.feedbackType = AccessibilityServiceInfo.FEEDBACK_SPOKEN;
         info.notificationTimeout = 100;
         this.setServiceInfo(info);
@@ -221,7 +245,7 @@ public class ModifierService extends android.accessibilityservice.AccessibilityS
         layoutParams.x = 0;
         layoutParams.y = 800;
         layoutParams.gravity = Gravity.START | Gravity.TOP;
-        ContextThemeWrapper newContext = new ContextThemeWrapper(getApplicationContext(), R.style.Theme_Modifier);
+        ContextThemeWrapper newContext = new ContextThemeWrapper(getApplicationContext(), R.style.FloatingWindow);
         LayoutInflater inflater = LayoutInflater.from(newContext);
         binding = FloatingWindowBinding.inflate(inflater, mLayout, true);
         windowManager.addView(mLayout, layoutParams);
@@ -252,6 +276,4 @@ public class ModifierService extends android.accessibilityservice.AccessibilityS
             mSocket.emit("message", data);
         });
     }
-
-
 }

+ 28 - 0
app/src/main/java/com/example/modifier/TraverseResult.java

@@ -0,0 +1,28 @@
+package com.example.modifier;
+
+
+import android.view.accessibility.AccessibilityNodeInfo;
+
+public class TraverseResult {
+
+    private boolean rcsCapable;
+
+    private AccessibilityNodeInfo sendBtn;
+
+
+    public boolean isRcsCapable() {
+        return rcsCapable;
+    }
+
+    public void setRcsCapable(boolean rcsCapable) {
+        this.rcsCapable = rcsCapable;
+    }
+
+    public AccessibilityNodeInfo getSendBtn() {
+        return sendBtn;
+    }
+
+    public void setSendBtn(AccessibilityNodeInfo sendBtn) {
+        this.sendBtn = sendBtn;
+    }
+}

+ 1 - 1
app/src/main/java/com/example/modifier/Utils.java

@@ -64,7 +64,7 @@ public class Utils {
             outputStream.flush();
             Log.i(TAG, "Running command: " + cmd);
         }
-        Thread.sleep(2000);
+        Thread.sleep(500);
         outputStream.writeBytes("exit\n");
         outputStream.flush();
         p.waitFor();

+ 24 - 0
app/src/main/res/layout/activity_main.xml

@@ -20,9 +20,33 @@
             android:orientation="vertical"
             android:padding="20dp">
 
+
+            <com.google.android.material.textfield.TextInputLayout
+                style="@style/Widget.MaterialComponents.TextInputLayout.FilledBox.ExposedDropdownMenu"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="16dp"
+                android:hint="Server">
+
+                <com.google.android.material.textfield.MaterialAutoCompleteTextView
+                    android:id="@+id/et_server"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content" />
+
+            </com.google.android.material.textfield.TextInputLayout>
+
+
+            <com.google.android.material.button.MaterialButton
+                android:id="@+id/btn_server"
+                android:layout_width="match_parent"
+                android:layout_height="56dp"
+                android:layout_marginTop="16dp"
+                android:text="Save" />
+
             <LinearLayout
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
+                android:layout_marginTop="16dp"
                 android:orientation="horizontal">
 
                 <com.google.android.material.textfield.TextInputLayout

+ 12 - 9
app/src/main/res/layout/floating_window.xml

@@ -5,22 +5,25 @@
     android:layout_height="wrap_content"
     android:background="#66000000"
     android:orientation="vertical"
-    android:padding="8dp"
-    android:theme="@style/Theme.MaterialComponents.DayNight.NoActionBar">
+    android:paddingEnd="8dp">
 
-    <com.google.android.material.switchmaterial.SwitchMaterial
+    <com.google.android.material.checkbox.MaterialCheckBox
         android:id="@+id/sw_connect"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
+        style="@style/Widget.Material3.CompoundButton.CheckBox"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
         android:checked="true"
         android:text="Connect"
-        android:textColor="@color/white" />
+        android:textColor="@color/white"
+        android:textSize="14sp" />
 
-    <com.google.android.material.switchmaterial.SwitchMaterial
+    <com.google.android.material.checkbox.MaterialCheckBox
         android:id="@+id/sw_send"
-        android:layout_width="match_parent"
+        style="@style/Widget.Material3.CompoundButton.CheckBox"
+        android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:checked="true"
         android:text="Send"
-        android:textColor="@color/white" />
+        android:textColor="@color/white"
+        android:textSize="14sp" />
 </LinearLayout>

+ 6 - 0
app/src/main/res/values/themes.xml

@@ -6,4 +6,10 @@
     </style>
 
     <style name="Theme.Modifier" parent="Base.Theme.Modifier" />
+
+    <style name="FloatingWindow" parent="Theme.MaterialComponents.DayNight.NoActionBar">
+        <!-- Customize your light theme here. -->
+        <!-- <item name="colorPrimary">@color/my_light_primary</item> -->
+
+    </style>
 </resources>

+ 1 - 0
settings.gradle

@@ -16,6 +16,7 @@ dependencyResolutionManagement {
     repositories {
         google()
         mavenCentral()
+        maven { url 'https://jitpack.io' }
     }
 }