初步实现单点点击
This commit is contained in:
parent
ee36b43981
commit
e5f52aaba3
@ -8,7 +8,7 @@ android {
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "com.auto.autoclicker"
|
||||
minSdk = 23
|
||||
minSdk = 24
|
||||
targetSdk = 35
|
||||
versionCode = 1
|
||||
versionName = "1.0.0"
|
||||
|
||||
@ -2,119 +2,103 @@ package com.auto.autoclicker;
|
||||
|
||||
import android.accessibilityservice.AccessibilityService;
|
||||
import android.accessibilityservice.GestureDescription;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Path;
|
||||
import android.os.Build;
|
||||
import android.graphics.Point;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.view.accessibility.AccessibilityEvent;
|
||||
|
||||
/**
|
||||
* 无障碍服务,执行自动点击操作
|
||||
*/
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
|
||||
public class AutoClickService extends AccessibilityService {
|
||||
private static final String TAG = "AutoClickService";
|
||||
private static volatile AutoClickService instance;
|
||||
private static final long MIN_CLICK_INTERVAL = 100;
|
||||
private static final long MAX_CLICK_INTERVAL = 10000;
|
||||
private static final int MIN_CLICK_DURATION = 50;
|
||||
private static final int MAX_CLICK_DURATION = 1000;
|
||||
private final Handler handler = new Handler(Looper.getMainLooper());
|
||||
private boolean isClicking = false;
|
||||
private int clickX = 500;
|
||||
private int clickY = 500;
|
||||
private long clickInterval = 1000; // 默认1秒间隔
|
||||
private int clickDuration = 200; // 默认200毫秒点击时长
|
||||
private int screenWidth, screenHeight;
|
||||
private long clickInterval = 1000;
|
||||
private int clickDuration = 200;
|
||||
private int screenWidth;
|
||||
private int screenHeight;
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
instance = this;
|
||||
// 获取屏幕尺寸
|
||||
DisplayMetrics metrics = getResources().getDisplayMetrics();
|
||||
screenWidth = metrics.widthPixels;
|
||||
screenHeight = metrics.heightPixels;
|
||||
Log.d(TAG, "屏幕尺寸: " + screenWidth + "x" + screenHeight);
|
||||
Point screenSize = ScreenUtils.getScreenSize(this);
|
||||
screenWidth = screenSize.x;
|
||||
screenHeight = screenSize.y;
|
||||
logDebug("屏幕尺寸: " + screenWidth + "x" + screenHeight);
|
||||
}
|
||||
|
||||
public static AutoClickService getInstance() {
|
||||
return instance;
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置点击位置
|
||||
*/
|
||||
public void setClickPosition(float x, float y) {
|
||||
this.clickX = (int) Math.max(0, Math.min(x, screenWidth));
|
||||
this.clickY = (int) Math.max(0, Math.min(y, screenHeight));
|
||||
Log.d(TAG, "点击位置设置为: (" + clickX + ", " + clickY + ")");
|
||||
Log.d(TAG, "设置点击位置为: (" + clickX + ", " + clickY + "), 屏幕尺寸: " + screenWidth + "x" + screenHeight);
|
||||
Point constrainedPoint = ViewUtils.constrainToScreen(x, y, screenWidth, screenHeight);
|
||||
this.clickX = constrainedPoint.x;
|
||||
this.clickY = constrainedPoint.y;
|
||||
logDebug("设置点击位置: x=" + x + ", y=" + y + ", 修正后: (" + clickX + ", " + clickY + ")");
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置点击间隔
|
||||
*/
|
||||
public void setClickInterval(long interval) {
|
||||
if (interval > 0) {
|
||||
this.clickInterval = interval;
|
||||
Log.d(TAG, "点击间隔设置为: " + interval + "ms");
|
||||
if (interval < MIN_CLICK_INTERVAL || interval > MAX_CLICK_INTERVAL) {
|
||||
Log.w(TAG, "点击间隔超出合理范围: " + interval + "ms");
|
||||
return;
|
||||
}
|
||||
this.clickInterval = interval;
|
||||
logDebug("点击间隔设置为: " + interval + "ms");
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置点击时长
|
||||
*/
|
||||
public void setClickDuration(int duration) {
|
||||
if (duration > 0) {
|
||||
this.clickDuration = duration;
|
||||
Log.d(TAG, "点击时长设置为: " + duration + "ms");
|
||||
if (duration < MIN_CLICK_DURATION || duration > MAX_CLICK_DURATION) {
|
||||
Log.w(TAG, "点击时长超出合理范围: " + duration + "ms");
|
||||
return;
|
||||
}
|
||||
this.clickDuration = duration;
|
||||
logDebug("点击时长设置为: " + duration + "ms");
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始自动点击
|
||||
*/
|
||||
public void startClicking() {
|
||||
if (!isClicking) {
|
||||
isClicking = true;
|
||||
performClick();
|
||||
Log.d(TAG, "开始点击: (" + clickX + ", " + clickY + ")");
|
||||
logDebug("开始点击: (" + clickX + ", " + clickY + ")");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止自动点击
|
||||
*/
|
||||
public void stopClicking() {
|
||||
if (isClicking) {
|
||||
isClicking = false;
|
||||
handler.removeCallbacksAndMessages(null);
|
||||
Log.d(TAG, "停止点击");
|
||||
logDebug("停止点击");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行单次点击
|
||||
*/
|
||||
private void performClick() {
|
||||
if (!isClicking || Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
|
||||
Log.w(TAG, "点击被跳过(未开始或系统版本不支持)");
|
||||
if (!isClicking) {
|
||||
logDebug("点击被跳过(未开始)");
|
||||
return;
|
||||
}
|
||||
|
||||
logDebug("执行点击: (" + clickX + ", " + clickY + ")");
|
||||
Path path = new Path();
|
||||
path.moveTo(clickX, clickY);
|
||||
path.lineTo(clickX, clickY); // 创建一个点状路径
|
||||
|
||||
GestureDescription.StrokeDescription stroke =
|
||||
new GestureDescription.StrokeDescription(path, 0, 10); // 10ms 点击
|
||||
|
||||
new GestureDescription.StrokeDescription(path, 0, clickDuration);
|
||||
GestureDescription gesture =
|
||||
new GestureDescription.Builder().addStroke(stroke).build();
|
||||
|
||||
dispatchGesture(gesture, new GestureResultCallback() {
|
||||
@Override
|
||||
public void onCompleted(GestureDescription gestureDescription) {
|
||||
super.onCompleted(gestureDescription);
|
||||
Log.i(TAG, "点击完成: (" + clickX + ", " + clickY + ")");
|
||||
logDebug("点击完成: (" + clickX + ", " + clickY + ")");
|
||||
if (isClicking) {
|
||||
handler.postDelayed(() -> performClick(), clickInterval);
|
||||
}
|
||||
@ -122,78 +106,44 @@ public class AutoClickService extends AccessibilityService {
|
||||
|
||||
@Override
|
||||
public void onCancelled(GestureDescription gestureDescription) {
|
||||
super.onCancelled(gestureDescription);
|
||||
Log.e(TAG, "点击取消(可能位置错误或界面变化): (" + clickX + ", " + clickY + ")");
|
||||
Log.e(TAG, "点击取消: (" + clickX + ", " + clickY + ")");
|
||||
if (isClicking) {
|
||||
handler.postDelayed(() -> performClick(), clickInterval + 300); // 稍微延迟重试
|
||||
handler.postDelayed(() -> performClick(), clickInterval + 300);
|
||||
}
|
||||
}
|
||||
}, null);
|
||||
}
|
||||
|
||||
public void testSingleClick(float x, float y) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
|
||||
Log.e(TAG, "设备不支持手势(需要 API 24+)");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
Path path = new Path();
|
||||
path.moveTo(x, y);
|
||||
path.lineTo(x, y); // 明确指定按下和抬起
|
||||
Log.d(TAG, "测试点击: (" + x + ", " + y + ")");
|
||||
|
||||
GestureDescription.StrokeDescription stroke = new GestureDescription.StrokeDescription(
|
||||
path, 0, 100
|
||||
);
|
||||
GestureDescription gesture = new GestureDescription.Builder()
|
||||
.addStroke(stroke)
|
||||
.build();
|
||||
|
||||
boolean dispatched = dispatchGesture(gesture, new GestureResultCallback() {
|
||||
@Override
|
||||
public void onCompleted(GestureDescription gestureDescription) {
|
||||
Log.v(TAG, "测试点击完成: (" + x + ", " + y + ")");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCancelled(GestureDescription gestureDescription) {
|
||||
Log.w(TAG, "测试点击取消: (" + x + ", " + y + ")");
|
||||
}
|
||||
}, null);
|
||||
|
||||
Log.d(TAG, "手势分发结果: " + (dispatched ? "成功" : "失败"));
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "测试点击失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAccessibilityEvent(AccessibilityEvent event) {
|
||||
// 不处理事件
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInterrupt() {
|
||||
stopClicking();
|
||||
Log.d(TAG, "无障碍服务中断");
|
||||
logDebug("无障碍服务中断");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onServiceConnected() {
|
||||
super.onServiceConnected();
|
||||
Log.d(TAG, "无障碍服务已连接");
|
||||
}
|
||||
|
||||
public boolean isClicking() {
|
||||
return isClicking;
|
||||
logDebug("无障碍服务已连接");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
stopClicking();
|
||||
instance = null;
|
||||
Log.d(TAG, "无障碍服务已销毁");
|
||||
Intent intent = new Intent("com.auto.autoclicker.SERVICE_DESTROYED");
|
||||
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
|
||||
logDebug("无障碍服务已销毁");
|
||||
}
|
||||
|
||||
public boolean isClicking() {
|
||||
return isClicking;
|
||||
}
|
||||
|
||||
private void logDebug(String message) {
|
||||
Log.d(TAG, message);
|
||||
}
|
||||
}
|
||||
@ -1,11 +1,11 @@
|
||||
package com.auto.autoclicker;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.content.Intent;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.graphics.Point;
|
||||
import android.os.Build;
|
||||
import android.provider.Settings;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
@ -16,11 +16,9 @@ import android.widget.Button;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.Toast;
|
||||
|
||||
/**
|
||||
* 悬浮窗管理器,负责触摸点和控制栏的显示与交互
|
||||
*/
|
||||
public class FloatingViewManager {
|
||||
private static final String TAG = "FloatingViewManager";
|
||||
private static final long DEBOUNCE_INTERVAL = 500;
|
||||
private final Context context;
|
||||
private final WindowManager windowManager;
|
||||
private View touchPointView;
|
||||
@ -30,32 +28,28 @@ public class FloatingViewManager {
|
||||
private float touchPointX = 500;
|
||||
private float touchPointY = 500;
|
||||
private boolean isClicking = false;
|
||||
private int screenWidth, screenHeight;
|
||||
private boolean isFloatingViewsShown = false;
|
||||
private long lastToggleTime = 0;
|
||||
private static final long DEBOUNCE_INTERVAL = 500;
|
||||
private static final int CONTROL_BAR_WIDTH = 200; // 假设控制栏宽度
|
||||
private static final int CONTROL_BAR_HEIGHT = 100; // 假设控制栏高度
|
||||
private final int screenWidth;
|
||||
private final int screenHeight;
|
||||
|
||||
public FloatingViewManager(Context context) {
|
||||
this.context = context;
|
||||
this.windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
|
||||
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
|
||||
screenWidth = metrics.widthPixels;
|
||||
screenHeight = metrics.heightPixels;
|
||||
Log.d(TAG, "屏幕尺寸: " + screenWidth + "x" + screenHeight);
|
||||
Point screenSize = ScreenUtils.getScreenSize(context);
|
||||
this.screenWidth = screenSize.x;
|
||||
this.screenHeight = screenSize.y;
|
||||
logDebug("屏幕尺寸: " + screenWidth + "x" + screenHeight);
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示悬浮窗(触摸点和控制栏)
|
||||
*/
|
||||
public void showFloatingViews() throws SecurityException {
|
||||
if (!Settings.canDrawOverlays(context)) {
|
||||
throw new SecurityException("需要悬浮窗权限");
|
||||
}
|
||||
|
||||
if (touchPointView != null || controlBarView != null) {
|
||||
Log.w(TAG, "悬浮窗已存在,跳过创建");
|
||||
return;
|
||||
if (isFloatingViewsShown) {
|
||||
logDebug("悬浮窗已存在,清理后重新创建");
|
||||
removeFloatingViews();
|
||||
}
|
||||
|
||||
initializeTouchPointView();
|
||||
@ -63,15 +57,13 @@ public class FloatingViewManager {
|
||||
|
||||
windowManager.addView(touchPointView, touchPointParams);
|
||||
windowManager.addView(controlBarView, controlBarParams);
|
||||
Log.d(TAG, "悬浮窗已添加");
|
||||
isFloatingViewsShown = true;
|
||||
logDebug("悬浮窗已添加");
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化触摸点视图
|
||||
*/
|
||||
private void initializeTouchPointView() {
|
||||
touchPointView = new View(context);
|
||||
touchPointView.setBackgroundResource(R.drawable.shoot); // 使用触摸点图标
|
||||
touchPointView.setBackgroundResource(R.drawable.un_touch_point);
|
||||
touchPointParams = new WindowManager.LayoutParams(
|
||||
100, 100,
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ?
|
||||
@ -81,7 +73,7 @@ public class FloatingViewManager {
|
||||
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL,
|
||||
PixelFormat.TRANSLUCENT
|
||||
);
|
||||
touchPointParams.gravity = Gravity.TOP | Gravity.LEFT;
|
||||
touchPointParams.gravity = Gravity.TOP | Gravity.START;
|
||||
touchPointParams.x = (int) touchPointX;
|
||||
touchPointParams.y = (int) touchPointY;
|
||||
|
||||
@ -101,15 +93,11 @@ public class FloatingViewManager {
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
float dx = event.getRawX() - lastX;
|
||||
float dy = event.getRawY() - lastY;
|
||||
touchPointParams.x = (int) Math.max(0, Math.min(paramX + dx, screenWidth - 100));
|
||||
touchPointParams.y = (int) Math.max(0, Math.min(paramY + dy, screenHeight - 100));
|
||||
touchPointX = touchPointParams.x;
|
||||
touchPointY = touchPointParams.y;
|
||||
windowManager.updateViewLayout(touchPointView, touchPointParams);
|
||||
updateTouchPointPosition(paramX, paramY, dx, dy);
|
||||
AutoClickService service = AutoClickService.getInstance();
|
||||
if (service != null) {
|
||||
service.setClickPosition(touchPointX + 50, touchPointY + 50);
|
||||
Log.d(TAG, "触摸点移动到: (" + touchPointX + ", " + touchPointY + ")");
|
||||
logDebug("触摸点移动到: (" + touchPointX + ", " + touchPointY + ")");
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -118,9 +106,16 @@ public class FloatingViewManager {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化控制栏视图
|
||||
*/
|
||||
private void updateTouchPointPosition(float paramX, float paramY, float dx, float dy) {
|
||||
Point constrainedPoint = ViewUtils.constrainToScreen(
|
||||
paramX + dx, paramY + dy, screenWidth - touchPointView.getWidth(), screenHeight - touchPointView.getHeight());
|
||||
touchPointParams.x = constrainedPoint.x;
|
||||
touchPointParams.y = constrainedPoint.y;
|
||||
touchPointX = touchPointParams.x;
|
||||
touchPointY = touchPointParams.y;
|
||||
windowManager.updateViewLayout(touchPointView, touchPointParams);
|
||||
}
|
||||
|
||||
private void initializeControlBarView() {
|
||||
controlBarView = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.control_bar, null);
|
||||
controlBarParams = new WindowManager.LayoutParams(
|
||||
@ -133,11 +128,10 @@ public class FloatingViewManager {
|
||||
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL,
|
||||
PixelFormat.TRANSLUCENT
|
||||
);
|
||||
controlBarParams.gravity = Gravity.TOP | Gravity.LEFT;
|
||||
controlBarParams.gravity = Gravity.TOP | Gravity.START;
|
||||
controlBarParams.x = 0;
|
||||
controlBarParams.y = 200;
|
||||
|
||||
// 替换 controlBarView 的 onTouchListener 和 toggleButton 的 OnClickListener
|
||||
controlBarView.setOnTouchListener(new View.OnTouchListener() {
|
||||
private float lastX, lastY;
|
||||
private float paramX, paramY;
|
||||
@ -154,9 +148,7 @@ public class FloatingViewManager {
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
float dx = event.getRawX() - lastX;
|
||||
float dy = event.getRawY() - lastY;
|
||||
controlBarParams.x = (int) Math.max(0, Math.min(paramX + dx, screenWidth - CONTROL_BAR_WIDTH));
|
||||
controlBarParams.y = (int) Math.max(0, Math.min(paramY + dy, screenHeight - CONTROL_BAR_HEIGHT));
|
||||
windowManager.updateViewLayout(controlBarView, controlBarParams);
|
||||
updateControlBarPosition(paramX, paramY, dx, dy);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@ -165,50 +157,55 @@ public class FloatingViewManager {
|
||||
|
||||
Button toggleButton = controlBarView.findViewById(R.id.toggle_button);
|
||||
toggleButton.setOnClickListener(v -> {
|
||||
long currentTime = System.currentTimeMillis();
|
||||
if (currentTime - lastToggleTime < DEBOUNCE_INTERVAL) {
|
||||
if (!isDebounced()) {
|
||||
return;
|
||||
}
|
||||
lastToggleTime = currentTime;
|
||||
|
||||
AutoClickService service = AutoClickService.getInstance();
|
||||
if (service == null) {
|
||||
Log.e(TAG, "AutoClickService 未初始化");
|
||||
Toast.makeText(context, "请启用无障碍服务", Toast.LENGTH_SHORT).show();
|
||||
Toast.makeText(context, "请在设置中启用无障碍服务", Toast.LENGTH_LONG).show();
|
||||
Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
context.startActivity(intent);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isClicking) {
|
||||
service.stopClicking();
|
||||
toggleButton.setText(R.string.start_click);
|
||||
touchPointView.setBackgroundResource(R.drawable.shoot);
|
||||
touchPointView.setBackgroundResource(R.drawable.un_touch_point);
|
||||
Toast.makeText(context, "停止自动点击", Toast.LENGTH_SHORT).show();
|
||||
} else {
|
||||
service.setClickPosition(touchPointX + 50, touchPointY + 50);
|
||||
service.startClicking();
|
||||
toggleButton.setText(R.string.stop_click);
|
||||
touchPointView.setBackgroundColor(Color.GREEN);
|
||||
touchPointView.setBackgroundResource(R.drawable.touch_point);
|
||||
Toast.makeText(context, "开始自动点击", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
isClicking = !isClicking;
|
||||
|
||||
// 测试单次点击(屏幕中央)
|
||||
float testX = screenWidth / 2f;
|
||||
float testY = screenHeight / 2f;
|
||||
service.testSingleClick(testX, testY);
|
||||
Toast.makeText(context, "测试点击: (" + testX + ", " + testY + ")", Toast.LENGTH_SHORT).show();
|
||||
|
||||
Log.d(TAG, "AutoClickService 实例:" + service);
|
||||
Log.d(TAG, "服务是否点击中:" + service.isClicking());
|
||||
|
||||
logDebug("服务是否点击中: " + service.isClicking());
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除悬浮窗
|
||||
*/
|
||||
private void updateControlBarPosition(float paramX, float paramY, float dx, float dy) {
|
||||
Point constrainedPoint = ViewUtils.constrainToScreen(
|
||||
paramX + dx, paramY + dy, screenWidth - controlBarView.getWidth(), screenHeight - controlBarView.getHeight());
|
||||
controlBarParams.x = constrainedPoint.x;
|
||||
controlBarParams.y = constrainedPoint.y;
|
||||
windowManager.updateViewLayout(controlBarView, controlBarParams);
|
||||
}
|
||||
|
||||
private boolean isDebounced() {
|
||||
long currentTime = System.currentTimeMillis();
|
||||
if (currentTime - lastToggleTime < DEBOUNCE_INTERVAL) {
|
||||
return false;
|
||||
}
|
||||
lastToggleTime = currentTime;
|
||||
return true;
|
||||
}
|
||||
|
||||
public void removeFloatingViews() {
|
||||
try {
|
||||
if (touchPointView != null) {
|
||||
@ -219,9 +216,15 @@ public class FloatingViewManager {
|
||||
windowManager.removeView(controlBarView);
|
||||
controlBarView = null;
|
||||
}
|
||||
Log.d(TAG, "悬浮窗已移除");
|
||||
isFloatingViewsShown = false;
|
||||
logDebug("悬浮窗已移除");
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "移除悬浮窗失败", e);
|
||||
isFloatingViewsShown = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void logDebug(String message) {
|
||||
Log.d(TAG, message);
|
||||
}
|
||||
}
|
||||
@ -12,9 +12,6 @@ import android.util.Log;
|
||||
|
||||
import androidx.core.app.NotificationCompat;
|
||||
|
||||
/**
|
||||
* 前台服务,管理自动点击的悬浮窗和通知
|
||||
*/
|
||||
public class ForegroundService extends Service {
|
||||
private static final String TAG = "ForegroundService";
|
||||
private static final String CHANNEL_ID = "AutoClickerChannel";
|
||||
@ -36,7 +33,6 @@ public class ForegroundService extends Service {
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
// 创建通知
|
||||
Intent notificationIntent = new Intent(this, MainActivity.class);
|
||||
PendingIntent pendingIntent = PendingIntent.getActivity(
|
||||
this, 0, notificationIntent, PendingIntent.FLAG_IMMUTABLE
|
||||
|
||||
@ -15,16 +15,13 @@ import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.contract.ActivityResultContracts;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
/**
|
||||
* 主活动,负责权限检查和控制悬浮窗显示/隐藏
|
||||
*/
|
||||
|
||||
public class MainActivity extends AppCompatActivity {
|
||||
private static final String TAG = "MainActivity";
|
||||
private Button startButton;
|
||||
private ActivityResultLauncher<Intent> permissionLauncher;
|
||||
private boolean isFloatingShown = false;
|
||||
private long lastClickTime = 0;
|
||||
private static final long DEBOUNCE_INTERVAL = 500; // 防抖间隔(毫秒)
|
||||
private static final long DEBOUNCE_INTERVAL = 500;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
@ -34,22 +31,16 @@ public class MainActivity extends AppCompatActivity {
|
||||
startButton = findViewById(R.id.start_Button);
|
||||
startButton.setOnClickListener(v -> toggleFloatingWindow());
|
||||
|
||||
// 初始化权限请求回调
|
||||
permissionLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> checkPermissions());
|
||||
|
||||
// 同步服务状态
|
||||
syncServiceState();
|
||||
checkPermissions();
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查必要的权限:无障碍服务、悬浮窗、电池优化
|
||||
*/
|
||||
private void checkPermissions() {
|
||||
boolean allPermissionsGranted = true;
|
||||
|
||||
// 检查无障碍服务
|
||||
if (!isAccessibilityServiceEnabled()) {
|
||||
if (isAccessibilityServiceEnabled()) {
|
||||
allPermissionsGranted = false;
|
||||
try {
|
||||
Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);
|
||||
@ -60,8 +51,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
}
|
||||
}
|
||||
|
||||
// 检查悬浮窗权限
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !Settings.canDrawOverlays(this)) {
|
||||
if (!Settings.canDrawOverlays(this)) {
|
||||
allPermissionsGranted = false;
|
||||
try {
|
||||
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getPackageName()));
|
||||
@ -72,30 +62,23 @@ public class MainActivity extends AppCompatActivity {
|
||||
}
|
||||
}
|
||||
|
||||
// 检查电池优化
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
|
||||
if (!pm.isIgnoringBatteryOptimizations(getPackageName())) {
|
||||
allPermissionsGranted = false;
|
||||
try {
|
||||
Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
|
||||
intent.setData(Uri.parse("package:" + getPackageName()));
|
||||
permissionLauncher.launch(intent);
|
||||
Toast.makeText(this, "请禁用电池优化", Toast.LENGTH_LONG).show();
|
||||
} catch (Exception e) {
|
||||
Toast.makeText(this, "无法打开电池优化设置", Toast.LENGTH_LONG).show();
|
||||
}
|
||||
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
|
||||
if (!pm.isIgnoringBatteryOptimizations(getPackageName())) {
|
||||
allPermissionsGranted = false;
|
||||
try {
|
||||
Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
|
||||
intent.setData(Uri.parse("package:" + getPackageName()));
|
||||
permissionLauncher.launch(intent);
|
||||
Toast.makeText(this, "请禁用电池优化", Toast.LENGTH_LONG).show();
|
||||
} catch (Exception e) {
|
||||
Toast.makeText(this, "无法打开电池优化设置", Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
|
||||
// 更新按钮状态
|
||||
startButton.setEnabled(allPermissionsGranted);
|
||||
updateButtonText();
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查无障碍服务是否启用
|
||||
*/
|
||||
private boolean isAccessibilityServiceEnabled() {
|
||||
String service = getPackageName() + "/" + AutoClickService.class.getCanonicalName();
|
||||
try {
|
||||
@ -106,47 +89,37 @@ public class MainActivity extends AppCompatActivity {
|
||||
String settingValue = Settings.Secure.getString(
|
||||
getContentResolver(),
|
||||
Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
|
||||
return settingValue != null && settingValue.contains(service);
|
||||
return settingValue == null || !settingValue.contains(service);
|
||||
}
|
||||
} catch (Settings.SettingNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步服务状态(从通知栏进入时)
|
||||
*/
|
||||
private void syncServiceState() {
|
||||
isFloatingShown = isServiceRunning(ForegroundService.class);
|
||||
isFloatingShown = isServiceRunning();
|
||||
updateButtonText();
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查服务是否运行
|
||||
*/
|
||||
private boolean isServiceRunning(Class<?> serviceClass) {
|
||||
private boolean isServiceRunning() {
|
||||
ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
|
||||
for (ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
|
||||
if (serviceClass.getName().equals(service.service.getClassName())) {
|
||||
if (ForegroundService.class.getName().equals(service.service.getClassName())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换悬浮窗显示/隐藏
|
||||
*/
|
||||
private void toggleFloatingWindow() {
|
||||
// 防抖处理
|
||||
long currentTime = System.currentTimeMillis();
|
||||
if (currentTime - lastClickTime < DEBOUNCE_INTERVAL) {
|
||||
return;
|
||||
}
|
||||
lastClickTime = currentTime;
|
||||
|
||||
if (!isAccessibilityServiceEnabled() || !Settings.canDrawOverlays(this)) {
|
||||
if (isAccessibilityServiceEnabled() || !Settings.canDrawOverlays(this)) {
|
||||
Toast.makeText(this, "请授予所有必要权限", Toast.LENGTH_LONG).show();
|
||||
checkPermissions();
|
||||
return;
|
||||
@ -169,9 +142,6 @@ public class MainActivity extends AppCompatActivity {
|
||||
updateButtonText();
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新按钮文本
|
||||
*/
|
||||
private void updateButtonText() {
|
||||
startButton.setText(isFloatingShown ? R.string.hide_floating_window : R.string.show_floating_window);
|
||||
}
|
||||
|
||||
14
app/src/main/java/com/auto/autoclicker/ScreenUtils.java
Normal file
14
app/src/main/java/com/auto/autoclicker/ScreenUtils.java
Normal file
@ -0,0 +1,14 @@
|
||||
package com.auto.autoclicker;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Point;
|
||||
import android.util.DisplayMetrics;
|
||||
|
||||
|
||||
public class ScreenUtils {
|
||||
|
||||
public static Point getScreenSize(Context context) {
|
||||
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
|
||||
return new Point(metrics.widthPixels, metrics.heightPixels);
|
||||
}
|
||||
}
|
||||
13
app/src/main/java/com/auto/autoclicker/ViewUtils.java
Normal file
13
app/src/main/java/com/auto/autoclicker/ViewUtils.java
Normal file
@ -0,0 +1,13 @@
|
||||
package com.auto.autoclicker;
|
||||
|
||||
import android.graphics.Point;
|
||||
|
||||
|
||||
public class ViewUtils {
|
||||
|
||||
public static Point constrainToScreen(float x, float y, int maxWidth, int maxHeight) {
|
||||
int constrainedX = (int) Math.max(0, Math.min(x, maxWidth));
|
||||
int constrainedY = (int) Math.max(0, Math.min(y, maxHeight));
|
||||
return new Point(constrainedX, constrainedY);
|
||||
}
|
||||
}
|
||||
@ -5,5 +5,5 @@
|
||||
android:viewportHeight="1024">
|
||||
<path
|
||||
android:pathData="M502.7,448a56,56 0,1 0,0 112,56 56,0 0,0 0,-112zM502.7,96C277.3,96 94.6,278.7 94.6,504S277.3,912 502.6,912C728,912 910.7,729.3 910.7,504S728,96 502.7,96zM544,829.3L544,688h-80v141.3C320,811.1 195.5,688 177.3,544L320,544v-80h-142.7C195.5,320 320,196.8 464,178.7L464,320h80v-141.3C688,196.8 809.8,320 828,464L688,464v80h140C809.8,688 688,811.2 544,829.3z"
|
||||
android:fillColor="#565D64"/>
|
||||
android:fillColor="#FF0057"/>
|
||||
</vector>
|
||||
9
app/src/main/res/drawable/un_touch_point.xml
Normal file
9
app/src/main/res/drawable/un_touch_point.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="32dp"
|
||||
android:height="32dp"
|
||||
android:viewportWidth="1024"
|
||||
android:viewportHeight="1024">
|
||||
<path
|
||||
android:pathData="M502.7,448a56,56 0,1 0,0 112,56 56,0 0,0 0,-112zM502.7,96C277.3,96 94.6,278.7 94.6,504S277.3,912 502.6,912C728,912 910.7,729.3 910.7,504S728,96 502.7,96zM544,829.3L544,688h-80v141.3C320,811.1 195.5,688 177.3,544L320,544v-80h-142.7C195.5,320 320,196.8 464,178.7L464,320h80v-141.3C688,196.8 809.8,320 828,464L688,464v80h140C809.8,688 688,811.2 544,829.3z"
|
||||
android:fillColor="#565D64"/>
|
||||
</vector>
|
||||
@ -1,8 +1,9 @@
|
||||
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:accessibilityEventTypes="typeAllMask"
|
||||
android:accessibilityFeedbackType="feedbackGeneric"
|
||||
android:accessibilityFeedbackType="feedbackAllMask"
|
||||
android:accessibilityFlags="flagDefault|flagRequestTouchExplorationMode|flagRequestEnhancedWebAccessibility|flagReportViewIds"
|
||||
android:canRetrieveWindowContent="true"
|
||||
android:canPerformGestures="true"
|
||||
android:description="@string/accessibility_service_description"
|
||||
android:notificationTimeout="100"
|
||||
android:packageNames="com.auto.autoclicker" />
|
||||
Loading…
Reference in New Issue
Block a user