diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 8a4f149..a8db7c9 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -27,7 +27,6 @@ - - multiClickPoints = new ArrayList<>(); + private static final String TAG = "AutoClickService"; private static AutoClickService instance; + private final List multiClickPositions = new ArrayList<>(); + private int currentClickIndex = 0; + private static final long MIN_CLICK_INTERVAL = 40; private static final long MAX_CLICK_INTERVAL = 10000; private static final int MIN_CLICK_DURATION = 50; @@ -44,6 +52,10 @@ public class AutoClickService extends AccessibilityService { logDebug("屏幕尺寸: " + screenWidth + "x" + screenHeight); } + public static AutoClickService getInstance() { + return instance; + } + @Override public void onServiceConnected() { super.onServiceConnected(); @@ -56,25 +68,17 @@ public class AutoClickService extends AccessibilityService { logDebug("无障碍服务中断"); } - @Override - public void onDestroy() { - super.onDestroy(); - instance = null; - stopClicking(); - Intent intent = new Intent("com.auto.autoclicker.SERVICE_DESTROYED"); - LocalBroadcastManager.getInstance(this).sendBroadcast(intent); - logDebug("无障碍服务已销毁"); - } - - public static AutoClickService getInstance() { - return instance; - } - public void setClickPosition(float x, float y) { Point constrained = ViewUtils.constrainToScreen(x, y, screenWidth, screenHeight); this.clickX = constrained.x; this.clickY = constrained.y; - logDebug("设置点击位置: 原始 (" + x + ", " + y + ") -> 修正 (" + clickX + ", " + clickY + ")"); + logDebug("设置单点点击位置: 原始 (" + x + ", " + y + ") -> 修正 (" + clickX + ", " + clickY + ")"); + } + + public void setMultiClickPositions(List positions) { + this.multiClickPositions.clear(); + this.multiClickPositions.addAll(positions); + logDebug("设置多点点击位置: " + positions.size() + " 个点"); } public void setClickInterval(long interval) { @@ -97,7 +101,12 @@ public class AutoClickService extends AccessibilityService { public void startClicking() { if (!isClicking) { + if (multiClickPositions.isEmpty() && (clickX == 0 && clickY == 0)) { + Log.w(TAG, "无有效点击位置,忽略开始点击"); + return; + } isClicking = true; + currentClickIndex = 0; logDebug("开始自动点击"); performClick(); } @@ -121,11 +130,22 @@ public class AutoClickService extends AccessibilityService { return; } - logDebug("执行点击: (" + clickX + ", " + clickY + ")"); + // 决定使用单点还是多点点击 + Point clickPoint; + if (!multiClickPositions.isEmpty()) { + // 多点点击模式 + clickPoint = multiClickPositions.get(currentClickIndex); + currentClickIndex = (currentClickIndex + 1) % multiClickPositions.size(); + } else { + // 单点点击模式 + clickPoint = new Point(clickX, clickY); + } + + logDebug("执行点击: (" + clickPoint.x + ", " + clickPoint.y + ")"); flashTouchFeedback(); Path path = new Path(); - path.moveTo(clickX, clickY); + path.moveTo(clickPoint.x, clickPoint.y); GestureDescription.StrokeDescription stroke = new GestureDescription.StrokeDescription(path, 0, clickDuration); GestureDescription gesture = @@ -162,4 +182,14 @@ public class AutoClickService extends AccessibilityService { private void logDebug(String message) { Log.d(TAG, message); } + + @Override + public void onDestroy() { + super.onDestroy(); + instance = null; + stopClicking(); + Intent intent = new Intent("com.auto.autoclicker.SERVICE_DESTROYED"); + LocalBroadcastManager.getInstance(this).sendBroadcast(intent); + logDebug("无障碍服务已销毁"); + } } \ No newline at end of file diff --git a/app/src/main/java/com/auto/autoclicker/FloatingViewManager.java b/app/src/main/java/com/auto/autoclicker/FloatingViewManager.java index 4b36d9d..c1505fc 100644 --- a/app/src/main/java/com/auto/autoclicker/FloatingViewManager.java +++ b/app/src/main/java/com/auto/autoclicker/FloatingViewManager.java @@ -2,7 +2,6 @@ package com.auto.autoclicker; import android.annotation.SuppressLint; import android.app.Activity; -import android.app.AlertDialog; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -24,27 +23,45 @@ import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.Toast; +import androidx.appcompat.app.AlertDialog; import androidx.localbroadcastmanager.content.LocalBroadcastManager; import com.auto.autoclicker.util.PrefUtils; import com.auto.autoclicker.util.ScreenUtils; import com.auto.autoclicker.util.ViewUtils; +import java.util.ArrayList; +import java.util.List; + public class FloatingViewManager { private static final String TAG = "FloatingViewManager"; - private static final long DEBOUNCE_INTERVAL = 500; private final Context context; + + private static final long DEBOUNCE_INTERVAL = 500; + private final WindowManager windowManager; - private View touchPointView; - private LinearLayout controlBarView; - private WindowManager.LayoutParams touchPointParams; - private WindowManager.LayoutParams controlBarParams; + + private final List moreTouchPointViews = new ArrayList<>(); + private final List moreTouchPointParams = new ArrayList<>(); + + private View singleTouchPointView; + private WindowManager.LayoutParams singleTouchPointParams; + + private LinearLayout singleControlBarView; + private LinearLayout moreControlBarView; + + private WindowManager.LayoutParams singleControlBarParams; + private WindowManager.LayoutParams moreControlBarParams; + private float touchPointX = 500; private float touchPointY = 500; + private boolean isClicking = false; private boolean isFloatingViewsShown = false; + private long lastToggleTime = 0; + private final int screenWidth; private final int screenHeight; @@ -58,47 +75,61 @@ public class FloatingViewManager { logDebug("屏幕尺寸: " + screenWidth + "x" + screenHeight); } - public void showFloatingViews() throws SecurityException { + private void registerBroadcast(Context context) { + LocalBroadcastManager.getInstance(context).registerReceiver(new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if ("com.auto.autoclicker.FLASH_TOUCH_POINT".equals(intent.getAction())) { + flashTouchPoint(); + } + } + }, new IntentFilter("com.auto.autoclicker.FLASH_TOUCH_POINT")); + } + + public void showFloatingViews(int mode) throws SecurityException { if (!Settings.canDrawOverlays(context)) { throw new SecurityException("需要悬浮窗权限"); } - if (isFloatingViewsShown) { - logDebug("悬浮窗已存在,清理后重新创建"); - removeFloatingViews(); + removeFloatingViews(); + + if (mode == 1) { + initSingleTouchPointView(); + initSingleControlBar(); + + if (singleTouchPointView != null) { + windowManager.addView(singleTouchPointView, singleTouchPointParams); + } + + if (singleControlBarView != null) { + windowManager.addView(singleControlBarView, singleControlBarParams); + } + } else if (mode == 2) { + initMoreTouchPointView(); + initMoreControlBar(); + + if (moreTouchPointViews.size() == moreTouchPointParams.size()) { + for (int i = 0; i < moreTouchPointViews.size(); i++) { + windowManager.addView(moreTouchPointViews.get(i), moreTouchPointParams.get(i)); + } + } else { + Log.e(TAG, "触摸点与布局参数数量不一致"); + } + + + if (moreControlBarView != null) { + windowManager.addView(moreControlBarView, moreControlBarParams); + } } - initializeTouchPointView(); - initializeControlBarView(); - - windowManager.addView(touchPointView, touchPointParams); - windowManager.addView(controlBarView, controlBarParams); isFloatingViewsShown = true; - logDebug("悬浮窗已添加"); + logDebug("悬浮窗已添加,模式 = " + mode); } - public void removeFloatingViews() { - try { - if (touchPointView != null) { - windowManager.removeView(touchPointView); - touchPointView = null; - } - if (controlBarView != null) { - windowManager.removeView(controlBarView); - controlBarView = null; - } - isFloatingViewsShown = false; - logDebug("悬浮窗已移除"); - } catch (Exception e) { - Log.e(TAG, "移除悬浮窗失败", e); - isFloatingViewsShown = false; - } - } - - private void initializeTouchPointView() { - touchPointView = new View(context); - touchPointView.setBackgroundResource(R.drawable.un_touch_point); - touchPointParams = new WindowManager.LayoutParams( + private void initSingleTouchPointView() { + singleTouchPointView = new View(context); + singleTouchPointView.setBackgroundResource(R.drawable.un_touch_point); + singleTouchPointParams = new WindowManager.LayoutParams( 100, 100, Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ? WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY : @@ -107,11 +138,11 @@ public class FloatingViewManager { WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL, PixelFormat.TRANSLUCENT ); - touchPointParams.gravity = Gravity.TOP | Gravity.START; - touchPointParams.x = (int) touchPointX; - touchPointParams.y = (int) touchPointY; + singleTouchPointParams.gravity = Gravity.TOP | Gravity.START; + singleTouchPointParams.x = (int) touchPointX; + singleTouchPointParams.y = (int) touchPointY; - touchPointView.setOnTouchListener(new View.OnTouchListener() { + singleTouchPointView.setOnTouchListener(new View.OnTouchListener() { private float lastX, lastY; private float paramX, paramY; @@ -121,8 +152,8 @@ public class FloatingViewManager { case MotionEvent.ACTION_DOWN: lastX = event.getRawX(); lastY = event.getRawY(); - paramX = touchPointParams.x; - paramY = touchPointParams.y; + paramX = singleTouchPointParams.x; + paramY = singleTouchPointParams.y; break; case MotionEvent.ACTION_MOVE: float dx = event.getRawX() - lastX; @@ -131,7 +162,7 @@ public class FloatingViewManager { AutoClickService service = AutoClickService.getInstance(); if (service != null) { service.setClickPosition(touchPointX + 50, touchPointY + 50); - logDebug("触摸点移动到: (" + touchPointX + ", " + touchPointY + ")"); + logDebug("单点触摸点移动到: (" + touchPointX + ", " + touchPointY + ")"); } break; } @@ -140,10 +171,16 @@ public class FloatingViewManager { }); } + private void initMoreTouchPointView() { + moreTouchPointViews.clear(); + moreTouchPointParams.clear(); + addNewTouchPoint(100, 500); + } + @SuppressLint("ClickableViewAccessibility") - private void initializeControlBarView() { - controlBarView = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.single_control_bar, null); - controlBarParams = new WindowManager.LayoutParams( + private void initSingleControlBar() { + singleControlBarView = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.single_control_bar, null); + singleControlBarParams = new WindowManager.LayoutParams( WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT, Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ? @@ -153,11 +190,11 @@ public class FloatingViewManager { WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL, PixelFormat.TRANSLUCENT ); - controlBarParams.gravity = Gravity.TOP | Gravity.START; - controlBarParams.x = 0; - controlBarParams.y = 200; + singleControlBarParams.gravity = Gravity.TOP | Gravity.START; + singleControlBarParams.x = 0; + singleControlBarParams.y = 200; - controlBarView.setOnTouchListener(new View.OnTouchListener() { + singleControlBarView.setOnTouchListener(new View.OnTouchListener() { private float lastX, lastY; private float paramX, paramY; @@ -167,13 +204,13 @@ public class FloatingViewManager { case MotionEvent.ACTION_DOWN: lastX = event.getRawX(); lastY = event.getRawY(); - paramX = controlBarParams.x; - paramY = controlBarParams.y; + paramX = singleControlBarParams.x; + paramY = singleControlBarParams.y; return true; case MotionEvent.ACTION_MOVE: float dx = event.getRawX() - lastX; float dy = event.getRawY() - lastY; - updateControlBarPosition(paramX, paramY, dx, dy); + updateSingleControlBarPosition(paramX, paramY, dx, dy); return true; case MotionEvent.ACTION_UP: v.performClick(); @@ -183,84 +220,304 @@ public class FloatingViewManager { } }); - ImageView play = controlBarView.findViewById(R.id.play_button); - ImageView close = controlBarView.findViewById(R.id.close_button); - ImageView setting = controlBarView.findViewById(R.id.settings_button); - - play.setOnClickListener(v -> { - if (!isDebounced()) return; - - AutoClickService service = AutoClickService.getInstance(); - - if (service == null) { - Log.e(TAG, "AutoClickService 未初始化"); - 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(); - play.setBackgroundResource(R.drawable.play); - touchPointView.setBackgroundResource(R.drawable.un_touch_point); - Toast.makeText(context, "停止自动点击", Toast.LENGTH_SHORT).show(); - } else { - service.setClickPosition(touchPointX, touchPointY); - service.startClicking(); - play.setBackgroundResource(R.drawable.play); - touchPointView.setBackgroundResource(R.drawable.touch_point); - Toast.makeText(context, "开始自动点击", Toast.LENGTH_SHORT).show(); - } - - isClicking = !isClicking; - logDebug("服务是否点击中: " + service.isClicking()); - }); - - close.setOnClickListener(v -> { - AutoClickService service = AutoClickService.getInstance(); - - if (service != null) { - service.stopClicking(); - } else { - logDebug("AutoClickService 未初始化"); - Toast.makeText(context, "请启用无障碍服务", Toast.LENGTH_SHORT).show(); - } - - touchPointView.setBackgroundResource(R.drawable.un_touch_point); - removeFloatingViews(); - - context.stopService(new Intent(context, ForegroundService.class)); - - PrefUtils.setFloatingShown(context, false); - - Intent intent = new Intent("com.auto.autoclicker.FLOATING_WINDOW_STATE_CHANGED"); - intent.putExtra("isShown", false); - LocalBroadcastManager.getInstance(context).sendBroadcast(intent); - - }); + ImageView play = singleControlBarView.findViewById(R.id.play_button); + ImageView close = singleControlBarView.findViewById(R.id.close_button); + ImageView setting = singleControlBarView.findViewById(R.id.settings_button); + play.setOnClickListener(v -> toggleSingleClicking(play)); + close.setOnClickListener(v -> closeFloatingViews()); setting.setOnClickListener(v -> showInputDialog()); + } + @SuppressLint("ClickableViewAccessibility") + private void initMoreControlBar() { + moreControlBarView = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.more_control_bar, null); + moreControlBarParams = new WindowManager.LayoutParams( + WindowManager.LayoutParams.WRAP_CONTENT, + WindowManager.LayoutParams.WRAP_CONTENT, + Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ? + WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY : + WindowManager.LayoutParams.TYPE_PHONE, + WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | + WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL, + PixelFormat.TRANSLUCENT + ); + moreControlBarParams.gravity = Gravity.TOP | Gravity.START; + moreControlBarParams.x = 0; + moreControlBarParams.y = 200; + + moreControlBarView.setOnTouchListener(new View.OnTouchListener() { + private float lastX, lastY; + private float paramX, paramY; + + @Override + public boolean onTouch(View v, MotionEvent event) { + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + lastX = event.getRawX(); + lastY = event.getRawY(); + paramX = moreControlBarParams.x; + paramY = moreControlBarParams.y; + return true; + case MotionEvent.ACTION_MOVE: + float dx = event.getRawX() - lastX; + float dy = event.getRawY() - lastY; + updateMoreControlBarPosition(paramX, paramY, dx, dy); + return true; + case MotionEvent.ACTION_UP: + v.performClick(); + return true; + } + return false; + } + }); + + ImageView addButton = moreControlBarView.findViewById(R.id.add_button); + ImageView removeButton = moreControlBarView.findViewById(R.id.remove_button); + ImageView playButton = moreControlBarView.findViewById(R.id.play_button); + ImageView closeButton = moreControlBarView.findViewById(R.id.close_button); + ImageView settingButton = moreControlBarView.findViewById(R.id.settings_button); + + addButton.setOnClickListener(v -> addNewTouchPoint(100 + moreTouchPointViews.size() * 120, 500)); + removeButton.setOnClickListener(v -> removeLastTouchPoint()); + playButton.setOnClickListener(v -> toggleMoreClicking(playButton)); + closeButton.setOnClickListener(v -> closeFloatingViews()); + settingButton.setOnClickListener(v -> showInputDialog()); + } + + private void toggleSingleClicking(ImageView play) { + if (isDebounced()) return; + + AutoClickService service = AutoClickService.getInstance(); + if (service == null) { + Log.e(TAG, "AutoClickService 未初始化"); + 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(); + play.setBackgroundResource(R.drawable.play); + singleTouchPointView.setBackgroundResource(R.drawable.un_touch_point); + Toast.makeText(context, "停止自动点击", Toast.LENGTH_SHORT).show(); + } else { + service.setClickPosition(touchPointX + 50, touchPointY + 50); + service.startClicking(); + play.setBackgroundResource(R.drawable.pause); + singleTouchPointView.setBackgroundResource(R.drawable.touch_point); + Toast.makeText(context, "开始自动点击", Toast.LENGTH_SHORT).show(); + } + + isClicking = !isClicking; + logDebug("单点服务是否点击中: " + service.isClicking()); + } + + private void toggleMoreClicking(ImageView play) { + if (isDebounced()) return; + + AutoClickService service = AutoClickService.getInstance(); + if (service == null) { + Log.e(TAG, "AutoClickService 未初始化"); + 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(); + play.setBackgroundResource(R.drawable.play); + for (View point : moreTouchPointViews) { + point.setBackgroundResource(R.drawable.un_touch_point); + } + Toast.makeText(context, "停止多点自动点击", Toast.LENGTH_SHORT).show(); + } else { + List positions = new ArrayList<>(); + for (WindowManager.LayoutParams params : moreTouchPointParams) { + positions.add(new Point(params.x + 50, params.y + 50)); + } + service.setMultiClickPositions(positions); + service.startClicking(); + play.setBackgroundResource(R.drawable.pause); + for (View point : moreTouchPointViews) { + point.setBackgroundResource(R.drawable.touch_point); + } + Toast.makeText(context, "开始多点自动点击", Toast.LENGTH_SHORT).show(); + } + + isClicking = !isClicking; + logDebug("多点服务是否点击中: " + service.isClicking()); + } + + private void closeFloatingViews() { + AutoClickService service = AutoClickService.getInstance(); + if (service != null) { + service.stopClicking(); + } else { + logDebug("AutoClickService 未初始化"); + Toast.makeText(context, "请启用无障碍服务", Toast.LENGTH_SHORT).show(); + } + + if (singleTouchPointView != null) { + singleTouchPointView.setBackgroundResource(R.drawable.un_touch_point); + } + for (View point : moreTouchPointViews) { + point.setBackgroundResource(R.drawable.un_touch_point); + } + + removeFloatingViews(); + + context.stopService(new Intent(context, ForegroundService.class)); + PrefUtils.setFloatingShown(context, 0); + + Intent intent = new Intent("com.auto.autoclicker.FLOATING_WINDOW_STATE_CHANGED"); + intent.putExtra("isShown", false); + LocalBroadcastManager.getInstance(context).sendBroadcast(intent); + } + + public void removeFloatingViews() { + try { + // 移除单点触摸视图 + if (singleTouchPointView != null) { + try { + windowManager.removeView(singleTouchPointView); + } catch (Exception e) { + Log.w(TAG, "移除 singleTouchPointView 失败", e); + } + singleTouchPointView = null; + } + + // 移除多点触摸视图 + List pointsToRemove = new ArrayList<>(moreTouchPointViews); + for (View point : pointsToRemove) { + try { + windowManager.removeView(point); + moreTouchPointViews.remove(point); // 成功移除后从列表中删除 + } catch (Exception e) { + Log.w(TAG, "移除 multiTouchPoint 失败", e); + } + } + moreTouchPointParams.clear(); // 清空参数列表 + + // 移除单点控制栏 + if (singleControlBarView != null) { + try { + windowManager.removeView(singleControlBarView); + } catch (Exception e) { + Log.w(TAG, "移除 singleControlBarView 失败", e); + } + singleControlBarView = null; + } + + // 移除多点控制栏 + if (moreControlBarView != null) { + try { + windowManager.removeView(moreControlBarView); + } catch (Exception e) { + Log.w(TAG, "移除 moreControlBarView 失败", e); + } + moreControlBarView = null; + } + + isFloatingViewsShown = false; + isClicking = false; + logDebug("悬浮窗已移除"); + } catch (Exception e) { + Log.e(TAG, "移除悬浮窗失败", e); + isFloatingViewsShown = false; + isClicking = false; + } + } + + private void addNewTouchPoint(int x, int y) { + View point = new View(context); + point.setBackgroundResource(R.drawable.un_touch_point); + WindowManager.LayoutParams params = createTouchPointLayoutParams(x, y); + windowManager.addView(point, params); + moreTouchPointViews.add(point); + moreTouchPointParams.add(params); + + point.setOnTouchListener(new View.OnTouchListener() { + private float lastX, lastY, startX, startY; + + @Override + public boolean onTouch(View v, MotionEvent event) { + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + lastX = event.getRawX(); + lastY = event.getRawY(); + startX = params.x; + startY = params.y; + break; + case MotionEvent.ACTION_MOVE: + float dx = event.getRawX() - lastX; + float dy = event.getRawY() - lastY; + params.x = (int) (startX + dx); + params.y = (int) (startY + dy); + windowManager.updateViewLayout(v, params); + logDebug("多点触摸点移动到: (" + params.x + ", " + params.y + ")"); + break; + } + return true; + } + }); + } + + private void removeLastTouchPoint() { + if (moreTouchPointViews.size() <= 1) { + Toast.makeText(context, "至少保留一个触摸点", Toast.LENGTH_SHORT).show(); + return; + } + int lastIndex = moreTouchPointViews.size() - 1; + View last = moreTouchPointViews.remove(lastIndex); + moreTouchPointParams.remove(lastIndex); + windowManager.removeView(last); + } + + private WindowManager.LayoutParams createTouchPointLayoutParams(int x, int y) { + WindowManager.LayoutParams params = new WindowManager.LayoutParams( + 100, 100, + Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ? + WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY : + WindowManager.LayoutParams.TYPE_PHONE, + WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | + WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL, + PixelFormat.TRANSLUCENT + ); + params.gravity = Gravity.TOP | Gravity.START; + params.x = x; + params.y = y; + return params; } 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); + paramX + dx, paramY + dy, screenWidth - singleTouchPointView.getWidth(), screenHeight - singleTouchPointView.getHeight()); + singleTouchPointParams.x = constrainedPoint.x; + singleTouchPointParams.y = constrainedPoint.y; + touchPointX = singleTouchPointParams.x; + touchPointY = singleTouchPointParams.y; + windowManager.updateViewLayout(singleTouchPointView, singleTouchPointParams); } - private void updateControlBarPosition(float paramX, float paramY, float dx, float dy) { + private void updateSingleControlBarPosition(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); + paramX + dx, paramY + dy, screenWidth - singleControlBarView.getWidth(), screenHeight - singleControlBarView.getHeight()); + singleControlBarParams.x = constrainedPoint.x; + singleControlBarParams.y = constrainedPoint.y; + windowManager.updateViewLayout(singleControlBarView, singleControlBarParams); + } + + private void updateMoreControlBarPosition(float paramX, float paramY, float dx, float dy) { + Point constrainedPoint = ViewUtils.constrainToScreen( + paramX + dx, paramY + dy, screenWidth - moreControlBarView.getWidth(), screenHeight - moreControlBarView.getHeight()); + moreControlBarParams.x = constrainedPoint.x; + moreControlBarParams.y = constrainedPoint.y; + windowManager.updateViewLayout(moreControlBarView, moreControlBarParams); } public void showInputDialog() { @@ -303,7 +560,6 @@ public class FloatingViewManager { dialog.show(); - // 强制显示软键盘 input.requestFocus(); input.post(() -> { InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); @@ -314,41 +570,42 @@ public class FloatingViewManager { } public void flashTouchPoint() { - if (touchPointView == null) return; - - touchPointView.animate() - .alpha(0.3f) - .setDuration(100) - .withEndAction(() -> - touchPointView.animate() - .alpha(1.0f) - .setDuration(100) - .start() - ) - .start(); - } - - private void registerBroadcast(Context context) { - LocalBroadcastManager.getInstance(context).registerReceiver(new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if ("com.auto.autoclicker.FLASH_TOUCH_POINT".equals(intent.getAction())) { - flashTouchPoint(); - } - } - }, new IntentFilter("com.auto.autoclicker.FLASH_TOUCH_POINT")); + if (singleTouchPointView != null) { + singleTouchPointView.animate() + .alpha(0.3f) + .setDuration(100) + .withEndAction(() -> + singleTouchPointView.animate() + .alpha(1.0f) + .setDuration(100) + .start() + ) + .start(); + } + for (View point : moreTouchPointViews) { + point.animate() + .alpha(0.3f) + .setDuration(100) + .withEndAction(() -> + point.animate() + .alpha(1.0f) + .setDuration(100) + .start() + ) + .start(); + } } private boolean isDebounced() { long currentTime = System.currentTimeMillis(); if (currentTime - lastToggleTime < DEBOUNCE_INTERVAL) { - return false; + return true; } lastToggleTime = currentTime; - return true; + return false; } private void logDebug(String message) { Log.d(TAG, message); } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/auto/autoclicker/ForegroundService.java b/app/src/main/java/com/auto/autoclicker/ForegroundService.java index e7be063..e74de4b 100644 --- a/app/src/main/java/com/auto/autoclicker/ForegroundService.java +++ b/app/src/main/java/com/auto/autoclicker/ForegroundService.java @@ -1,5 +1,7 @@ package com.auto.autoclicker; +import static com.auto.autoclicker.MainActivity.FLOATING_SINGLE; + import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; @@ -33,11 +35,10 @@ 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 - ); + int type = intent != null ? intent.getIntExtra("FLOATING_TYPE", FLOATING_SINGLE) : FLOATING_SINGLE; + Intent notificationIntent = new Intent(this, MainActivity.class); + PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, PendingIntent.FLAG_IMMUTABLE); Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID) .setContentTitle("自动点击运行中") .setContentText("自动点击正在后台运行") @@ -50,21 +51,20 @@ public class ForegroundService extends Service { if (floatingViewManager != null && !isFloatingViewShown) { try { - floatingViewManager.showFloatingViews(); + floatingViewManager.showFloatingViews(type); isFloatingViewShown = true; - Log.d(TAG, "悬浮窗已显示"); } catch (SecurityException e) { Log.e(TAG, "未授予悬浮窗权限", e); stopSelf(); } } else if (floatingViewManager == null) { - Log.e(TAG, "FloatingViewManager 未初始化"); stopSelf(); } return START_STICKY; } - + + private void createNotificationChannel() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { NotificationChannel channel = new NotificationChannel( diff --git a/app/src/main/java/com/auto/autoclicker/MainActivity.java b/app/src/main/java/com/auto/autoclicker/MainActivity.java index 0723a45..cda9422 100644 --- a/app/src/main/java/com/auto/autoclicker/MainActivity.java +++ b/app/src/main/java/com/auto/autoclicker/MainActivity.java @@ -23,17 +23,27 @@ import com.auto.autoclicker.util.PrefUtils; public class MainActivity extends AppCompatActivity { private static final String TAG = "MainActivity"; + private static final String ACTION_FLOATING_STATE_CHANGED = "com.auto.autoclicker.FLOATING_WINDOW_STATE_CHANGED"; + private ActivityMainBinding binding; + + public static final int FLOATING_NONE = 0; + public static final int FLOATING_SINGLE = 1; + public static final int FLOATING_MULTI = 2; + + private Toast debounceToast; + private ActivityResultLauncher permissionLauncher; + private long lastClickTime = 0; private static final long DEBOUNCE_INTERVAL = 500; - private boolean isFloatingShown; - private static final String ACTION_FLOATING_STATE_CHANGED = "com.auto.autoclicker.FLOATING_WINDOW_STATE_CHANGED"; + + private int isFloatingShown; private final BroadcastReceiver floatingReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - isFloatingShown = intent.getBooleanExtra("isShown", false); + isFloatingShown = intent.getIntExtra("isShown", 0); updateButtonText(); } }; @@ -45,13 +55,14 @@ public class MainActivity extends AppCompatActivity { binding = ActivityMainBinding.inflate(getLayoutInflater()); setContentView(binding.getRoot()); - isFloatingShown = PrefUtils.isFloatingShown(this); + isFloatingShown = PrefUtils.getFloatingShown(this); updateButtonText(); LocalBroadcastManager.getInstance(this).registerReceiver( floatingReceiver, new IntentFilter(ACTION_FLOATING_STATE_CHANGED)); - binding.singleButton.setOnClickListener(v -> onToggleFloatingWindowClicked()); + binding.singleButton.setOnClickListener(v -> onToggleFloatingWindowClicked(FLOATING_SINGLE)); + binding.moreButton.setOnClickListener(v -> onToggleFloatingWindowClicked(FLOATING_MULTI)); permissionLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> checkPermissions()); @@ -120,24 +131,24 @@ public class MainActivity extends AppCompatActivity { } private void syncServiceState() { - isFloatingShown = isServiceRunning(); + isFloatingShown = getFloatingMode(); updateButtonText(); } - private boolean isServiceRunning() { + private int getFloatingMode() { ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE); for (ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) { if (ForegroundService.class.getName().equals(service.service.getClassName())) { - return true; + return PrefUtils.getFloatingShown(this); } } - return false; + return 0; } - public void onToggleFloatingWindowClicked() { + public void onToggleFloatingWindowClicked(int requestedType) { long currentTime = System.currentTimeMillis(); if (currentTime - lastClickTime < DEBOUNCE_INTERVAL) { - Toast.makeText(this, "点击太频繁", Toast.LENGTH_LONG).show(); + showDebounceToast(); return; } lastClickTime = currentTime; @@ -148,20 +159,28 @@ public class MainActivity extends AppCompatActivity { return; } - if (isFloatingShown) { + if (isFloatingShown != FLOATING_NONE && isFloatingShown != requestedType) { + Toast.makeText(this, "已显示其他悬浮窗,请先关闭", Toast.LENGTH_SHORT).show(); + return; + } + + if (isFloatingShown == requestedType) { stopService(new Intent(this, ForegroundService.class)); AutoClickService.getInstance().stopClicking(); - isFloatingShown = false; + isFloatingShown = FLOATING_NONE; logDebug("悬浮窗已经隐藏"); } else { Intent serviceIntent = new Intent(this, ForegroundService.class); + serviceIntent.putExtra("FLOATING_TYPE", requestedType); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { startForegroundService(serviceIntent); } else { startService(serviceIntent); } - isFloatingShown = true; - logDebug("悬浮窗已经显示"); + + isFloatingShown = requestedType; + logDebug("悬浮窗已显示: " + requestedType); } PrefUtils.setFloatingShown(this, isFloatingShown); @@ -169,14 +188,14 @@ public class MainActivity extends AppCompatActivity { updateButtonText(); } - private void broadcastFloatingState(boolean isShown) { + private void broadcastFloatingState(int isShown) { Intent intent = new Intent(ACTION_FLOATING_STATE_CHANGED); intent.putExtra("isShown", isShown); LocalBroadcastManager.getInstance(this).sendBroadcast(intent); } private void updateButtonText() { - binding.singleButton.setText(isFloatingShown ? "隐藏悬浮窗" : "显示悬浮窗"); + binding.singleButton.setText(isFloatingShown == 1 ? "隐藏悬浮窗" : "显示悬浮窗"); } @Override @@ -185,6 +204,15 @@ public class MainActivity extends AppCompatActivity { LocalBroadcastManager.getInstance(this).unregisterReceiver(floatingReceiver); } + private void showDebounceToast() { + if (debounceToast == null) { + debounceToast = Toast.makeText(this, "点击太频繁", Toast.LENGTH_SHORT); + } else { + debounceToast.setText("点击太频繁"); + } + debounceToast.show(); + } + private void logDebug(String message) { Log.d(TAG, message); } diff --git a/app/src/main/java/com/auto/autoclicker/util/PrefUtils.java b/app/src/main/java/com/auto/autoclicker/util/PrefUtils.java index 6401cb4..f261634 100644 --- a/app/src/main/java/com/auto/autoclicker/util/PrefUtils.java +++ b/app/src/main/java/com/auto/autoclicker/util/PrefUtils.java @@ -6,13 +6,13 @@ public class PrefUtils { private static final String PREF_NAME = "app_prefs"; private static final String KEY_FLOATING_SHOWN = "floating_window_shown"; - public static void setFloatingShown(Context context, boolean shown) { + public static void setFloatingShown(Context context, int shown) { context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE) - .edit().putBoolean(KEY_FLOATING_SHOWN, shown).apply(); + .edit().putInt(KEY_FLOATING_SHOWN, shown).apply(); } - public static boolean isFloatingShown(Context context) { + public static int getFloatingShown(Context context) { return context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE) - .getBoolean(KEY_FLOATING_SHOWN, false); + .getInt(KEY_FLOATING_SHOWN, 0); } } diff --git a/app/src/main/res/drawable/pause.xml b/app/src/main/res/drawable/pause.xml new file mode 100644 index 0000000..09624dd --- /dev/null +++ b/app/src/main/res/drawable/pause.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/layout/more_control_bar.xml b/app/src/main/res/layout/more_control_bar.xml index e8ced22..53ac0dc 100644 --- a/app/src/main/res/layout/more_control_bar.xml +++ b/app/src/main/res/layout/more_control_bar.xml @@ -16,14 +16,14 @@ android:src="@drawable/play" />