|
@@ -1,356 +1,372 @@
|
|
|
-package com.example.modifier;
|
|
|
|
|
-
|
|
|
|
|
-import android.accessibilityservice.AccessibilityServiceInfo;
|
|
|
|
|
-import android.annotation.SuppressLint;
|
|
|
|
|
-import android.content.Context;
|
|
|
|
|
-import android.content.Intent;
|
|
|
|
|
-import android.content.pm.ApplicationInfo;
|
|
|
|
|
-import android.content.pm.PackageManager;
|
|
|
|
|
-import android.graphics.PixelFormat;
|
|
|
|
|
-import android.net.Uri;
|
|
|
|
|
-import android.os.Build;
|
|
|
|
|
-import android.os.PowerManager;
|
|
|
|
|
-import android.util.DisplayMetrics;
|
|
|
|
|
-import android.util.Log;
|
|
|
|
|
-import android.view.Gravity;
|
|
|
|
|
-import android.view.LayoutInflater;
|
|
|
|
|
-import android.view.MotionEvent;
|
|
|
|
|
-import android.view.WindowManager;
|
|
|
|
|
-import android.view.accessibility.AccessibilityEvent;
|
|
|
|
|
-import android.view.accessibility.AccessibilityNodeInfo;
|
|
|
|
|
-import android.widget.FrameLayout;
|
|
|
|
|
-
|
|
|
|
|
-import androidx.annotation.NonNull;
|
|
|
|
|
-
|
|
|
|
|
-import com.example.modifier.databinding.FloatingWindowBinding;
|
|
|
|
|
-import com.google.android.material.color.DynamicColors;
|
|
|
|
|
-
|
|
|
|
|
-import org.apache.commons.lang3.StringUtils;
|
|
|
|
|
-import org.json.JSONArray;
|
|
|
|
|
-import org.json.JSONException;
|
|
|
|
|
-import org.json.JSONObject;
|
|
|
|
|
-
|
|
|
|
|
-import java.util.Optional;
|
|
|
|
|
-import java.util.concurrent.ScheduledExecutorService;
|
|
|
|
|
-import java.util.concurrent.ScheduledFuture;
|
|
|
|
|
-import java.util.concurrent.ScheduledThreadPoolExecutor;
|
|
|
|
|
-import java.util.concurrent.TimeUnit;
|
|
|
|
|
-import java.util.concurrent.atomic.AtomicReference;
|
|
|
|
|
-
|
|
|
|
|
-import io.socket.client.IO;
|
|
|
|
|
-import io.socket.client.Socket;
|
|
|
|
|
-import io.socket.emitter.Emitter;
|
|
|
|
|
-
|
|
|
|
|
-public class ModifierService extends android.accessibilityservice.AccessibilityService implements Emitter.Listener {
|
|
|
|
|
-
|
|
|
|
|
- public interface JobListener {
|
|
|
|
|
- void onSuccess();
|
|
|
|
|
-
|
|
|
|
|
- void onFail();
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private static final String TAG = "ModifierService";
|
|
|
|
|
-
|
|
|
|
|
- public static final String NAME = BuildConfig.APPLICATION_ID + ".ModifierService";
|
|
|
|
|
-
|
|
|
|
|
- private static ModifierService instance;
|
|
|
|
|
-
|
|
|
|
|
- private ScheduledExecutorService mExecutor = new ScheduledThreadPoolExecutor(8);
|
|
|
|
|
-
|
|
|
|
|
- private Socket mSocket;
|
|
|
|
|
- private IO.Options mSocketOpts = new IO.Options();
|
|
|
|
|
- private boolean canSend = false;
|
|
|
|
|
- private FloatingWindowBinding binding;
|
|
|
|
|
-
|
|
|
|
|
- public static ModifierService getInstance() {
|
|
|
|
|
- return instance;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- public void connect() {
|
|
|
|
|
- mExecutor.submit(() -> {
|
|
|
|
|
- Global.load();
|
|
|
|
|
- if (mSocket != null) {
|
|
|
|
|
- mSocket.disconnect();
|
|
|
|
|
- }
|
|
|
|
|
|
|
+package com.example.modifier
|
|
|
|
|
+
|
|
|
|
|
+import android.accessibilityservice.AccessibilityService
|
|
|
|
|
+import android.accessibilityservice.AccessibilityServiceInfo
|
|
|
|
|
+import android.annotation.SuppressLint
|
|
|
|
|
+import android.content.Intent
|
|
|
|
|
+import android.graphics.PixelFormat
|
|
|
|
|
+import android.net.Uri
|
|
|
|
|
+import android.os.Build
|
|
|
|
|
+import android.util.DisplayMetrics
|
|
|
|
|
+import android.util.Log
|
|
|
|
|
+import android.view.Gravity
|
|
|
|
|
+import android.view.LayoutInflater
|
|
|
|
|
+import android.view.MotionEvent
|
|
|
|
|
+import android.view.View
|
|
|
|
|
+import android.view.WindowManager
|
|
|
|
|
+import android.view.accessibility.AccessibilityEvent
|
|
|
|
|
+import android.view.accessibility.AccessibilityNodeInfo
|
|
|
|
|
+import android.widget.CompoundButton
|
|
|
|
|
+import android.widget.FrameLayout
|
|
|
|
|
+import com.example.modifier.Global.load
|
|
|
|
|
+import com.example.modifier.databinding.FloatingWindowBinding
|
|
|
|
|
+import com.google.android.material.color.DynamicColors
|
|
|
|
|
+import io.socket.client.IO
|
|
|
|
|
+import io.socket.client.Socket
|
|
|
|
|
+import io.socket.emitter.Emitter
|
|
|
|
|
+import kotlinx.coroutines.GlobalScope
|
|
|
|
|
+import kotlinx.coroutines.MainScope
|
|
|
|
|
+import kotlinx.coroutines.launch
|
|
|
|
|
+import org.apache.commons.lang3.StringUtils
|
|
|
|
|
+import org.json.JSONArray
|
|
|
|
|
+import org.json.JSONException
|
|
|
|
|
+import org.json.JSONObject
|
|
|
|
|
+import java.util.Optional
|
|
|
|
|
+import java.util.concurrent.ScheduledExecutorService
|
|
|
|
|
+import java.util.concurrent.ScheduledThreadPoolExecutor
|
|
|
|
|
+import java.util.concurrent.TimeUnit
|
|
|
|
|
+import java.util.concurrent.atomic.AtomicReference
|
|
|
|
|
+import kotlin.math.max
|
|
|
|
|
+import kotlin.math.min
|
|
|
|
|
+
|
|
|
|
|
+class ModifierService : AccessibilityService(), Emitter.Listener {
|
|
|
|
|
+
|
|
|
|
|
+ private val mExecutor: ScheduledExecutorService = ScheduledThreadPoolExecutor(8)
|
|
|
|
|
+
|
|
|
|
|
+ private lateinit var mSocket: Socket
|
|
|
|
|
+ private val mSocketOpts = IO.Options()
|
|
|
|
|
+ private var canSend = false
|
|
|
|
|
+ private lateinit var binding: FloatingWindowBinding
|
|
|
|
|
+
|
|
|
|
|
+ fun connect() {
|
|
|
|
|
+ mExecutor.submit {
|
|
|
try {
|
|
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(Global.serverUrl, mSocketOpts);
|
|
|
|
|
- mSocket.on("message", this);
|
|
|
|
|
- mSocket.on(Socket.EVENT_CONNECT, args -> {
|
|
|
|
|
- Log.i(TAG, "Connected to server");
|
|
|
|
|
- });
|
|
|
|
|
- mSocket.on(Socket.EVENT_DISCONNECT, args -> {
|
|
|
|
|
- Log.i(TAG, "Disconnected from server");
|
|
|
|
|
- });
|
|
|
|
|
- mSocket.on(Socket.EVENT_CONNECT_ERROR, args -> {
|
|
|
|
|
- Log.i(TAG, "Connection error: " + args[0]);
|
|
|
|
|
- if (args[0] instanceof Exception) {
|
|
|
|
|
- Exception e = (Exception) args[0];
|
|
|
|
|
- e.printStackTrace();
|
|
|
|
|
|
|
+ load()
|
|
|
|
|
+ if (this::binding.isInitialized) {
|
|
|
|
|
+ binding.tvDeviceName.text = Global.name
|
|
|
|
|
+ }
|
|
|
|
|
+ if (this::mSocket.isInitialized) {
|
|
|
|
|
+ mSocket.disconnect()
|
|
|
|
|
+ }
|
|
|
|
|
+ canSend = getSharedPreferences(
|
|
|
|
|
+ BuildConfig.APPLICATION_ID,
|
|
|
|
|
+ MODE_PRIVATE
|
|
|
|
|
+ ).getBoolean("canSend", false)
|
|
|
|
|
+ mSocketOpts.query =
|
|
|
|
|
+ "model=" + Build.MANUFACTURER + " " + Build.MODEL + "&name=" + Global.name + "&id=" + Utils.getUniqueID() + "&canSend=" + canSend
|
|
|
|
|
+ mSocket = IO.socket(Global.serverUrl, mSocketOpts)
|
|
|
|
|
+ mSocket.on("message", this)
|
|
|
|
|
+ mSocket.on(Socket.EVENT_CONNECT, Emitter.Listener { args: Array<Any?>? ->
|
|
|
|
|
+ Log.i(TAG, "Connected to server")
|
|
|
|
|
+ })
|
|
|
|
|
+ mSocket.on(Socket.EVENT_DISCONNECT, Emitter.Listener { args: Array<Any?>? ->
|
|
|
|
|
+ Log.i(TAG, "Disconnected from server")
|
|
|
|
|
+ })
|
|
|
|
|
+ mSocket.on(Socket.EVENT_CONNECT_ERROR, Emitter.Listener { args: Array<Any> ->
|
|
|
|
|
+ Log.i(TAG, "Connection error: " + args[0])
|
|
|
|
|
+ if (args[0] is Exception) {
|
|
|
|
|
+ val e = args[0] as Exception
|
|
|
|
|
+ e.printStackTrace()
|
|
|
}
|
|
}
|
|
|
- });
|
|
|
|
|
|
|
+ })
|
|
|
|
|
|
|
|
- mSocket.connect();
|
|
|
|
|
- } catch (Exception e) {
|
|
|
|
|
- e.printStackTrace();
|
|
|
|
|
|
|
+ mSocket.connect()
|
|
|
|
|
+ } catch (e: Exception) {
|
|
|
|
|
+ e.printStackTrace()
|
|
|
}
|
|
}
|
|
|
- });
|
|
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- @Override
|
|
|
|
|
- public void onCreate() {
|
|
|
|
|
- super.onCreate();
|
|
|
|
|
- Log.i(TAG, "Starting ModifierService");
|
|
|
|
|
- connect();
|
|
|
|
|
|
|
+ override fun onCreate() {
|
|
|
|
|
+ super.onCreate()
|
|
|
|
|
+ Log.i(TAG, "Starting ModifierService")
|
|
|
|
|
+ connect()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- @Override
|
|
|
|
|
- public void onAccessibilityEvent(AccessibilityEvent event) {
|
|
|
|
|
|
|
+ override fun onAccessibilityEvent(event: AccessibilityEvent) {
|
|
|
// traverseNode(getRootInActiveWindow(), new TraverseResult());
|
|
// traverseNode(getRootInActiveWindow(), new TraverseResult());
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- @Override
|
|
|
|
|
- public void onInterrupt() {
|
|
|
|
|
-
|
|
|
|
|
|
|
+ override fun onInterrupt() {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- @Override
|
|
|
|
|
- public void call(Object... args) {
|
|
|
|
|
- if (args.length > 0) {
|
|
|
|
|
- Log.i(TAG, "Received message: " + args[0]);
|
|
|
|
|
- if (args[0] instanceof JSONObject) {
|
|
|
|
|
- JSONObject json = (JSONObject) args[0];
|
|
|
|
|
- String action = json.optString("action");
|
|
|
|
|
- if ("send".equals(action)) {
|
|
|
|
|
- JSONObject data = json.optJSONObject("data");
|
|
|
|
|
|
|
+ override fun call(vararg args: Any) {
|
|
|
|
|
+ if (args.size > 0) {
|
|
|
|
|
+ Log.i(TAG, "Received message: " + args[0])
|
|
|
|
|
+ if (args[0] is JSONObject) {
|
|
|
|
|
+ val json = args[0] as JSONObject
|
|
|
|
|
+ val action = json.optString("action")
|
|
|
|
|
+ if ("send" == action) {
|
|
|
|
|
+ val data = json.optJSONObject("data")
|
|
|
if (data != null) {
|
|
if (data != null) {
|
|
|
- String to = data.optString("to");
|
|
|
|
|
- String body = data.optString("body");
|
|
|
|
|
- send(to, body);
|
|
|
|
|
|
|
+ val to = data.optString("to")
|
|
|
|
|
+ val body = data.optString("body")
|
|
|
|
|
+ send(to, body, 2000)
|
|
|
}
|
|
}
|
|
|
- } else if ("task".equals(action)) {
|
|
|
|
|
- JSONArray data = json.optJSONArray("data");
|
|
|
|
|
- String id = json.optString("id");
|
|
|
|
|
|
|
+ } else if ("task" == action) {
|
|
|
|
|
+ val data = json.optJSONObject("data")
|
|
|
|
|
+ val id = json.optString("id")
|
|
|
if (data != null && StringUtils.isNoneBlank(id)) {
|
|
if (data != null && StringUtils.isNoneBlank(id)) {
|
|
|
- runTask(id, data);
|
|
|
|
|
|
|
+ runTask(id, data)
|
|
|
}
|
|
}
|
|
|
- } else if ("changeNumber".equals(action)) {
|
|
|
|
|
-
|
|
|
|
|
|
|
+ } else if ("changeNumber" == action) {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- private void runTask(String id, JSONArray data) {
|
|
|
|
|
- mExecutor.submit(() -> {
|
|
|
|
|
- Global.load();
|
|
|
|
|
- JSONArray success = new JSONArray();
|
|
|
|
|
- JSONArray fail = new JSONArray();
|
|
|
|
|
- for (int i = 0; i < data.length(); i++) {
|
|
|
|
|
- JSONObject task = data.optJSONObject(i);
|
|
|
|
|
- String to = task.optString("number");
|
|
|
|
|
- String body = task.optString("message");
|
|
|
|
|
- int taskId = task.optInt("id");
|
|
|
|
|
|
|
+ private fun runTask(id: String, data: JSONObject) {
|
|
|
|
|
+ val config = data.optJSONObject("config")
|
|
|
|
|
+ val rcsWait = config.optLong("rcsWait", 2000)
|
|
|
|
|
+ val tasks = data.optJSONArray("tasks")
|
|
|
|
|
+ mExecutor.submit {
|
|
|
|
|
+ val success = JSONArray()
|
|
|
|
|
+ val fail = JSONArray()
|
|
|
|
|
+ for (i in 0 until tasks.length()) {
|
|
|
|
|
+ val task = tasks.optJSONObject(i)
|
|
|
|
|
+ val to = task.optString("number")
|
|
|
|
|
+ val body = task.optString("message")
|
|
|
|
|
+ val taskId = task.optInt("id")
|
|
|
try {
|
|
try {
|
|
|
- if (send(to, body)) {
|
|
|
|
|
- success.put(taskId);
|
|
|
|
|
|
|
+ if (send(to, body, rcsWait)) {
|
|
|
|
|
+ success.put(taskId)
|
|
|
} else {
|
|
} else {
|
|
|
- fail.put(taskId);
|
|
|
|
|
|
|
+ fail.put(taskId)
|
|
|
}
|
|
}
|
|
|
- } catch (Exception e) {
|
|
|
|
|
- e.printStackTrace();
|
|
|
|
|
- fail.put(taskId);
|
|
|
|
|
|
|
+ } catch (e: Exception) {
|
|
|
|
|
+ e.printStackTrace()
|
|
|
|
|
+ fail.put(taskId)
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
- JSONObject res = new JSONObject();
|
|
|
|
|
|
|
+ val res = JSONObject()
|
|
|
try {
|
|
try {
|
|
|
- res.put("id", id);
|
|
|
|
|
- res.put("status", 0);
|
|
|
|
|
- res.put("data", new JSONObject()
|
|
|
|
|
|
|
+ res.put("id", id)
|
|
|
|
|
+ res.put("status", 0)
|
|
|
|
|
+ res.put(
|
|
|
|
|
+ "data", JSONObject()
|
|
|
.put("success", success)
|
|
.put("success", success)
|
|
|
- .put("fail", fail));
|
|
|
|
|
- } catch (JSONException e) {
|
|
|
|
|
|
|
+ .put("fail", fail)
|
|
|
|
|
+ )
|
|
|
|
|
+ } catch (e: JSONException) {
|
|
|
}
|
|
}
|
|
|
- mSocket.emit("callback", res);
|
|
|
|
|
- updateDevice("busy", false);
|
|
|
|
|
- });
|
|
|
|
|
|
|
+ mSocket!!.emit("callback", res)
|
|
|
|
|
+ updateDevice("busy", false)
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- private boolean send(String to, String body) {
|
|
|
|
|
- Log.i(TAG, "Sending SMS to " + to + ": " + body);
|
|
|
|
|
- Intent intent = new Intent(Intent.ACTION_SENDTO);
|
|
|
|
|
- intent.setData(Uri.parse("sms:" + to));
|
|
|
|
|
- intent.putExtra("sms_body", body);
|
|
|
|
|
- intent.putExtra("exit_on_sent", true);
|
|
|
|
|
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
|
|
|
- startActivity(intent);
|
|
|
|
|
|
|
+ private fun send(to: String, body: String, rcsWait: Long): Boolean {
|
|
|
|
|
+ Log.i(TAG, "Sending SMS to $to: $body")
|
|
|
|
|
+ val intent = Intent(Intent.ACTION_SENDTO)
|
|
|
|
|
+ intent.setData(Uri.parse("sms:$to"))
|
|
|
|
|
+ intent.putExtra("sms_body", body)
|
|
|
|
|
+ intent.putExtra("exit_on_sent", true)
|
|
|
|
|
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
|
|
|
|
+ startActivity(intent)
|
|
|
try {
|
|
try {
|
|
|
- Log.i(TAG, "Command executed successfully, waiting for app to open...");
|
|
|
|
|
- ScheduledFuture<Boolean> f = mExecutor.schedule(() -> {
|
|
|
|
|
- long ts = System.currentTimeMillis();
|
|
|
|
|
- while (System.currentTimeMillis() - ts < Global.rcsTimeout) {
|
|
|
|
|
- AccessibilityNodeInfo root = getRootInActiveWindow();
|
|
|
|
|
- String packageName = root.getPackageName().toString();
|
|
|
|
|
- TraverseResult result = new TraverseResult();
|
|
|
|
|
- traverseNode(root, result);
|
|
|
|
|
- if (result.isRcsCapable()) {
|
|
|
|
|
- if (result.getSendBtn() == null) {
|
|
|
|
|
- Log.i(TAG, "Send button not found");
|
|
|
|
|
|
|
+ Log.i(TAG, "Command executed successfully, waiting for app to open...")
|
|
|
|
|
+ val f = mExecutor.schedule<Boolean>(
|
|
|
|
|
+ {
|
|
|
|
|
+ val ts = System.currentTimeMillis()
|
|
|
|
|
+ while (System.currentTimeMillis() - ts < rcsWait) {
|
|
|
|
|
+ val root = rootInActiveWindow
|
|
|
|
|
+ val packageName = root.packageName.toString()
|
|
|
|
|
+ val result = TraverseResult()
|
|
|
|
|
+ traverseNode(root, result)
|
|
|
|
|
+ if (result.isRcsCapable) {
|
|
|
|
|
+ if (result.sendBtn == null) {
|
|
|
|
|
+ Log.i(TAG, "Send button not found")
|
|
|
|
|
+ } else {
|
|
|
|
|
+ Log.i(TAG, "Clicking send button")
|
|
|
|
|
+ result.sendBtn.performAction(AccessibilityNodeInfo.ACTION_CLICK)
|
|
|
|
|
+ return@schedule true
|
|
|
|
|
+ }
|
|
|
} else {
|
|
} else {
|
|
|
- Log.i(TAG, "Clicking send button");
|
|
|
|
|
- result.getSendBtn().performAction(AccessibilityNodeInfo.ACTION_CLICK);
|
|
|
|
|
- return true;
|
|
|
|
|
|
|
+ Log.i(TAG, "RCS not detected")
|
|
|
|
|
+ }
|
|
|
|
|
+ try {
|
|
|
|
|
+ Thread.sleep(500)
|
|
|
|
|
+ } catch (e: InterruptedException) {
|
|
|
|
|
+ e.printStackTrace()
|
|
|
}
|
|
}
|
|
|
- } else {
|
|
|
|
|
- Log.i(TAG, "RCS not detected");
|
|
|
|
|
- }
|
|
|
|
|
- try {
|
|
|
|
|
- Thread.sleep(500);
|
|
|
|
|
- } catch (InterruptedException e) {
|
|
|
|
|
- e.printStackTrace();
|
|
|
|
|
}
|
|
}
|
|
|
- }
|
|
|
|
|
- return false;
|
|
|
|
|
- }, 1000, TimeUnit.MILLISECONDS);
|
|
|
|
|
- synchronized (f) {
|
|
|
|
|
- Log.i(TAG, "Waiting for task to complete...");
|
|
|
|
|
- return f.get();
|
|
|
|
|
|
|
+ false
|
|
|
|
|
+ }, 1000, TimeUnit.MILLISECONDS
|
|
|
|
|
+ )
|
|
|
|
|
+ synchronized(f) {
|
|
|
|
|
+ Log.i(TAG, "Waiting for task to complete...")
|
|
|
|
|
+ return f.get()
|
|
|
}
|
|
}
|
|
|
- } catch (Exception e) {
|
|
|
|
|
- e.printStackTrace();
|
|
|
|
|
|
|
+ } catch (e: Exception) {
|
|
|
|
|
+ e.printStackTrace()
|
|
|
}
|
|
}
|
|
|
- return false;
|
|
|
|
|
|
|
+ return false
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- private void traverseNode(AccessibilityNodeInfo node, @NonNull TraverseResult result) {
|
|
|
|
|
|
|
+ private fun traverseNode(node: AccessibilityNodeInfo?, result: TraverseResult) {
|
|
|
if (node == null) {
|
|
if (node == null) {
|
|
|
- return;
|
|
|
|
|
|
|
+ return
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- String className = node.getClassName().toString();
|
|
|
|
|
- String name = node.getViewIdResourceName();
|
|
|
|
|
- String text = Optional.ofNullable(node.getText()).map(CharSequence::toString).orElse(null);
|
|
|
|
|
- String id = node.getViewIdResourceName();
|
|
|
|
|
|
|
+ val className = node.className.toString()
|
|
|
|
|
+ val name = node.viewIdResourceName
|
|
|
|
|
+ val text = Optional.ofNullable(node.text).map { obj: CharSequence -> obj.toString() }
|
|
|
|
|
+ .orElse(null)
|
|
|
|
|
+ val id = node.viewIdResourceName
|
|
|
|
|
|
|
|
- Log.d(TAG, "Node: class=" + className + ", text=" + text + ", name=" + name + ", id=" + id);
|
|
|
|
|
|
|
+ Log.d(TAG, "Node: class=$className, text=$text, name=$name, id=$id")
|
|
|
|
|
|
|
|
- if ("Compose:Draft:Send".equals(name)) {
|
|
|
|
|
- result.setSendBtn(node);
|
|
|
|
|
|
|
+ if ("Compose:Draft:Send" == name) {
|
|
|
|
|
+ result.sendBtn = node
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- if ("com.google.android.apps.messaging:id/send_message_button_icon".equals(id)) {
|
|
|
|
|
- result.setSendBtn(node);
|
|
|
|
|
|
|
+ if ("com.google.android.apps.messaging:id/send_message_button_icon" == id) {
|
|
|
|
|
+ result.sendBtn = node
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
if (text != null && (text.contains("RCS 聊天") || text.contains("RCS chat"))) {
|
|
if (text != null && (text.contains("RCS 聊天") || text.contains("RCS chat"))) {
|
|
|
- result.setRcsCapable(true);
|
|
|
|
|
|
|
+ result.isRcsCapable = true
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- if (node.getChildCount() != 0) {
|
|
|
|
|
- for (int i = 0; i < node.getChildCount(); i++) {
|
|
|
|
|
- traverseNode(node.getChild(i), result);
|
|
|
|
|
|
|
+ if (node.childCount != 0) {
|
|
|
|
|
+ for (i in 0 until node.childCount) {
|
|
|
|
|
+ traverseNode(node.getChild(i), result)
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
@SuppressLint("ClickableViewAccessibility")
|
|
@SuppressLint("ClickableViewAccessibility")
|
|
|
- @Override
|
|
|
|
|
- protected void onServiceConnected() {
|
|
|
|
|
- super.onServiceConnected();
|
|
|
|
|
-
|
|
|
|
|
- instance = this;
|
|
|
|
|
-
|
|
|
|
|
- AccessibilityServiceInfo info = new AccessibilityServiceInfo();
|
|
|
|
|
- info.flags = AccessibilityServiceInfo.DEFAULT | AccessibilityServiceInfo.FLAG_REPORT_VIEW_IDS;
|
|
|
|
|
- info.eventTypes = AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED;
|
|
|
|
|
- info.feedbackType = AccessibilityServiceInfo.FEEDBACK_SPOKEN;
|
|
|
|
|
- info.notificationTimeout = 100;
|
|
|
|
|
- this.setServiceInfo(info);
|
|
|
|
|
-
|
|
|
|
|
- DisplayMetrics displayMetrics = new DisplayMetrics();
|
|
|
|
|
- WindowManager windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
|
|
|
|
|
- windowManager.getDefaultDisplay().getMetrics(displayMetrics);
|
|
|
|
|
- int height = displayMetrics.heightPixels;
|
|
|
|
|
- int width = displayMetrics.widthPixels;
|
|
|
|
|
- int maxX = width - Utils.dp2px(this, 108);
|
|
|
|
|
- int maxY = height - Utils.dp2px(this, 108);
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
- FrameLayout mLayout = new FrameLayout(this);
|
|
|
|
|
- WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();
|
|
|
|
|
- layoutParams.type = WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
|
|
|
|
|
- layoutParams.format = PixelFormat.TRANSLUCENT;
|
|
|
|
|
- layoutParams.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
|
|
|
|
|
- layoutParams.flags |= WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
|
|
|
|
|
- layoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
|
|
|
|
|
- layoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
|
|
|
|
|
- layoutParams.x = 0;
|
|
|
|
|
- layoutParams.y = 800;
|
|
|
|
|
- layoutParams.gravity = Gravity.START | Gravity.TOP;
|
|
|
|
|
-
|
|
|
|
|
- Context newContext = DynamicColors.wrapContextIfAvailable(getApplicationContext(), R.style.AppTheme);
|
|
|
|
|
- LayoutInflater inflater = LayoutInflater.from(newContext);
|
|
|
|
|
- binding = FloatingWindowBinding.inflate(inflater, mLayout, true);
|
|
|
|
|
- windowManager.addView(mLayout, layoutParams);
|
|
|
|
|
-
|
|
|
|
|
- AtomicReference<Float> downX = new AtomicReference<>(0f);
|
|
|
|
|
- AtomicReference<Float> downY = new AtomicReference<>(0f);
|
|
|
|
|
- AtomicReference<Integer> downParamX = new AtomicReference<>(0);
|
|
|
|
|
- AtomicReference<Integer> downParamY = new AtomicReference<>(0);
|
|
|
|
|
-
|
|
|
|
|
- binding.floatingWindow.setOnTouchListener((v, event) -> {
|
|
|
|
|
- switch (event.getAction()) {
|
|
|
|
|
- case MotionEvent.ACTION_DOWN:
|
|
|
|
|
- downX.set(event.getRawX());
|
|
|
|
|
- downY.set(event.getRawY());
|
|
|
|
|
- downParamX.set(layoutParams.x);
|
|
|
|
|
- downParamY.set(layoutParams.y);
|
|
|
|
|
- return true;
|
|
|
|
|
-
|
|
|
|
|
- case MotionEvent.ACTION_MOVE:
|
|
|
|
|
- layoutParams.x = (int) Math.min(Math.max(downParamX.get() + (event.getRawX() - downX.get()), 0), maxX);
|
|
|
|
|
- layoutParams.y = (int) Math.min(Math.max(downParamY.get() + (event.getRawY() - downY.get()), 0), maxY);
|
|
|
|
|
- windowManager.updateViewLayout(mLayout, layoutParams);
|
|
|
|
|
- return true;
|
|
|
|
|
|
|
+ override fun onServiceConnected() {
|
|
|
|
|
+ super.onServiceConnected()
|
|
|
|
|
+
|
|
|
|
|
+ instance = this
|
|
|
|
|
+
|
|
|
|
|
+ val info = AccessibilityServiceInfo()
|
|
|
|
|
+ info.flags =
|
|
|
|
|
+ AccessibilityServiceInfo.DEFAULT or AccessibilityServiceInfo.FLAG_REPORT_VIEW_IDS
|
|
|
|
|
+ info.eventTypes = AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED
|
|
|
|
|
+ info.feedbackType = AccessibilityServiceInfo.FEEDBACK_SPOKEN
|
|
|
|
|
+ info.notificationTimeout = 100
|
|
|
|
|
+ this.serviceInfo = info
|
|
|
|
|
+
|
|
|
|
|
+ val displayMetrics = DisplayMetrics()
|
|
|
|
|
+ val windowManager = getSystemService(WINDOW_SERVICE) as WindowManager
|
|
|
|
|
+ windowManager.defaultDisplay.getMetrics(displayMetrics)
|
|
|
|
|
+ val height = displayMetrics.heightPixels
|
|
|
|
|
+ val width = displayMetrics.widthPixels
|
|
|
|
|
+ val maxX = width - Utils.dp2px(this, 108)
|
|
|
|
|
+ val maxY = height - Utils.dp2px(this, 108)
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ val mLayout = FrameLayout(this)
|
|
|
|
|
+ val layoutParams = WindowManager.LayoutParams()
|
|
|
|
|
+ layoutParams.type = WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY
|
|
|
|
|
+ layoutParams.format = PixelFormat.TRANSLUCENT
|
|
|
|
|
+ layoutParams.flags = layoutParams.flags or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
|
|
|
|
|
+ layoutParams.flags = layoutParams.flags or WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
|
|
|
|
|
+ layoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT
|
|
|
|
|
+ layoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT
|
|
|
|
|
+ layoutParams.x = 0
|
|
|
|
|
+ layoutParams.y = 800
|
|
|
|
|
+ layoutParams.gravity = Gravity.START or Gravity.TOP
|
|
|
|
|
+
|
|
|
|
|
+ val newContext = DynamicColors.wrapContextIfAvailable(applicationContext, R.style.AppTheme)
|
|
|
|
|
+ val inflater = LayoutInflater.from(newContext)
|
|
|
|
|
+ binding = FloatingWindowBinding.inflate(inflater, mLayout, true)
|
|
|
|
|
+ binding!!.tvDeviceName.text = Global.name
|
|
|
|
|
+ windowManager.addView(mLayout, layoutParams)
|
|
|
|
|
+
|
|
|
|
|
+ val downX = AtomicReference(0f)
|
|
|
|
|
+ val downY = AtomicReference(0f)
|
|
|
|
|
+ val downParamX = AtomicReference(0)
|
|
|
|
|
+ val downParamY = AtomicReference(0)
|
|
|
|
|
+
|
|
|
|
|
+ binding!!.floatingWindow.setOnTouchListener { v: View?, event: MotionEvent ->
|
|
|
|
|
+ when (event.action) {
|
|
|
|
|
+ MotionEvent.ACTION_DOWN -> {
|
|
|
|
|
+ downX.set(event.rawX)
|
|
|
|
|
+ downY.set(event.rawY)
|
|
|
|
|
+ downParamX.set(layoutParams.x)
|
|
|
|
|
+ downParamY.set(layoutParams.y)
|
|
|
|
|
+ return@setOnTouchListener true
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ MotionEvent.ACTION_MOVE -> {
|
|
|
|
|
+ layoutParams.x = min(
|
|
|
|
|
+ max((downParamX.get() + (event.rawX - downX.get())).toDouble(), 0.0),
|
|
|
|
|
+ maxX.toDouble()
|
|
|
|
|
+ )
|
|
|
|
|
+ .toInt()
|
|
|
|
|
+ layoutParams.y = min(
|
|
|
|
|
+ max((downParamY.get() + (event.rawY - downY.get())).toDouble(), 0.0),
|
|
|
|
|
+ maxY.toDouble()
|
|
|
|
|
+ )
|
|
|
|
|
+ .toInt()
|
|
|
|
|
+ windowManager.updateViewLayout(mLayout, layoutParams)
|
|
|
|
|
+ return@setOnTouchListener true
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
- return false;
|
|
|
|
|
- });
|
|
|
|
|
|
|
+ false
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
|
|
|
|
|
- binding.swConnect.setChecked(true);
|
|
|
|
|
- binding.swConnect.setOnCheckedChangeListener((buttonView, isChecked) -> {
|
|
|
|
|
|
|
+ binding!!.swConnect.isChecked = true
|
|
|
|
|
+ binding!!.swConnect.setOnCheckedChangeListener { buttonView: CompoundButton?, isChecked: Boolean ->
|
|
|
if (isChecked) {
|
|
if (isChecked) {
|
|
|
- connect();
|
|
|
|
|
|
|
+ connect()
|
|
|
} else {
|
|
} else {
|
|
|
- mSocket.disconnect();
|
|
|
|
|
|
|
+ if (mSocket != null) {
|
|
|
|
|
+ mSocket!!.disconnect()
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- canSend = getSharedPreferences(BuildConfig.APPLICATION_ID, MODE_PRIVATE).getBoolean("canSend", false);
|
|
|
|
|
- binding.swSend.setChecked(canSend);
|
|
|
|
|
- binding.swSend.setOnCheckedChangeListener((buttonView, isChecked) -> {
|
|
|
|
|
- getSharedPreferences(BuildConfig.APPLICATION_ID, MODE_PRIVATE).edit().putBoolean("canSend", isChecked).apply();
|
|
|
|
|
- canSend = isChecked;
|
|
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- updateDevice("canSend", canSend);
|
|
|
|
|
- });
|
|
|
|
|
|
|
+ canSend = getSharedPreferences(BuildConfig.APPLICATION_ID, MODE_PRIVATE).getBoolean(
|
|
|
|
|
+ "canSend",
|
|
|
|
|
+ false
|
|
|
|
|
+ )
|
|
|
|
|
+ binding!!.swSend.isChecked = canSend
|
|
|
|
|
+ binding!!.swSend.setOnCheckedChangeListener { buttonView: CompoundButton?, isChecked: Boolean ->
|
|
|
|
|
+ getSharedPreferences(BuildConfig.APPLICATION_ID, MODE_PRIVATE).edit()
|
|
|
|
|
+ .putBoolean("canSend", isChecked).apply()
|
|
|
|
|
+ canSend = isChecked
|
|
|
|
|
+ updateDevice("canSend", canSend)
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
-// PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
|
|
|
|
|
|
|
+ // PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
|
|
|
// PowerManager.WakeLock wakeLock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK, "RCS:MyWakelockTag");
|
|
// PowerManager.WakeLock wakeLock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK, "RCS:MyWakelockTag");
|
|
|
// wakeLock.acquire();
|
|
// wakeLock.acquire();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- private void updateDevice(String key, Object value) {
|
|
|
|
|
- JSONObject data = new JSONObject();
|
|
|
|
|
|
|
+ private fun updateDevice(key: String, value: Any) {
|
|
|
|
|
+ val data = JSONObject()
|
|
|
try {
|
|
try {
|
|
|
- data.put("action", "updateDevice");
|
|
|
|
|
- JSONObject dataObj = new JSONObject();
|
|
|
|
|
- dataObj.put(key, value);
|
|
|
|
|
- data.put("data", dataObj);
|
|
|
|
|
- mSocket.emit("message", data);
|
|
|
|
|
- } catch (JSONException e) {
|
|
|
|
|
- e.printStackTrace();
|
|
|
|
|
|
|
+ data.put("action", "updateDevice")
|
|
|
|
|
+ val dataObj = JSONObject()
|
|
|
|
|
+ dataObj.put(key, value)
|
|
|
|
|
+ data.put("data", dataObj)
|
|
|
|
|
+ mSocket!!.emit("message", data)
|
|
|
|
|
+ } catch (e: JSONException) {
|
|
|
|
|
+ e.printStackTrace()
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ companion object {
|
|
|
|
|
+ private const val TAG = "ModifierService"
|
|
|
|
|
+
|
|
|
|
|
+ const val NAME: String = BuildConfig.APPLICATION_ID + ".ModifierService"
|
|
|
|
|
+
|
|
|
|
|
+ @JvmStatic
|
|
|
|
|
+ var instance: ModifierService? = null
|
|
|
|
|
+ private set
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|