diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 9bc6f41..e03c3e2 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -2,6 +2,11 @@
+
+
+
+
+
+
+
+
+
+
@@ -43,6 +63,25 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/com/auto/clicker/autoclicker/data/Event.java b/app/src/main/java/com/auto/clicker/autoclicker/data/Event.java
new file mode 100644
index 0000000..2ceb22e
--- /dev/null
+++ b/app/src/main/java/com/auto/clicker/autoclicker/data/Event.java
@@ -0,0 +1,13 @@
+package com.auto.clicker.autoclicker.data;
+
+public abstract class Event {
+ protected int id;
+
+ public Event(int id) {
+ this.id = id;
+ }
+
+ public int getId() {
+ return id;
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/auto/clicker/autoclicker/data/EventWrapper.java b/app/src/main/java/com/auto/clicker/autoclicker/data/EventWrapper.java
new file mode 100644
index 0000000..93ee581
--- /dev/null
+++ b/app/src/main/java/com/auto/clicker/autoclicker/data/EventWrapper.java
@@ -0,0 +1,40 @@
+package com.auto.clicker.autoclicker.data;
+
+public class EventWrapper {
+ public enum EventType { POINT, SLIDE }
+
+ private EventType type;
+ private Event event;
+ private int order; // 添加顺序编号(用于UI编号)
+
+ public EventWrapper(EventType type, Event event, int order) {
+ this.type = type;
+ this.event = event;
+ this.order = order;
+ }
+
+
+ public EventType getType() {
+ return type;
+ }
+
+ public void setType(EventType type) {
+ this.type = type;
+ }
+
+ public Event getEvent() {
+ return event;
+ }
+
+ public void setEvent(Event event) {
+ this.event = event;
+ }
+
+ public int getOrder() {
+ return order;
+ }
+
+ public void setOrder(int order) {
+ this.order = order;
+ }
+}
diff --git a/app/src/main/java/com/auto/clicker/autoclicker/data/PointEvent.java b/app/src/main/java/com/auto/clicker/autoclicker/data/PointEvent.java
new file mode 100644
index 0000000..ade4d60
--- /dev/null
+++ b/app/src/main/java/com/auto/clicker/autoclicker/data/PointEvent.java
@@ -0,0 +1,52 @@
+package com.auto.clicker.autoclicker.data;
+
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.TextView;
+
+public class PointEvent extends Event {
+ private int x;
+ private int y;
+ private TextView view; // 悬浮窗上的控件
+ private WindowManager.LayoutParams layoutParams;
+
+ public PointEvent(int id, int x, int y, TextView view, WindowManager.LayoutParams layoutParams) {
+ super(id);
+ this.x = x;
+ this.y = y;
+ this.view = view;
+ this.layoutParams = layoutParams;
+ }
+
+ public int getX() {
+ return x;
+ }
+
+ public void setX(int x) {
+ this.x = x;
+ }
+
+ public int getY() {
+ return y;
+ }
+
+ public void setY(int y) {
+ this.y = y;
+ }
+
+ public View getView() {
+ return view;
+ }
+
+ public void setView(TextView view) {
+ this.view = view;
+ }
+
+ public WindowManager.LayoutParams getLayoutParams() {
+ return layoutParams;
+ }
+
+ public void setLayoutParams(WindowManager.LayoutParams layoutParams) {
+ this.layoutParams = layoutParams;
+ }
+}
diff --git a/app/src/main/java/com/auto/clicker/autoclicker/data/PositionUpdater.java b/app/src/main/java/com/auto/clicker/autoclicker/data/PositionUpdater.java
new file mode 100644
index 0000000..b72be4b
--- /dev/null
+++ b/app/src/main/java/com/auto/clicker/autoclicker/data/PositionUpdater.java
@@ -0,0 +1,5 @@
+package com.auto.clicker.autoclicker.data;
+
+public interface PositionUpdater {
+ void update(float paramX, float paramY, float dx, float dy);
+}
diff --git a/app/src/main/java/com/auto/clicker/autoclicker/data/SlideEvent.java b/app/src/main/java/com/auto/clicker/autoclicker/data/SlideEvent.java
new file mode 100644
index 0000000..1e9c527
--- /dev/null
+++ b/app/src/main/java/com/auto/clicker/autoclicker/data/SlideEvent.java
@@ -0,0 +1,63 @@
+package com.auto.clicker.autoclicker.data;
+
+import android.view.WindowManager;
+
+import com.auto.clicker.autoclicker.view.ConnectingLineView;
+
+
+public class SlideEvent extends Event {
+ private PointEvent startPoint;
+ private PointEvent endPoint;
+ private ConnectingLineView lineView;
+ private WindowManager.LayoutParams lineParams;
+ private long durationMillis; // 滑动时间
+
+ public SlideEvent(int id, PointEvent startPoint, PointEvent endPoint,
+ ConnectingLineView lineView, WindowManager.LayoutParams lineParams) {
+ super(id);
+ this.startPoint = startPoint;
+ this.endPoint = endPoint;
+ this.lineView = lineView;
+ this.lineParams = lineParams;
+ }
+
+ public PointEvent getStartPoint() {
+ return startPoint;
+ }
+
+ public void setStartPoint(PointEvent startPoint) {
+ this.startPoint = startPoint;
+ }
+
+ public PointEvent getEndPoint() {
+ return endPoint;
+ }
+
+ public void setEndPoint(PointEvent endPoint) {
+ this.endPoint = endPoint;
+ }
+
+ public ConnectingLineView getLineView() {
+ return lineView;
+ }
+
+ public void setLineView(ConnectingLineView lineView) {
+ this.lineView = lineView;
+ }
+
+ public WindowManager.LayoutParams getLineParams() {
+ return lineParams;
+ }
+
+ public void setLineParams(WindowManager.LayoutParams lineParams) {
+ this.lineParams = lineParams;
+ }
+
+ public long getDurationMillis() {
+ return durationMillis;
+ }
+
+ public void setDurationMillis(long durationMillis) {
+ this.durationMillis = durationMillis;
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/auto/clicker/autoclicker/presentation/ui/activity/MainActivity.java b/app/src/main/java/com/auto/clicker/autoclicker/presentation/ui/activity/MainActivity.java
deleted file mode 100644
index 9155218..0000000
--- a/app/src/main/java/com/auto/clicker/autoclicker/presentation/ui/activity/MainActivity.java
+++ /dev/null
@@ -1,55 +0,0 @@
-package com.auto.clicker.autoclicker.presentation.ui.activity;
-
-import android.content.Intent;
-import android.os.Bundle;
-
-import androidx.activity.EdgeToEdge;
-import androidx.appcompat.app.AppCompatActivity;
-import androidx.core.graphics.Insets;
-import androidx.core.view.ViewCompat;
-import androidx.core.view.WindowInsetsCompat;
-
-import com.auto.clicker.autoclicker.R;
-import com.auto.clicker.autoclicker.databinding.ActivityMainBinding;
-import com.auto.clicker.autoclicker.presentation.ui.activity.menu.MenuActivity;
-import com.auto.clicker.autoclicker.presentation.ui.activity.welcome.AgreementActivity;
-import com.auto.clicker.autoclicker.presentation.ui.activity.welcome.SplashActivity;
-
-public class MainActivity extends AppCompatActivity {
- private ActivityMainBinding binding;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- binding = ActivityMainBinding.inflate(getLayoutInflater());
- setContentView(binding.getRoot());
-
- EdgeToEdge.enable(this);
- ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
- Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
- v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
- return insets;
- });
-
- initData();
- initEvent();
- }
-
- private void initData() {
-
- }
-
- private void initEvent() {
- binding.menu.setOnClickListener(v -> {
- Intent intent = new Intent(this, MenuActivity.class);
- startActivity(intent);
- });
- }
-
- @Override
- protected void onDestroy() {
- super.onDestroy();
- binding = null;
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/auto/clicker/autoclicker/room/AppDatabase.java b/app/src/main/java/com/auto/clicker/autoclicker/room/AppDatabase.java
new file mode 100644
index 0000000..f98997f
--- /dev/null
+++ b/app/src/main/java/com/auto/clicker/autoclicker/room/AppDatabase.java
@@ -0,0 +1,30 @@
+package com.auto.clicker.autoclicker.room;
+
+import android.content.Context;
+
+import androidx.room.Database;
+import androidx.room.Room;
+import androidx.room.RoomDatabase;
+
+import com.auto.clicker.autoclicker.MyApplication;
+
+@Database(entities = {TouchEventEntity.class}, version = MyApplication.DB_VERSION, exportSchema = false)
+public abstract class AppDatabase extends RoomDatabase {
+
+ public abstract TouchEventDao touchEventDao();
+
+ private static volatile AppDatabase INSTANCE;
+
+ public static AppDatabase getInstance(Context context) {
+ if (INSTANCE == null) {
+ synchronized (AppDatabase.class) {
+ if (INSTANCE == null) {
+ INSTANCE = Room.databaseBuilder(context.getApplicationContext(),
+ AppDatabase.class, MyApplication.DB_NAME)
+ .build();
+ }
+ }
+ }
+ return INSTANCE;
+ }
+}
diff --git a/app/src/main/java/com/auto/clicker/autoclicker/room/TouchEventDao.java b/app/src/main/java/com/auto/clicker/autoclicker/room/TouchEventDao.java
new file mode 100644
index 0000000..c92221c
--- /dev/null
+++ b/app/src/main/java/com/auto/clicker/autoclicker/room/TouchEventDao.java
@@ -0,0 +1,19 @@
+package com.auto.clicker.autoclicker.room;
+
+import androidx.room.Dao;
+import androidx.room.Delete;
+import androidx.room.Insert;
+import androidx.room.Update;
+
+@Dao
+public interface TouchEventDao {
+
+ @Insert
+ long insertEvent(TouchEventEntity event);
+
+ @Delete
+ void deleteEvent(TouchEventEntity event);
+
+ @Update
+ void updateEvent(TouchEventEntity event);
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/auto/clicker/autoclicker/room/TouchEventEntity.java b/app/src/main/java/com/auto/clicker/autoclicker/room/TouchEventEntity.java
new file mode 100644
index 0000000..e13911f
--- /dev/null
+++ b/app/src/main/java/com/auto/clicker/autoclicker/room/TouchEventEntity.java
@@ -0,0 +1,116 @@
+package com.auto.clicker.autoclicker.room;
+
+import androidx.room.Entity;
+import androidx.room.PrimaryKey;
+
+@Entity(tableName = "touch_event")
+public class TouchEventEntity {
+
+ @PrimaryKey(autoGenerate = true)
+ private int id;
+ private int eventType;
+ private int orderIndex;
+
+ private int x;
+ private int y;
+
+ private Integer endX;
+ private Integer endY;
+
+ private int clickInterval;
+ private Integer slideDuration;
+
+ private int displayOrderNumber;
+
+ public TouchEventEntity(int eventType, int orderIndex, int x, int y, Integer endX, Integer endY, int clickInterval, Integer slideDuration, int displayOrderNumber) {
+ this.eventType = eventType;
+ this.orderIndex = orderIndex;
+ this.x = x;
+ this.y = y;
+ this.endX = endX;
+ this.endY = endY;
+ this.clickInterval = clickInterval;
+ this.slideDuration = slideDuration;
+ this.displayOrderNumber = displayOrderNumber;
+ }
+
+ public int getId() {
+ return id;
+ }
+
+ public void setId(int id) {
+ this.id = id;
+ }
+
+ public int getEventType() {
+ return eventType;
+ }
+
+ public void setEventType(int eventType) {
+ this.eventType = eventType;
+ }
+
+ public int getOrderIndex() {
+ return orderIndex;
+ }
+
+ public void setOrderIndex(int orderIndex) {
+ this.orderIndex = orderIndex;
+ }
+
+ public int getX() {
+ return x;
+ }
+
+ public void setX(int x) {
+ this.x = x;
+ }
+
+ public int getY() {
+ return y;
+ }
+
+ public void setY(int y) {
+ this.y = y;
+ }
+
+ public Integer getEndX() {
+ return endX;
+ }
+
+ public void setEndX(Integer endX) {
+ this.endX = endX;
+ }
+
+ public Integer getEndY() {
+ return endY;
+ }
+
+ public void setEndY(Integer endY) {
+ this.endY = endY;
+ }
+
+ public int getClickInterval() {
+ return clickInterval;
+ }
+
+ public void setClickInterval(int clickInterval) {
+ this.clickInterval = clickInterval;
+ }
+
+ public Integer getSlideDuration() {
+ return slideDuration;
+ }
+
+ public void setSlideDuration(Integer slideDuration) {
+ this.slideDuration = slideDuration;
+ }
+
+ public int getDisplayOrderNumber() {
+ return displayOrderNumber;
+ }
+
+ public void setDisplayOrderNumber(int displayOrderNumber) {
+ this.displayOrderNumber = displayOrderNumber;
+ }
+}
diff --git a/app/src/main/java/com/auto/clicker/autoclicker/room/repository/TouchEventRepository.java b/app/src/main/java/com/auto/clicker/autoclicker/room/repository/TouchEventRepository.java
new file mode 100644
index 0000000..03996d1
--- /dev/null
+++ b/app/src/main/java/com/auto/clicker/autoclicker/room/repository/TouchEventRepository.java
@@ -0,0 +1,34 @@
+package com.auto.clicker.autoclicker.room.repository;
+
+import android.app.Application;
+
+import com.auto.clicker.autoclicker.room.AppDatabase;
+import com.auto.clicker.autoclicker.room.TouchEventDao;
+import com.auto.clicker.autoclicker.room.TouchEventEntity;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+public class TouchEventRepository {
+ private final TouchEventDao dao;
+ private final ExecutorService executorService;
+
+ public TouchEventRepository(Application application) {
+ AppDatabase db = AppDatabase.getInstance(application);
+ this.dao = db.touchEventDao();
+ this.executorService = Executors.newSingleThreadExecutor();
+ }
+
+ public void insertEvent(TouchEventEntity touchEventEntity){
+ executorService.execute(() -> dao.insertEvent(touchEventEntity));
+ }
+
+ public void deleteEvent(TouchEventEntity touchEventEntity){
+ executorService.execute(() -> dao.deleteEvent(touchEventEntity));
+ }
+
+ public void updateEvent(TouchEventEntity touchEventEntity){
+ executorService.execute(() -> dao.updateEvent(touchEventEntity));
+ }
+
+}
diff --git a/app/src/main/java/com/auto/clicker/autoclicker/room/viewmodel/TouchEventViewModel.java b/app/src/main/java/com/auto/clicker/autoclicker/room/viewmodel/TouchEventViewModel.java
new file mode 100644
index 0000000..b715061
--- /dev/null
+++ b/app/src/main/java/com/auto/clicker/autoclicker/room/viewmodel/TouchEventViewModel.java
@@ -0,0 +1,18 @@
+package com.auto.clicker.autoclicker.room.viewmodel;
+
+import android.app.Application;
+
+import androidx.annotation.NonNull;
+import androidx.lifecycle.AndroidViewModel;
+
+import com.auto.clicker.autoclicker.room.repository.TouchEventRepository;
+
+public class TouchEventViewModel extends AndroidViewModel {
+ private final TouchEventRepository repository;
+
+ public TouchEventViewModel(@NonNull Application application) {
+ super(application);
+ repository = new TouchEventRepository(application);
+ }
+
+}
diff --git a/app/src/main/java/com/auto/clicker/autoclicker/service/AutoClickService.java b/app/src/main/java/com/auto/clicker/autoclicker/service/AutoClickService.java
new file mode 100644
index 0000000..409df0e
--- /dev/null
+++ b/app/src/main/java/com/auto/clicker/autoclicker/service/AutoClickService.java
@@ -0,0 +1,264 @@
+package com.auto.clicker.autoclicker.service;
+
+import android.accessibilityservice.AccessibilityService;
+import android.accessibilityservice.GestureDescription;
+import android.content.Intent;
+import android.graphics.Path;
+import android.graphics.Point;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Log;
+import android.view.accessibility.AccessibilityEvent;
+
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+
+import com.auto.clicker.autoclicker.data.Event;
+import com.auto.clicker.autoclicker.data.EventWrapper;
+import com.auto.clicker.autoclicker.data.PointEvent;
+import com.auto.clicker.autoclicker.data.SlideEvent;
+import com.auto.clicker.autoclicker.util.ScreenUtils;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+
+public class AutoClickService extends AccessibilityService {
+ private static final String TAG = "AutoClickService";
+ private static AutoClickService instance;
+
+ private static final long MIN_CLICK_INTERVAL = 40;
+ 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 static final int MIN_SLIDE_DURATION = 100;
+ private static final int MAX_SLIDE_DURATION = 2000;
+
+ private List runtimeEvents = new ArrayList<>();
+
+ private final Handler handler = new Handler(Looper.getMainLooper());
+ private boolean isRunning = false;
+
+ private long clickInterval = 1000;
+ private int clickDuration = 200;
+ private int slideDuration = 500;
+ private int currentEventIndex = 0;
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ instance = this;
+ Point screenSize = ScreenUtils.getScreenSize(this);
+ int screenWidth = screenSize.x;
+ int screenHeight = screenSize.y;
+ logDebug("屏幕尺寸: " + screenWidth + "x" + screenHeight);
+ }
+
+ public static AutoClickService getInstance() {
+ return instance;
+ }
+
+ @Override
+ public void onServiceConnected() {
+ super.onServiceConnected();
+ logDebug("无障碍服务已连接");
+ }
+
+ @Override
+ public void onInterrupt() {
+ stopClicking();
+ logDebug("无障碍服务中断");
+ }
+
+ public void clearEvents() {
+ runtimeEvents.clear();
+ logDebug("清除所有事件");
+ }
+
+ public void addMultipleClickEvents(List eventWrapperList) {
+ clearEvents();
+
+ runtimeEvents = new ArrayList<>(eventWrapperList);
+ logDebug("设置多点点击事件: " + eventWrapperList.size() + " 个点");
+ }
+
+ public void setClickInterval(long interval) {
+ 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 < MIN_CLICK_DURATION || duration > MAX_CLICK_DURATION) {
+ Log.w(TAG, "点击时长超出范围: " + duration + "ms");
+ return;
+ }
+ this.clickDuration = duration;
+ logDebug("设置点击时长: " + duration + "ms");
+ }
+
+ public void setSlideDuration(int duration) {
+ if (duration < MIN_SLIDE_DURATION || duration > MAX_SLIDE_DURATION) {
+ Log.w(TAG, "滑动时长超出范围: " + duration + "ms");
+ return;
+ }
+ this.slideDuration = duration;
+ logDebug("设置滑动时长: " + duration + "ms");
+ }
+
+ public void startClicking() {
+ if (!isRunning) {
+ if (runtimeEvents.isEmpty()) {
+ Log.w(TAG, "无事件队列,忽略开始");
+ return;
+ }
+
+ isRunning = true;
+ currentEventIndex = 0;
+ logDebug("开始执行事件队列 - 共 " + runtimeEvents.size() + " 个事件");
+ executeEventsByType();
+ }
+ }
+
+ public void stopClicking() {
+ if (isRunning) {
+ isRunning = false;
+ handler.removeCallbacksAndMessages(null);
+ logDebug("停止执行事件");
+ }
+ }
+
+ public boolean isClicking() {
+ return isRunning;
+ }
+
+ private void executeEventsByType() {
+ if (!isRunning || runtimeEvents.isEmpty()) {
+ logDebug("跳过执行:服务未运行或事件队列为空");
+ return;
+ }
+
+ runtimeEvents.sort(Comparator.comparingInt(EventWrapper::getOrder));
+
+ if (currentEventIndex >= runtimeEvents.size()) {
+ currentEventIndex = 0;
+ }
+
+ EventWrapper wrapper = runtimeEvents.get(currentEventIndex);
+ Event event = wrapper.getEvent();
+
+ if (wrapper.getType() == EventWrapper.EventType.POINT && event instanceof PointEvent) {
+ performSingleClick((PointEvent) event);
+ } else if (wrapper.getType() == EventWrapper.EventType.SLIDE && event instanceof SlideEvent) {
+ performSlide((SlideEvent) event);
+ } else {
+ logDebug("未知或不匹配的事件类型,跳过执行: " + wrapper.getType());
+ currentEventIndex++;
+ handler.postDelayed(this::executeEventsByType, clickInterval);
+ }
+ }
+
+ private void performSingleClick(PointEvent pointEvent) {
+ int x = pointEvent.getX();
+ int y = pointEvent.getY();
+
+ logDebug("执行点击: (" + x + ", " + y + ")");
+
+ Path path = new Path();
+ path.moveTo(x, y);
+ GestureDescription.StrokeDescription stroke =
+ new GestureDescription.StrokeDescription(path, 0, clickDuration);
+ GestureDescription gesture =
+ new GestureDescription.Builder().addStroke(stroke).build();
+
+ dispatchGesture(gesture, new GestureResultCallback() {
+ @Override
+ public void onCompleted(GestureDescription gestureDescription) {
+ logDebug("点击完成");
+ if (isRunning) {
+ int feedbackIndex = currentEventIndex;
+ currentEventIndex++;
+ handler.postDelayed(() -> executeEventsByType(), clickInterval);
+ flashTouchFeedback(feedbackIndex);
+ }
+ }
+
+ @Override
+ public void onCancelled(GestureDescription gestureDescription) {
+ Log.e(TAG, "点击被取消");
+ if (isRunning) {
+ currentEventIndex++;
+ handler.postDelayed(() -> executeEventsByType(), clickInterval + 300);
+ }
+ }
+ }, null);
+ }
+
+ private void performSlide(SlideEvent slideEvent) {
+ PointEvent start = slideEvent.getStartPoint();
+ PointEvent end = slideEvent.getEndPoint();
+ int startX = start.getX();
+ int startY = start.getY();
+ int endX = end.getX();
+ int endY = end.getY();
+
+ logDebug("执行滑动: 从 (" + startX + ", " + startY + ") 到 (" + endX + ", " + endY + ")");
+
+ Path path = new Path();
+ path.moveTo(startX, startY);
+ path.lineTo(endX, endY);
+ GestureDescription.StrokeDescription stroke =
+ new GestureDescription.StrokeDescription(path, 0, slideDuration);
+ GestureDescription gesture =
+ new GestureDescription.Builder().addStroke(stroke).build();
+
+ dispatchGesture(gesture, new GestureResultCallback() {
+ @Override
+ public void onCompleted(GestureDescription gestureDescription) {
+ logDebug("滑动完成");
+ if (isRunning) {
+ int feedbackIndex = currentEventIndex;
+ currentEventIndex++;
+ handler.postDelayed(() -> executeEventsByType(), clickInterval);
+ flashTouchFeedback(feedbackIndex);
+ }
+ }
+
+ @Override
+ public void onCancelled(GestureDescription gestureDescription) {
+ Log.e(TAG, "滑动被取消");
+ if (isRunning) {
+ currentEventIndex++;
+ handler.postDelayed(() -> executeEventsByType(), clickInterval + 300);
+ }
+ }
+ }, null);
+ }
+
+ private void flashTouchFeedback(int index) {
+ Intent intent = new Intent("com.auto.autoclicker.FLASH_TOUCH_POINT");
+ intent.putExtra("index", index);
+ LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
+ }
+
+ @Override
+ public void onAccessibilityEvent(AccessibilityEvent event) {
+ }
+
+ 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/clicker/autoclicker/service/ForegroundService.java b/app/src/main/java/com/auto/clicker/autoclicker/service/ForegroundService.java
new file mode 100644
index 0000000..b1d88dc
--- /dev/null
+++ b/app/src/main/java/com/auto/clicker/autoclicker/service/ForegroundService.java
@@ -0,0 +1,101 @@
+package com.auto.clicker.autoclicker.service;
+
+import static com.auto.clicker.autoclicker.ui.activity.main.MainActivity.FLOATING_SINGLE;
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.Intent;
+import android.os.Build;
+import android.os.IBinder;
+import android.util.Log;
+
+import androidx.core.app.NotificationCompat;
+
+import com.auto.clicker.autoclicker.R;
+import com.auto.clicker.autoclicker.ui.activity.main.MainActivity;
+import com.auto.clicker.autoclicker.view.FloatingViewManager;
+
+public class ForegroundService extends Service {
+ private static final String TAG = "ForegroundService";
+ private static final String CHANNEL_ID = "AutoClickerChannel";
+ private static final int NOTIFICATION_ID = 1;
+ private FloatingViewManager floatingViewManager;
+ private boolean isFloatingViewShown = false;
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ createNotificationChannel();
+ try {
+ floatingViewManager = new FloatingViewManager(this);
+ } catch (Exception e) {
+ Log.e(TAG, "初始化 FloatingViewManager 失败", e);
+ stopSelf();
+ }
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ 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("自动点击正在后台运行")
+ .setSmallIcon(R.drawable.notification)
+ .setContentIntent(pendingIntent)
+ .setOngoing(true)
+ .build();
+
+ startForeground(NOTIFICATION_ID, notification);
+
+ if (floatingViewManager != null && !isFloatingViewShown) {
+ try {
+ floatingViewManager.showFloatingViews(type);
+ isFloatingViewShown = true;
+ } catch (SecurityException e) {
+ Log.e(TAG, "未授予悬浮窗权限", e);
+ stopSelf();
+ }
+ } else if (floatingViewManager == null) {
+ stopSelf();
+ }
+
+ return START_STICKY;
+ }
+
+ private void createNotificationChannel() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ NotificationChannel channel = new NotificationChannel(
+ CHANNEL_ID,
+ "自动点击服务",
+ NotificationManager.IMPORTANCE_LOW
+ );
+ channel.setDescription("自动点击前台服务通知渠道");
+ NotificationManager manager = getSystemService(NotificationManager.class);
+ if (manager != null) {
+ manager.createNotificationChannel(channel);
+ }
+ }
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ if (floatingViewManager != null) {
+ floatingViewManager.removeFloatingViews();
+ isFloatingViewShown = false;
+ Log.d(TAG, "悬浮窗已移除");
+ }
+ stopForeground(true);
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/auto/clicker/autoclicker/presentation/ui/activity/AccessibilityActivity.java b/app/src/main/java/com/auto/clicker/autoclicker/ui/activity/main/AccessibilityActivity.java
similarity index 93%
rename from app/src/main/java/com/auto/clicker/autoclicker/presentation/ui/activity/AccessibilityActivity.java
rename to app/src/main/java/com/auto/clicker/autoclicker/ui/activity/main/AccessibilityActivity.java
index c690453..4499aa1 100644
--- a/app/src/main/java/com/auto/clicker/autoclicker/presentation/ui/activity/AccessibilityActivity.java
+++ b/app/src/main/java/com/auto/clicker/autoclicker/ui/activity/main/AccessibilityActivity.java
@@ -1,4 +1,4 @@
-package com.auto.clicker.autoclicker.presentation.ui.activity;
+package com.auto.clicker.autoclicker.ui.activity.main;
import android.os.Bundle;
diff --git a/app/src/main/java/com/auto/clicker/autoclicker/ui/activity/main/MainActivity.java b/app/src/main/java/com/auto/clicker/autoclicker/ui/activity/main/MainActivity.java
new file mode 100644
index 0000000..7831edf
--- /dev/null
+++ b/app/src/main/java/com/auto/clicker/autoclicker/ui/activity/main/MainActivity.java
@@ -0,0 +1,390 @@
+package com.auto.clicker.autoclicker.ui.activity.main;
+
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.app.ActivityManager;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.PowerManager;
+import android.provider.Settings;
+import android.util.Log;
+import android.view.accessibility.AccessibilityManager;
+import android.widget.Toast;
+import android.widget.VideoView;
+
+import androidx.activity.EdgeToEdge;
+import androidx.activity.result.ActivityResultLauncher;
+import androidx.activity.result.contract.ActivityResultContracts;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.core.graphics.Insets;
+import androidx.core.view.ViewCompat;
+import androidx.core.view.WindowInsetsCompat;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import com.auto.clicker.autoclicker.R;
+import com.auto.clicker.autoclicker.databinding.ActivityMainBinding;
+import com.auto.clicker.autoclicker.service.AutoClickService;
+import com.auto.clicker.autoclicker.service.ForegroundService;
+import com.auto.clicker.autoclicker.ui.activity.menu.MenuActivity;
+import com.auto.clicker.autoclicker.ui.activity.setting.SettingActivity;
+import com.auto.clicker.autoclicker.util.PrefUtils;
+
+import java.util.List;
+
+public class MainActivity extends AppCompatActivity {
+ private static final String TAG = "MainActivity";
+ private static final String ACTION_FLOATING_STATE_CHANGED = "com.auto.clicker.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 int isFloatingShown;
+ private int selectedFloatingMode = FLOATING_SINGLE;
+
+ private final BroadcastReceiver floatingReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ isFloatingShown = intent.getIntExtra("isShown", FLOATING_NONE);
+ logDebug("接收到浮窗状态广播: isShown = " + isFloatingShown);
+
+ if (isFloatingShown == FLOATING_NONE) {
+ } else {
+ selectedFloatingMode = isFloatingShown;
+ }
+ updateSelectionButtons();
+ }
+ };
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ binding = ActivityMainBinding.inflate(getLayoutInflater());
+ setContentView(binding.getRoot());
+
+ EdgeToEdge.enable(this);
+ ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
+ Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
+ v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
+ return insets;
+ });
+
+ initData();
+ initEvent();
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ logDebug("onResume: 检查权限和同步服务状态");
+ checkPermissions();
+ syncServiceState();
+ }
+
+ private void initData() {
+ isFloatingShown = PrefUtils.getFloatingShown(this);
+ if (isFloatingShown != FLOATING_NONE) {
+ selectedFloatingMode = isFloatingShown;
+ } else {
+ selectedFloatingMode = FLOATING_SINGLE;
+ }
+ logDebug("initData: 初始化 isFloatingShown=" + isFloatingShown + ", selectedFloatingMode=" + selectedFloatingMode);
+
+ updateSelectionButtons();
+
+ LocalBroadcastManager.getInstance(this).registerReceiver(
+ floatingReceiver, new IntentFilter(ACTION_FLOATING_STATE_CHANGED));
+
+ permissionLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
+ logDebug("权限回调结果: " + result.getResultCode());
+ binding.getRoot().postDelayed(() -> {
+ checkPermissions();
+ syncServiceState();
+ }, 300);
+ });
+
+ setVideo(Uri.parse("android.resource://" + getPackageName() + "/" + R.raw.bolangblue));
+ }
+
+ private void initEvent() {
+ binding.menu.setOnClickListener(v -> {
+ Intent intent = new Intent(this, MenuActivity.class);
+ startActivity(intent);
+ });
+
+ binding.question.setOnClickListener(v -> {
+ Intent intent = new Intent(this, QuestionActivity.class);
+ startActivity(intent);
+ });
+
+ binding.setting.setOnClickListener(v -> {
+ Intent intent = new Intent(this, SettingActivity.class);
+ startActivity(intent);
+ });
+
+ binding.animatedView.setOnClickListener(v -> onToggleFloatingWindowClicked());
+
+ binding.single.setOnClickListener(v -> {
+ selectedFloatingMode = FLOATING_SINGLE;
+ updateSelectionButtons();
+
+ logDebug("选择单点模式. selectedFloatingMode=" + selectedFloatingMode);
+ });
+
+ binding.multi.setOnClickListener(v -> {
+ selectedFloatingMode = FLOATING_MULTI;
+ updateSelectionButtons();
+
+ logDebug("选择多点模式. selectedFloatingMode=" + selectedFloatingMode);
+ });
+ }
+
+ private void checkPermissions() {
+ boolean accessibilityGranted = isAccessibilityServiceEnabled();
+ boolean overlayGranted = Settings.canDrawOverlays(this);
+ boolean batteryOptimizationIgnored = isIgnoringBatteryOptimizations();
+
+ logDebug("检查权限: 无障碍服务=" + accessibilityGranted + ", 悬浮窗=" + overlayGranted + ", 电池优化=" + batteryOptimizationIgnored);
+
+ if (!accessibilityGranted) {
+ logDebug("无障碍服务未启用,请求权限");
+ showPermissionRequest(Settings.ACTION_ACCESSIBILITY_SETTINGS, "请启用无障碍服务以便应用正常工作", "无法打开无障碍设置");
+ setStartButtonEnabled(false);
+
+ return;
+ }
+
+ if (!overlayGranted) {
+ logDebug("悬浮窗权限未授予,请求权限");
+ Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getPackageName()));
+ showPermissionRequest(intent, "请授予悬浮窗权限,否则无法显示浮窗", "无法打开悬浮窗设置");
+ setStartButtonEnabled(false);
+
+ return;
+ }
+
+ if (!batteryOptimizationIgnored) {
+ logDebug("电池优化未忽略,请求权限");
+ Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
+ intent.setData(Uri.parse("package:" + getPackageName()));
+ showPermissionRequest(intent, "请禁用电池优化,以确保应用在后台稳定运行", "无法打开电池优化设置");
+ setStartButtonEnabled(false);
+
+ return;
+ }
+
+ logDebug("所有权限均已授予");
+ setStartButtonEnabled(true);
+
+ }
+
+ private void showPermissionRequest(String action, String toastMessage, String errorMessage) {
+ try {
+ Intent intent = new Intent(action);
+ permissionLauncher.launch(intent);
+ Toast.makeText(this, toastMessage, Toast.LENGTH_LONG).show();
+ logDebug("尝试启动权限设置页面: " + action);
+ } catch (Exception e) {
+ Toast.makeText(this, errorMessage, Toast.LENGTH_LONG).show();
+ logDebug("打开设置页面失败: " + errorMessage + ", 异常: " + e.getMessage());
+ }
+ }
+
+
+ private void showPermissionRequest(Intent intent, String toastMessage, String errorMessage) {
+ try {
+ permissionLauncher.launch(intent);
+ Toast.makeText(this, toastMessage, Toast.LENGTH_LONG).show();
+ logDebug("尝试启动权限设置页面 (带URI): " + intent.getAction());
+ } catch (Exception e) {
+ Toast.makeText(this, errorMessage, Toast.LENGTH_LONG).show();
+ logDebug("打开设置页面失败 (带URI): " + errorMessage + ", 异常: " + e.getMessage());
+ }
+ }
+
+ private boolean isAccessibilityServiceEnabled() {
+ AccessibilityManager am = (AccessibilityManager) getSystemService(Context.ACCESSIBILITY_SERVICE);
+ if (am == null) return false;
+
+ List enabledServices =
+ am.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_ALL_MASK);
+
+ String expectedId = new ComponentName(this, AutoClickService.class).flattenToShortString();
+
+ for (AccessibilityServiceInfo enabledService : enabledServices) {
+ if (enabledService.getId().equals(expectedId)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean isIgnoringBatteryOptimizations() {
+ PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
+ boolean ignored = pm != null && pm.isIgnoringBatteryOptimizations(getPackageName());
+ logDebug("是否忽略电池优化: " + ignored);
+ return ignored;
+ }
+
+ private void syncServiceState() {
+ int currentFloatingMode = getFloatingMode();
+ logDebug("同步服务状态: 当前浮窗模式 = " + currentFloatingMode);
+ isFloatingShown = currentFloatingMode;
+ PrefUtils.setFloatingShown(this, isFloatingShown);
+
+ updateSelectionButtons();
+ }
+
+ private int getFloatingMode() {
+ ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
+ if (manager == null) {
+ logDebug("ActivityManager 为空");
+ return FLOATING_NONE;
+ }
+
+ List runningServices = manager.getRunningServices(Integer.MAX_VALUE);
+ for (ActivityManager.RunningServiceInfo service : runningServices) {
+ if (ForegroundService.class.getName().equals(service.service.getClassName())) {
+
+ int mode = PrefUtils.getFloatingShown(this);
+ logDebug("前台服务正在运行,PrefUtils 获取浮窗模式: " + mode);
+ return mode;
+ }
+ }
+ logDebug("前台服务未运行");
+ return FLOATING_NONE;
+ }
+
+
+ public void onToggleFloatingWindowClicked() {
+ long currentTime = System.currentTimeMillis();
+ if (currentTime - lastClickTime < DEBOUNCE_INTERVAL) {
+ showDebounceToast();
+ logDebug("点击过于频繁");
+ return;
+ }
+ lastClickTime = currentTime;
+ logDebug("点击启动/隐藏浮窗按钮. 当前 isFloatingShown=" + isFloatingShown + ", selectedFloatingMode=" + selectedFloatingMode);
+
+ if (!isAccessibilityServiceEnabled() || !Settings.canDrawOverlays(this) || !isIgnoringBatteryOptimizations()) {
+ Toast.makeText(this, "请授予所有必要权限", Toast.LENGTH_LONG).show();
+ checkPermissions();
+ logDebug("权限不足,无法启动浮窗,重新检查权限");
+ return;
+ }
+
+ if (isFloatingShown != FLOATING_NONE && isFloatingShown != selectedFloatingMode) {
+ Toast.makeText(this, "已显示其他悬浮窗,请先关闭", Toast.LENGTH_SHORT).show();
+ logDebug("已显示其他模式的浮窗,请先关闭");
+ return;
+ }
+
+ if (isFloatingShown == selectedFloatingMode) {
+
+ logDebug("隐藏当前模式浮窗: " + selectedFloatingMode);
+ stopService(new Intent(this, ForegroundService.class));
+
+ if (AutoClickService.getInstance() != null) {
+ AutoClickService.getInstance().stopClicking();
+ logDebug("已停止 AutoClickService 点击");
+ }
+ isFloatingShown = FLOATING_NONE;
+ logDebug("悬浮窗已经隐藏");
+ } else {
+
+ logDebug("启动浮窗: " + selectedFloatingMode);
+ Intent serviceIntent = new Intent(this, ForegroundService.class);
+ serviceIntent.putExtra("FLOATING_TYPE", selectedFloatingMode);
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ startForegroundService(serviceIntent);
+ } else {
+ startService(serviceIntent);
+ }
+
+ isFloatingShown = selectedFloatingMode;
+ logDebug("悬浮窗已显示: " + selectedFloatingMode);
+ }
+
+ PrefUtils.setFloatingShown(this, isFloatingShown);
+ broadcastFloatingState(isFloatingShown);
+ updateSelectionButtons();
+
+ if (isFloatingShown == FLOATING_NONE) {
+ setVideo(Uri.parse("android.resource://" + getPackageName() + "/" + R.raw.bolangblue));
+ } else {
+ setVideo(Uri.parse("android.resource://" + getPackageName() + "/" + R.raw.bolangred));
+ }
+ }
+
+ private void broadcastFloatingState(int isShown) {
+ Intent intent = new Intent(ACTION_FLOATING_STATE_CHANGED);
+ intent.putExtra("isShown", isShown);
+ LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
+ logDebug("发送浮窗状态广播: isShown = " + isShown);
+ }
+
+ private void updateSelectionButtons() {
+ logDebug("更新选择按钮UI: selectedFloatingMode=" + selectedFloatingMode);
+ if (selectedFloatingMode == FLOATING_SINGLE) {
+ binding.single.setSelected(true);
+ binding.multi.setSelected(false);
+ } else if (selectedFloatingMode == FLOATING_MULTI) {
+ binding.single.setSelected(false);
+ binding.multi.setSelected(true);
+ }
+ }
+
+ private void setStartButtonEnabled(boolean enabled) {
+ logDebug("设置启动按钮启用状态: " + enabled);
+ }
+
+ private void setVideo(Uri uri) {
+ binding.animatedVideo.setVideoURI(uri);
+ binding.animatedVideo.setOnPreparedListener(mp -> {
+ mp.setLooping(true);
+ mp.start();
+ });
+ binding.animatedVideo.start();
+ }
+
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ binding = null;
+ LocalBroadcastManager.getInstance(this).unregisterReceiver(floatingReceiver);
+ if (debounceToast != null) {
+ debounceToast.cancel();
+ }
+ logDebug("MainActivity onDestroy");
+ }
+
+ 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);
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/auto/clicker/autoclicker/presentation/ui/activity/setting/SettingActivity.java b/app/src/main/java/com/auto/clicker/autoclicker/ui/activity/main/QuestionActivity.java
similarity index 80%
rename from app/src/main/java/com/auto/clicker/autoclicker/presentation/ui/activity/setting/SettingActivity.java
rename to app/src/main/java/com/auto/clicker/autoclicker/ui/activity/main/QuestionActivity.java
index a487474..92a63fc 100644
--- a/app/src/main/java/com/auto/clicker/autoclicker/presentation/ui/activity/setting/SettingActivity.java
+++ b/app/src/main/java/com/auto/clicker/autoclicker/ui/activity/main/QuestionActivity.java
@@ -1,4 +1,4 @@
-package com.auto.clicker.autoclicker.presentation.ui.activity.setting;
+package com.auto.clicker.autoclicker.ui.activity.main;
import android.os.Bundle;
@@ -10,13 +10,13 @@ import androidx.core.view.WindowInsetsCompat;
import com.auto.clicker.autoclicker.R;
-public class SettingActivity extends AppCompatActivity {
+public class QuestionActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
- setContentView(R.layout.activity_setting);
+ setContentView(R.layout.activity_question);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
diff --git a/app/src/main/java/com/auto/clicker/autoclicker/ui/activity/menu/BatteryLimitFreeActivity.java b/app/src/main/java/com/auto/clicker/autoclicker/ui/activity/menu/BatteryLimitFreeActivity.java
new file mode 100644
index 0000000..8364da9
--- /dev/null
+++ b/app/src/main/java/com/auto/clicker/autoclicker/ui/activity/menu/BatteryLimitFreeActivity.java
@@ -0,0 +1,26 @@
+package com.auto.clicker.autoclicker.ui.activity.menu;
+
+import android.os.Bundle;
+
+import androidx.activity.EdgeToEdge;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.core.graphics.Insets;
+import androidx.core.view.ViewCompat;
+import androidx.core.view.WindowInsetsCompat;
+
+import com.auto.clicker.autoclicker.R;
+
+public class BatteryLimitFreeActivity extends AppCompatActivity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ EdgeToEdge.enable(this);
+ setContentView(R.layout.activity_battery_limit_free);
+ ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
+ Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
+ v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
+ return insets;
+ });
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/auto/clicker/autoclicker/ui/activity/menu/FeedbackActivity.java b/app/src/main/java/com/auto/clicker/autoclicker/ui/activity/menu/FeedbackActivity.java
new file mode 100644
index 0000000..cf5f863
--- /dev/null
+++ b/app/src/main/java/com/auto/clicker/autoclicker/ui/activity/menu/FeedbackActivity.java
@@ -0,0 +1,26 @@
+package com.auto.clicker.autoclicker.ui.activity.menu;
+
+import android.os.Bundle;
+
+import androidx.activity.EdgeToEdge;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.core.graphics.Insets;
+import androidx.core.view.ViewCompat;
+import androidx.core.view.WindowInsetsCompat;
+
+import com.auto.clicker.autoclicker.R;
+
+public class FeedbackActivity extends AppCompatActivity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ EdgeToEdge.enable(this);
+ setContentView(R.layout.activity_feedback);
+ ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
+ Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
+ v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
+ return insets;
+ });
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/auto/clicker/autoclicker/ui/activity/menu/HowToUseActivity.java b/app/src/main/java/com/auto/clicker/autoclicker/ui/activity/menu/HowToUseActivity.java
new file mode 100644
index 0000000..b38480b
--- /dev/null
+++ b/app/src/main/java/com/auto/clicker/autoclicker/ui/activity/menu/HowToUseActivity.java
@@ -0,0 +1,67 @@
+package com.auto.clicker.autoclicker.ui.activity.menu;
+
+import android.os.Bundle;
+import android.view.LayoutInflater;
+
+import androidx.activity.EdgeToEdge;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.core.graphics.Insets;
+import androidx.core.view.ViewCompat;
+import androidx.core.view.WindowInsetsCompat;
+
+import com.auto.clicker.autoclicker.R;
+import com.auto.clicker.autoclicker.databinding.ActivityHowToUseBinding;
+import com.auto.clicker.autoclicker.databinding.HowTabBinding;
+import com.auto.clicker.autoclicker.ui.adapter.menu.HowAdapter;
+import com.google.android.material.tabs.TabLayoutMediator;
+
+public class HowToUseActivity extends AppCompatActivity {
+ private ActivityHowToUseBinding binding;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ EdgeToEdge.enable(this);
+ binding = ActivityHowToUseBinding.inflate(getLayoutInflater());
+ setContentView(binding.getRoot());
+ ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
+ Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
+ v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
+ return insets;
+ });
+
+ initPagerAdapter();
+ initTabs();
+
+ binding.howViewpager.setCurrentItem(0, false);
+ }
+
+ private void initPagerAdapter() {
+ HowAdapter adapter = new HowAdapter(this);
+ binding.howViewpager.setAdapter(adapter);
+ }
+
+ private void initTabs() {
+ new TabLayoutMediator(binding.howTablayout, binding.howViewpager, (tab, idx) -> {
+ com.auto.clicker.autoclicker.databinding.HowTabBinding tabViewBinding = HowTabBinding.inflate(LayoutInflater.from(this));
+ tab.setCustomView(tabViewBinding.getRoot());
+ decorateTab(tabViewBinding, idx);
+ }).attach();
+ }
+
+ private void decorateTab(HowTabBinding tabBinding, int position) {
+ String text = "";
+ if (position == 0) {
+ text = "Single-Point";
+ } else if (position == 1) {
+ text = "Multi-Point";
+ }
+ tabBinding.textCustom.setText(text);
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ binding = null;
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/auto/clicker/autoclicker/presentation/ui/activity/menu/MenuActivity.java b/app/src/main/java/com/auto/clicker/autoclicker/ui/activity/menu/MenuActivity.java
similarity index 76%
rename from app/src/main/java/com/auto/clicker/autoclicker/presentation/ui/activity/menu/MenuActivity.java
rename to app/src/main/java/com/auto/clicker/autoclicker/ui/activity/menu/MenuActivity.java
index 10028f0..b92294f 100644
--- a/app/src/main/java/com/auto/clicker/autoclicker/presentation/ui/activity/menu/MenuActivity.java
+++ b/app/src/main/java/com/auto/clicker/autoclicker/ui/activity/menu/MenuActivity.java
@@ -1,5 +1,6 @@
-package com.auto.clicker.autoclicker.presentation.ui.activity.menu;
+package com.auto.clicker.autoclicker.ui.activity.menu;
+import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.ImageView;
@@ -13,7 +14,6 @@ import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import com.auto.clicker.autoclicker.R;
-import com.auto.clicker.autoclicker.databinding.ActivityMainBinding;
import com.auto.clicker.autoclicker.databinding.ActivityMenuBinding;
public class MenuActivity extends AppCompatActivity {
@@ -49,6 +49,26 @@ public class MenuActivity extends AppCompatActivity {
private void initEvent() {
binding.btnBack.setOnClickListener(v -> finish());
+
+ binding.scripts.arrow.setOnClickListener(v -> {
+ Intent intent = new Intent(this, ScriptsActivity.class);
+ startActivity(intent);
+ });
+
+ binding.trouble.arrow.setOnClickListener(v -> {
+ Intent intent = new Intent(this, TroubleshootingActivity.class);
+ startActivity(intent);
+ });
+
+ binding.battery.arrow.setOnClickListener(v -> {
+ Intent intent = new Intent(this, BatteryLimitFreeActivity.class);
+ startActivity(intent);
+ });
+
+ binding.question.arrow.setOnClickListener(v -> {
+ Intent intent = new Intent(this, HowToUseActivity.class);
+ startActivity(intent);
+ });
}
private void setupItem(int viewId, int iconRes, String title, String subtitle) {
diff --git a/app/src/main/java/com/auto/clicker/autoclicker/presentation/ui/activity/menu/ScriptsActivity.java b/app/src/main/java/com/auto/clicker/autoclicker/ui/activity/menu/ScriptsActivity.java
similarity index 92%
rename from app/src/main/java/com/auto/clicker/autoclicker/presentation/ui/activity/menu/ScriptsActivity.java
rename to app/src/main/java/com/auto/clicker/autoclicker/ui/activity/menu/ScriptsActivity.java
index 9ad3262..17fc777 100644
--- a/app/src/main/java/com/auto/clicker/autoclicker/presentation/ui/activity/menu/ScriptsActivity.java
+++ b/app/src/main/java/com/auto/clicker/autoclicker/ui/activity/menu/ScriptsActivity.java
@@ -1,4 +1,4 @@
-package com.auto.clicker.autoclicker.presentation.ui.activity.menu;
+package com.auto.clicker.autoclicker.ui.activity.menu;
import android.os.Bundle;
diff --git a/app/src/main/java/com/auto/clicker/autoclicker/ui/activity/menu/TroubleshootingActivity.java b/app/src/main/java/com/auto/clicker/autoclicker/ui/activity/menu/TroubleshootingActivity.java
new file mode 100644
index 0000000..709e6d4
--- /dev/null
+++ b/app/src/main/java/com/auto/clicker/autoclicker/ui/activity/menu/TroubleshootingActivity.java
@@ -0,0 +1,25 @@
+package com.auto.clicker.autoclicker.ui.activity.menu;
+
+import android.os.Bundle;
+
+import androidx.activity.EdgeToEdge;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.core.graphics.Insets;
+import androidx.core.view.ViewCompat;
+import androidx.core.view.WindowInsetsCompat;
+import com.auto.clicker.autoclicker.R;
+
+public class TroubleshootingActivity extends AppCompatActivity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ EdgeToEdge.enable(this);
+ setContentView(R.layout.activity_troubleshooting);
+ ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
+ Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
+ v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
+ return insets;
+ });
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/auto/clicker/autoclicker/ui/activity/setting/SettingActivity.java b/app/src/main/java/com/auto/clicker/autoclicker/ui/activity/setting/SettingActivity.java
new file mode 100644
index 0000000..c3e5588
--- /dev/null
+++ b/app/src/main/java/com/auto/clicker/autoclicker/ui/activity/setting/SettingActivity.java
@@ -0,0 +1,67 @@
+package com.auto.clicker.autoclicker.ui.activity.setting;
+
+import android.os.Bundle;
+import android.view.LayoutInflater;
+
+import androidx.activity.EdgeToEdge;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.core.graphics.Insets;
+import androidx.core.view.ViewCompat;
+import androidx.core.view.WindowInsetsCompat;
+
+import com.auto.clicker.autoclicker.R;
+import com.auto.clicker.autoclicker.databinding.ActivitySettingBinding;
+import com.auto.clicker.autoclicker.databinding.HowTabBinding;
+import com.auto.clicker.autoclicker.ui.adapter.setting.SettingAdapter;
+import com.google.android.material.tabs.TabLayoutMediator;
+
+public class SettingActivity extends AppCompatActivity {
+ private ActivitySettingBinding binding;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ EdgeToEdge.enable(this);
+ binding = ActivitySettingBinding.inflate(getLayoutInflater());
+ setContentView(binding.getRoot());
+ ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
+ Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
+ v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
+ return insets;
+ });
+
+ initPagerAdapter();
+ initTabs();
+
+ binding.settingViewpager.setCurrentItem(0, false);
+ }
+
+ private void initPagerAdapter() {
+ SettingAdapter adapter = new SettingAdapter(this);
+ binding.settingViewpager.setAdapter(adapter);
+ }
+
+ private void initTabs() {
+ new TabLayoutMediator(binding.settingTablayout, binding.settingViewpager, (tab, idx) -> {
+ com.auto.clicker.autoclicker.databinding.HowTabBinding tabViewBinding = HowTabBinding.inflate(LayoutInflater.from(this));
+ tab.setCustomView(tabViewBinding.getRoot());
+ decorateTab(tabViewBinding, idx);
+ }).attach();
+ }
+
+ private void decorateTab(HowTabBinding tabBinding, int position) {
+ String text = "";
+ if (position == 0) {
+ text = "Actions";
+ } else if (position == 1) {
+ text = "UI Size";
+ }
+ tabBinding.textCustom.setText(text);
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ binding = null;
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/auto/clicker/autoclicker/presentation/ui/activity/welcome/AgreementActivity.java b/app/src/main/java/com/auto/clicker/autoclicker/ui/activity/welcome/AgreementActivity.java
similarity index 93%
rename from app/src/main/java/com/auto/clicker/autoclicker/presentation/ui/activity/welcome/AgreementActivity.java
rename to app/src/main/java/com/auto/clicker/autoclicker/ui/activity/welcome/AgreementActivity.java
index d31757c..bbac569 100644
--- a/app/src/main/java/com/auto/clicker/autoclicker/presentation/ui/activity/welcome/AgreementActivity.java
+++ b/app/src/main/java/com/auto/clicker/autoclicker/ui/activity/welcome/AgreementActivity.java
@@ -1,4 +1,4 @@
-package com.auto.clicker.autoclicker.presentation.ui.activity.welcome;
+package com.auto.clicker.autoclicker.ui.activity.welcome;
import android.content.Intent;
import android.os.Bundle;
@@ -13,7 +13,7 @@ import androidx.core.view.WindowInsetsCompat;
import com.auto.clicker.autoclicker.R;
import com.auto.clicker.autoclicker.databinding.ActivityAgreementBinding;
-import com.auto.clicker.autoclicker.presentation.ui.activity.MainActivity;
+import com.auto.clicker.autoclicker.ui.activity.main.MainActivity;
public class AgreementActivity extends AppCompatActivity {
private ActivityAgreementBinding binding;
diff --git a/app/src/main/java/com/auto/clicker/autoclicker/presentation/ui/activity/welcome/LauncherActivity.java b/app/src/main/java/com/auto/clicker/autoclicker/ui/activity/welcome/LauncherActivity.java
similarity index 68%
rename from app/src/main/java/com/auto/clicker/autoclicker/presentation/ui/activity/welcome/LauncherActivity.java
rename to app/src/main/java/com/auto/clicker/autoclicker/ui/activity/welcome/LauncherActivity.java
index becf6d9..9580dea 100644
--- a/app/src/main/java/com/auto/clicker/autoclicker/presentation/ui/activity/welcome/LauncherActivity.java
+++ b/app/src/main/java/com/auto/clicker/autoclicker/ui/activity/welcome/LauncherActivity.java
@@ -1,15 +1,9 @@
-package com.auto.clicker.autoclicker.presentation.ui.activity.welcome;
+package com.auto.clicker.autoclicker.ui.activity.welcome;
import android.content.Intent;
import android.os.Bundle;
-import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
-import androidx.core.graphics.Insets;
-import androidx.core.view.ViewCompat;
-import androidx.core.view.WindowInsetsCompat;
-
-import com.auto.clicker.autoclicker.R;
public class LauncherActivity extends AppCompatActivity {
diff --git a/app/src/main/java/com/auto/clicker/autoclicker/presentation/ui/activity/welcome/SplashActivity.java b/app/src/main/java/com/auto/clicker/autoclicker/ui/activity/welcome/SplashActivity.java
similarity index 94%
rename from app/src/main/java/com/auto/clicker/autoclicker/presentation/ui/activity/welcome/SplashActivity.java
rename to app/src/main/java/com/auto/clicker/autoclicker/ui/activity/welcome/SplashActivity.java
index 6283d51..c674287 100644
--- a/app/src/main/java/com/auto/clicker/autoclicker/presentation/ui/activity/welcome/SplashActivity.java
+++ b/app/src/main/java/com/auto/clicker/autoclicker/ui/activity/welcome/SplashActivity.java
@@ -1,4 +1,4 @@
-package com.auto.clicker.autoclicker.presentation.ui.activity.welcome;
+package com.auto.clicker.autoclicker.ui.activity.welcome;
import android.content.Intent;
import android.os.Bundle;
@@ -12,7 +12,7 @@ import androidx.core.view.WindowInsetsCompat;
import com.auto.clicker.autoclicker.R;
import com.auto.clicker.autoclicker.databinding.ActivitySplashBinding;
-import com.auto.clicker.autoclicker.presentation.ui.activity.MainActivity;
+import com.auto.clicker.autoclicker.ui.activity.main.MainActivity;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.resource.bitmap.RoundedCorners;
diff --git a/app/src/main/java/com/auto/clicker/autoclicker/ui/adapter/menu/HowAdapter.java b/app/src/main/java/com/auto/clicker/autoclicker/ui/adapter/menu/HowAdapter.java
new file mode 100644
index 0000000..35e6822
--- /dev/null
+++ b/app/src/main/java/com/auto/clicker/autoclicker/ui/adapter/menu/HowAdapter.java
@@ -0,0 +1,34 @@
+package com.auto.clicker.autoclicker.ui.adapter.menu;
+
+import androidx.annotation.NonNull;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentActivity;
+import androidx.viewpager2.adapter.FragmentStateAdapter;
+
+import com.auto.clicker.autoclicker.ui.fragment.menu.HowMultiFragment;
+import com.auto.clicker.autoclicker.ui.fragment.menu.HowSingleFragment;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class HowAdapter extends FragmentStateAdapter {
+ private final List fragmentList;
+
+ public HowAdapter(@NonNull FragmentActivity fragmentActivity) {
+ super(fragmentActivity);
+ fragmentList = new ArrayList<>();
+ fragmentList.add(new HowSingleFragment());
+ fragmentList.add(new HowMultiFragment());
+ }
+
+ @NonNull
+ @Override
+ public Fragment createFragment(int position) {
+ return fragmentList.get(position);
+ }
+
+ @Override
+ public int getItemCount() {
+ return fragmentList.size();
+ }
+}
diff --git a/app/src/main/java/com/auto/clicker/autoclicker/ui/adapter/setting/SettingAdapter.java b/app/src/main/java/com/auto/clicker/autoclicker/ui/adapter/setting/SettingAdapter.java
new file mode 100644
index 0000000..78a589a
--- /dev/null
+++ b/app/src/main/java/com/auto/clicker/autoclicker/ui/adapter/setting/SettingAdapter.java
@@ -0,0 +1,34 @@
+package com.auto.clicker.autoclicker.ui.adapter.setting;
+
+import androidx.annotation.NonNull;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentActivity;
+import androidx.viewpager2.adapter.FragmentStateAdapter;
+
+import com.auto.clicker.autoclicker.ui.fragment.setting.ActionFragment;
+import com.auto.clicker.autoclicker.ui.fragment.setting.UISizeFragment;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class SettingAdapter extends FragmentStateAdapter {
+ private final List fragmentList;
+
+ public SettingAdapter(@NonNull FragmentActivity fragmentActivity) {
+ super(fragmentActivity);
+ fragmentList = new ArrayList<>();
+ fragmentList.add(new ActionFragment());
+ fragmentList.add(new UISizeFragment());
+ }
+
+ @NonNull
+ @Override
+ public Fragment createFragment(int position) {
+ return fragmentList.get(position);
+ }
+
+ @Override
+ public int getItemCount() {
+ return fragmentList.size();
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/auto/clicker/autoclicker/ui/fragment/menu/HowMultiFragment.java b/app/src/main/java/com/auto/clicker/autoclicker/ui/fragment/menu/HowMultiFragment.java
new file mode 100644
index 0000000..2373479
--- /dev/null
+++ b/app/src/main/java/com/auto/clicker/autoclicker/ui/fragment/menu/HowMultiFragment.java
@@ -0,0 +1,20 @@
+package com.auto.clicker.autoclicker.ui.fragment.menu;
+
+import android.os.Bundle;
+
+import androidx.fragment.app.Fragment;
+
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.auto.clicker.autoclicker.R;
+
+public class HowMultiFragment extends Fragment {
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ return inflater.inflate(R.layout.fragment_how_multi, container, false);
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/auto/clicker/autoclicker/ui/fragment/menu/HowSingleFragment.java b/app/src/main/java/com/auto/clicker/autoclicker/ui/fragment/menu/HowSingleFragment.java
new file mode 100644
index 0000000..731abee
--- /dev/null
+++ b/app/src/main/java/com/auto/clicker/autoclicker/ui/fragment/menu/HowSingleFragment.java
@@ -0,0 +1,20 @@
+package com.auto.clicker.autoclicker.ui.fragment.menu;
+
+import android.os.Bundle;
+
+import androidx.fragment.app.Fragment;
+
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.auto.clicker.autoclicker.R;
+
+public class HowSingleFragment extends Fragment {
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ return inflater.inflate(R.layout.fragment_how_single, container, false);
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/auto/clicker/autoclicker/ui/fragment/setting/ActionFragment.java b/app/src/main/java/com/auto/clicker/autoclicker/ui/fragment/setting/ActionFragment.java
new file mode 100644
index 0000000..e9e7348
--- /dev/null
+++ b/app/src/main/java/com/auto/clicker/autoclicker/ui/fragment/setting/ActionFragment.java
@@ -0,0 +1,20 @@
+package com.auto.clicker.autoclicker.ui.fragment.setting;
+
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.fragment.app.Fragment;
+
+import com.auto.clicker.autoclicker.R;
+
+public class ActionFragment extends Fragment {
+
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ return inflater.inflate(R.layout.fragment_action, container, false);
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/auto/clicker/autoclicker/ui/fragment/setting/UISizeFragment.java b/app/src/main/java/com/auto/clicker/autoclicker/ui/fragment/setting/UISizeFragment.java
new file mode 100644
index 0000000..bafd485
--- /dev/null
+++ b/app/src/main/java/com/auto/clicker/autoclicker/ui/fragment/setting/UISizeFragment.java
@@ -0,0 +1,20 @@
+package com.auto.clicker.autoclicker.ui.fragment.setting;
+
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.fragment.app.Fragment;
+
+import com.auto.clicker.autoclicker.R;
+
+public class UISizeFragment extends Fragment {
+
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ return inflater.inflate(R.layout.fragment_u_i_size, container, false);
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/auto/clicker/autoclicker/util/PrefUtils.java b/app/src/main/java/com/auto/clicker/autoclicker/util/PrefUtils.java
new file mode 100644
index 0000000..c9a87c7
--- /dev/null
+++ b/app/src/main/java/com/auto/clicker/autoclicker/util/PrefUtils.java
@@ -0,0 +1,18 @@
+package com.auto.clicker.autoclicker.util;
+
+import android.content.Context;
+
+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, int shown) {
+ context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
+ .edit().putInt(KEY_FLOATING_SHOWN, shown).apply();
+ }
+
+ public static int getFloatingShown(Context context) {
+ return context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
+ .getInt(KEY_FLOATING_SHOWN, 0);
+ }
+}
diff --git a/app/src/main/java/com/auto/clicker/autoclicker/util/ScreenUtils.java b/app/src/main/java/com/auto/clicker/autoclicker/util/ScreenUtils.java
new file mode 100644
index 0000000..13ec4e8
--- /dev/null
+++ b/app/src/main/java/com/auto/clicker/autoclicker/util/ScreenUtils.java
@@ -0,0 +1,14 @@
+package com.auto.clicker.autoclicker.util;
+
+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);
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/auto/clicker/autoclicker/util/ViewUtils.java b/app/src/main/java/com/auto/clicker/autoclicker/util/ViewUtils.java
new file mode 100644
index 0000000..0713a52
--- /dev/null
+++ b/app/src/main/java/com/auto/clicker/autoclicker/util/ViewUtils.java
@@ -0,0 +1,13 @@
+package com.auto.clicker.autoclicker.util;
+
+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);
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/auto/clicker/autoclicker/view/ConnectingLineView.java b/app/src/main/java/com/auto/clicker/autoclicker/view/ConnectingLineView.java
new file mode 100644
index 0000000..02e85ee
--- /dev/null
+++ b/app/src/main/java/com/auto/clicker/autoclicker/view/ConnectingLineView.java
@@ -0,0 +1,38 @@
+package com.auto.clicker.autoclicker.view;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+
+public class ConnectingLineView extends View {
+ private float startX, startY, endX, endY;
+ private final Paint paint;
+
+ public ConnectingLineView(Context context) {
+ super(context);
+ paint = new Paint();
+ paint.setColor(Color.RED);
+ paint.setAlpha(180);
+ paint.setStrokeWidth(5);
+ paint.setAntiAlias(true);
+ paint.setStyle(Paint.Style.STROKE);
+ }
+
+ @Override
+ protected void onDraw(@NonNull Canvas canvas) {
+ super.onDraw(canvas);
+ canvas.drawLine(startX, startY, endX, endY, paint);
+ }
+
+ public void setPoints(float startX, float startY, float endX, float endY) {
+ this.startX = startX;
+ this.startY = startY;
+ this.endX = endX;
+ this.endY = endY;
+ invalidate();
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/auto/clicker/autoclicker/view/FloatingViewManager.java b/app/src/main/java/com/auto/clicker/autoclicker/view/FloatingViewManager.java
new file mode 100644
index 0000000..de6b4e1
--- /dev/null
+++ b/app/src/main/java/com/auto/clicker/autoclicker/view/FloatingViewManager.java
@@ -0,0 +1,688 @@
+package com.auto.clicker.autoclicker.view;
+
+import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.graphics.Color;
+import android.graphics.PixelFormat;
+import android.graphics.Point;
+import android.os.Build;
+import android.provider.Settings;
+import android.text.InputType;
+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.inputmethod.InputMethodManager;
+import android.widget.EditText;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.appcompat.app.AlertDialog;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import com.auto.clicker.autoclicker.R;
+import com.auto.clicker.autoclicker.data.Event;
+import com.auto.clicker.autoclicker.data.EventWrapper;
+import com.auto.clicker.autoclicker.data.PointEvent;
+import com.auto.clicker.autoclicker.data.PositionUpdater;
+import com.auto.clicker.autoclicker.data.SlideEvent;
+import com.auto.clicker.autoclicker.service.AutoClickService;
+import com.auto.clicker.autoclicker.service.ForegroundService;
+import com.auto.clicker.autoclicker.util.PrefUtils;
+import com.auto.clicker.autoclicker.util.ScreenUtils;
+import com.auto.clicker.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 static final int TOUCH_POINT_SIZE = 100;
+
+ private final Context context;
+ private final WindowManager windowManager;
+ private final int screenWidth;
+ private final int screenHeight;
+
+ private final List runtimeEvents = new ArrayList<>();
+ private int eventOrderCounter = 1;
+
+ private LinearLayout multipleControlBarView;
+ private WindowManager.LayoutParams multipleControlBarParams;
+
+ private boolean isMultipleRunning = false;
+ private long lastToggleTime = 0;
+
+ public FloatingViewManager(Context context) {
+ this.context = context;
+ this.windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+ Point screenSize = ScreenUtils.getScreenSize(context);
+ this.screenWidth = screenSize.x;
+ this.screenHeight = screenSize.y;
+ registerBroadcastReceiver();
+ Log.d(TAG, "Screen size: " + screenWidth + "x" + screenHeight);
+ }
+
+ public void showFloatingViews(int mode) throws SecurityException {
+ if (!Settings.canDrawOverlays(context)) {
+ throw new SecurityException("需要悬浮窗许可");
+ }
+
+ removeFloatingViews();
+ showMultipleModeViews(mode);
+
+ Log.d(TAG, "添加悬浮窗, 模式 = " + mode);
+ }
+
+ public void removeFloatingViews() {
+ try {
+ for (EventWrapper wrapper : new ArrayList<>(runtimeEvents)) {
+ if (wrapper.getType() == EventWrapper.EventType.POINT) {
+ PointEvent pe = (PointEvent) wrapper.getEvent();
+ safeRemoveView(pe.getView());
+ }
+ }
+
+ for (EventWrapper wrapper : new ArrayList<>(runtimeEvents)) {
+ if (wrapper.getType() == EventWrapper.EventType.SLIDE) {
+ SlideEvent se = (SlideEvent) wrapper.getEvent();
+ safeRemoveView(se.getStartPoint().getView());
+ safeRemoveView(se.getEndPoint().getView());
+ safeRemoveView(se.getLineView());
+ }
+ }
+
+ runtimeEvents.clear();
+
+ Log.d(TAG, "remove runtimeEvents: " + runtimeEvents.size() + " eventOrderCounter :" + eventOrderCounter);
+
+ if (multipleControlBarView != null) {
+ safeRemoveView(multipleControlBarView);
+ multipleControlBarView = null;
+ }
+
+ isMultipleRunning = false;
+ Log.d(TAG, "悬浮窗已移除");
+ } catch (Exception e) {
+ Log.e(TAG, "移除悬浮窗失败", e);
+ isMultipleRunning = false;
+ }
+ }
+
+ private void safeRemoveView(View view) {
+ try {
+ windowManager.removeView(view);
+ } catch (Exception e) {
+ Log.w(TAG, "移除视图失败: " + view, e);
+ }
+ }
+
+ private void closeFloatingViews() {
+ AutoClickService service = AutoClickService.getInstance();
+ if (service != null) {
+ service.stopClicking();
+ } else {
+ Log.d(TAG, "自动点击服务没有初始化");
+ Toast.makeText(context, "请同意无障碍服务权限", Toast.LENGTH_SHORT).show();
+ }
+
+ updateTouchPointsBackground(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);
+ }
+
+ private void showMultipleModeViews(int mode) {
+ initMultipleTouchPointView();
+ initMultipleControlBar();
+
+ if (multipleControlBarView != null) {
+ windowManager.addView(multipleControlBarView, multipleControlBarParams);
+ }
+ }
+
+ private void initMultipleTouchPointView() {
+ runtimeEvents.clear();
+ eventOrderCounter = 1;
+
+ Log.d(TAG, "init runtimeEvents: " + runtimeEvents.size() + " eventOrderCounter :" + eventOrderCounter);
+ }
+
+ @SuppressLint("ClickableViewAccessibility")
+ private void initMultipleControlBar() {
+ multipleControlBarView = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.multiple_control_bar, null);
+ multipleControlBarParams = createControlBarParams();
+
+ setupDraggableView(multipleControlBarView, multipleControlBarParams, this::updateMoreControlBarPosition);
+
+ setupControlButtons(multipleControlBarView);
+ }
+
+ private void setupControlButtons(LinearLayout controlBar) {
+ ImageView playButton = controlBar.findViewById(R.id.play_button);
+ ImageView addButton = controlBar.findViewById(R.id.add_button);
+ ImageView removeButton = controlBar.findViewById(R.id.remove_button);
+ ImageView slideButton = controlBar.findViewById(R.id.slide_button);
+ ImageView closeButton = controlBar.findViewById(R.id.close_button);
+ ImageView settingButton = controlBar.findViewById(R.id.settings_button);
+
+ playButton.setOnClickListener(v -> toggleMultipleClicking());
+ addButton.setOnClickListener(v -> addPointEvent());
+ removeButton.setOnClickListener(v -> removeLastEvent());
+ slideButton.setOnClickListener(v -> addSlideEvent());
+ closeButton.setOnClickListener(v -> closeFloatingViews());
+ settingButton.setOnClickListener(v -> showInputDialog());
+ }
+
+ private TextView createTouchPointView(String label) {
+ TextView touchPointView = new TextView(context);
+ touchPointView.setBackgroundResource(R.drawable.un_touch_point);
+ touchPointView.setGravity(Gravity.CENTER);
+ touchPointView.setText(label);
+ touchPointView.setTextColor(Color.BLACK);
+ touchPointView.setTextSize(16);
+ return touchPointView;
+ }
+
+ private WindowManager.LayoutParams createTouchPointParams() {
+ int overlayType = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ?
+ WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY :
+ WindowManager.LayoutParams.TYPE_PHONE;
+
+ WindowManager.LayoutParams params = new WindowManager.LayoutParams(
+ TOUCH_POINT_SIZE, TOUCH_POINT_SIZE,
+ overlayType,
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
+ WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL,
+ PixelFormat.TRANSLUCENT
+ );
+ params.gravity = Gravity.TOP | Gravity.START;
+ return params;
+ }
+
+ private WindowManager.LayoutParams createControlBarParams() {
+ int overlayType = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ?
+ WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY :
+ WindowManager.LayoutParams.TYPE_PHONE;
+
+ WindowManager.LayoutParams params = new WindowManager.LayoutParams(
+ WindowManager.LayoutParams.WRAP_CONTENT,
+ WindowManager.LayoutParams.WRAP_CONTENT,
+ overlayType,
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
+ WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL,
+ PixelFormat.TRANSLUCENT
+ );
+ params.gravity = Gravity.TOP | Gravity.START;
+ params.x = 0;
+ params.y = 200;
+ return params;
+ }
+
+ private WindowManager.LayoutParams createLineViewParams() {
+ int overlayType = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ?
+ WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY :
+ WindowManager.LayoutParams.TYPE_PHONE;
+
+ WindowManager.LayoutParams params = new WindowManager.LayoutParams(
+ WindowManager.LayoutParams.MATCH_PARENT,
+ WindowManager.LayoutParams.MATCH_PARENT,
+ overlayType,
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
+ WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL |
+ WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE,
+ PixelFormat.TRANSLUCENT
+ );
+ params.gravity = Gravity.TOP | Gravity.START;
+ params.x = 0;
+ params.y = 0;
+ return params;
+ }
+
+ @SuppressLint("ClickableViewAccessibility")
+ private void addPointEvent() {
+ if (isMultipleRunning) {
+ Toast.makeText(context, "请停止点击", Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ TextView point = createTouchPointView(String.valueOf(eventOrderCounter));
+
+ WindowManager.LayoutParams params = createTouchPointParams();
+ params.x = screenWidth / 2 - TOUCH_POINT_SIZE / 2;
+ params.y = screenHeight / 2 - TOUCH_POINT_SIZE / 2;
+
+ PointEvent pointEvent = new PointEvent(eventOrderCounter, params.x, params.y, point, params);
+
+ runtimeEvents.add(new EventWrapper(EventWrapper.EventType.POINT, pointEvent, eventOrderCounter));
+ eventOrderCounter++;
+
+ Log.d(TAG, "addPoint runtimeEvents: " + runtimeEvents.size() + " eventOrderCounter :" + eventOrderCounter);
+
+ setupDraggablePoint(point, params, pointEvent);
+ windowManager.addView(point, params);
+ updateServicePositions();
+ }
+
+ @SuppressLint("ClickableViewAccessibility")
+ private void addSlideEvent() {
+ if (isMultipleRunning) {
+ Toast.makeText(context, "请先停止点击", Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ TextView startPoint = createTouchPointView(String.valueOf(eventOrderCounter));
+ WindowManager.LayoutParams startParams = createTouchPointParams();
+ startParams.x = screenWidth / 2 - 100;
+ startParams.y = screenHeight / 2 - 50;
+ PointEvent startPointEvent = new PointEvent(eventOrderCounter, startParams.x, startParams.y, startPoint, startParams);
+
+ TextView endPoint = createTouchPointView(String.valueOf(eventOrderCounter));
+ WindowManager.LayoutParams endParams = createTouchPointParams();
+ endParams.x = screenWidth / 2 + 100;
+ endParams.y = screenHeight / 2 - 50;
+ PointEvent endPointEvent = new PointEvent(eventOrderCounter, endParams.x, endParams.y, endPoint, endParams);
+
+ ConnectingLineView lineView = new ConnectingLineView(context);
+ WindowManager.LayoutParams lineParams = createLineViewParams();
+
+ SlideEvent slideEvent = new SlideEvent(eventOrderCounter, startPointEvent, endPointEvent, lineView, lineParams);
+
+ runtimeEvents.add(new EventWrapper(EventWrapper.EventType.SLIDE, slideEvent, eventOrderCounter));
+ eventOrderCounter++;
+
+ Log.d(TAG, "addSlide runtimeEvents: " + runtimeEvents.size() + " eventOrderCounter :" + eventOrderCounter);
+
+ setupDraggableSlidePoint(startPoint, startParams, startPointEvent, slideEvent);
+ setupDraggableSlidePoint(endPoint, endParams, endPointEvent, slideEvent);
+
+ windowManager.addView(lineView, lineParams);
+ windowManager.addView(startPoint, startParams);
+ windowManager.addView(endPoint, endParams);
+
+ updateLinePosition(slideEvent);
+ updateServicePositions();
+ }
+
+ private void removeLastEvent() {
+ if (isMultipleRunning) {
+ Toast.makeText(context, "请先停止点击", Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ if (runtimeEvents.isEmpty()) {
+ Toast.makeText(context, "没有更多的事件需要删除", Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ Event lastEvent = runtimeEvents.remove(runtimeEvents.size() - 1).getEvent();
+ removeEvent(lastEvent);
+ eventOrderCounter--;
+ updateServicePositions();
+ }
+
+ private void removeEvent(Event event) {
+ try {
+ if (event instanceof PointEvent) {
+ PointEvent pointEvent = (PointEvent) event;
+ windowManager.removeView(pointEvent.getView());
+ } else if (event instanceof SlideEvent) {
+ SlideEvent slideEvent = (SlideEvent) event;
+ windowManager.removeView(slideEvent.getStartPoint().getView());
+ windowManager.removeView(slideEvent.getEndPoint().getView());
+ windowManager.removeView(slideEvent.getLineView());
+ }
+ } catch (Exception e) {
+ Log.w(TAG, "删除事件失败", e);
+ }
+ }
+
+ private void updateServicePositions() {
+ AutoClickService service = AutoClickService.getInstance();
+ if (service != null) {
+ service.addMultipleClickEvents(runtimeEvents);
+ }
+ }
+
+ private void toggleMultipleClicking() {
+ if (isDebounced()) return;
+
+ AutoClickService service = AutoClickService.getInstance();
+ if (service == null) {
+ handleMissingService();
+ return;
+ }
+
+ if (runtimeEvents.isEmpty()) {
+ Toast.makeText(context, "请添加一个触摸点或者滑动事件", Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ if (isMultipleRunning) {
+ stopMultiClicking(service);
+ } else {
+ startMultiClicking(service);
+ }
+
+ isMultipleRunning = !isMultipleRunning;
+ Log.d(TAG, "多点点击服务状态: " + service.isClicking());
+ }
+
+ private void startMultiClicking(AutoClickService service) {
+ service.addMultipleClickEvents(runtimeEvents);
+ service.startClicking();
+
+ Log.d(TAG, "开始多点点击事件 " + runtimeEvents.size());
+ Toast.makeText(context, "多点点击开始", Toast.LENGTH_SHORT).show();
+
+ updateTouchPointsBackground(R.drawable.touch_point);
+ }
+
+ private void stopMultiClicking(AutoClickService service) {
+ service.stopClicking();
+ Toast.makeText(context, "多点点击停止", Toast.LENGTH_SHORT).show();
+
+ updateTouchPointsBackground(R.drawable.un_touch_point);
+ }
+
+ private void updateTouchPointsBackground(int resourceId) {
+ for (EventWrapper wrapper : new ArrayList<>(runtimeEvents)) {
+ if (wrapper.getType() == EventWrapper.EventType.POINT) {
+ PointEvent pe = (PointEvent) wrapper.getEvent();
+ pe.getView().setBackgroundResource(resourceId);
+ }
+ }
+
+ for (EventWrapper wrapper : new ArrayList<>(runtimeEvents)) {
+ if (wrapper.getType() == EventWrapper.EventType.SLIDE) {
+ SlideEvent se = (SlideEvent) wrapper.getEvent();
+ se.getStartPoint().getView().setBackgroundResource(resourceId);
+ se.getEndPoint().getView().setBackgroundResource(resourceId);
+ }
+ }
+ }
+
+ private void handleMissingService() {
+ Log.e(TAG, "自动点击服务没有初始化");
+ 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);
+ }
+
+ private boolean isDebounced() {
+ long currentTime = System.currentTimeMillis();
+ if (currentTime - lastToggleTime < DEBOUNCE_INTERVAL) {
+ return true;
+ }
+ lastToggleTime = currentTime;
+ return false;
+ }
+
+ @SuppressLint("ClickableViewAccessibility")
+ private void setupDraggableView(View view, WindowManager.LayoutParams params, PositionUpdater positionUpdater) {
+ view.setOnTouchListener(new View.OnTouchListener() {
+ private float lastX, lastY;
+ private float paramX, paramY;
+ private boolean isDragging = false;
+
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ if (isMultipleRunning) {
+ if (event.getAction() == MotionEvent.ACTION_UP) {
+ v.performClick();
+ return true;
+ }
+ return false;
+ }
+
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ lastX = event.getRawX();
+ lastY = event.getRawY();
+ paramX = params.x;
+ paramY = params.y;
+ isDragging = true;
+ return true;
+
+ case MotionEvent.ACTION_MOVE:
+ if (isDragging) {
+ float dx = event.getRawX() - lastX;
+ float dy = event.getRawY() - lastY;
+ positionUpdater.update(paramX, paramY, dx, dy);
+ return true;
+ }
+ break;
+
+ case MotionEvent.ACTION_UP:
+ isDragging = false;
+ v.performClick();
+ return true;
+ }
+ return false;
+ }
+ });
+ }
+
+ @SuppressLint("ClickableViewAccessibility")
+ private void setupDraggablePoint(TextView point, WindowManager.LayoutParams params, PointEvent pointEvent) {
+ point.setOnTouchListener(new View.OnTouchListener() {
+ private float lastX, lastY;
+ private float paramX, paramY;
+
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ if (isMultipleRunning) {
+ if (event.getAction() == MotionEvent.ACTION_UP) {
+ v.performClick();
+ return true;
+ }
+ return false;
+ }
+
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ lastX = event.getRawX();
+ lastY = event.getRawY();
+ paramX = params.x;
+ paramY = params.y;
+ return true;
+ case MotionEvent.ACTION_MOVE:
+ float dx = event.getRawX() - lastX;
+ float dy = event.getRawY() - lastY;
+ Point constrainedPoint = ViewUtils.constrainToScreen(
+ paramX + dx, paramY + dy,
+ screenWidth - point.getWidth(), screenHeight - point.getHeight());
+ params.x = constrainedPoint.x;
+ params.y = constrainedPoint.y;
+ pointEvent.setX(params.x);
+ pointEvent.setY(params.y);
+ windowManager.updateViewLayout(point, params);
+ updateServicePositions();
+ return true;
+ case MotionEvent.ACTION_UP:
+ v.performClick();
+ return true;
+ }
+ return false;
+ }
+ });
+ }
+
+ @SuppressLint("ClickableViewAccessibility")
+ private void setupDraggableSlidePoint(TextView point, WindowManager.LayoutParams params,
+ PointEvent pointEvent, SlideEvent slideEvent) {
+ point.setOnTouchListener(new View.OnTouchListener() {
+ private float lastX, lastY;
+ private float paramX, paramY;
+
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ if (isMultipleRunning) {
+ if (event.getAction() == MotionEvent.ACTION_UP) {
+ v.performClick();
+ return true;
+ }
+ return false;
+ }
+
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ lastX = event.getRawX();
+ lastY = event.getRawY();
+ paramX = params.x;
+ paramY = params.y;
+ return true;
+ case MotionEvent.ACTION_MOVE:
+ float dx = event.getRawX() - lastX;
+ float dy = event.getRawY() - lastY;
+ Point constrainedPoint = ViewUtils.constrainToScreen(
+ paramX + dx, paramY + dy,
+ screenWidth - point.getWidth(), screenHeight - point.getHeight());
+ params.x = constrainedPoint.x;
+ params.y = constrainedPoint.y;
+ pointEvent.setX(params.x);
+ pointEvent.setY(params.y);
+ windowManager.updateViewLayout(point, params);
+
+ updateLinePosition(slideEvent);
+ updateServicePositions();
+ return true;
+ case MotionEvent.ACTION_UP:
+ v.performClick();
+ return true;
+ }
+ return false;
+ }
+ });
+ }
+
+ private void updateMoreControlBarPosition(float paramX, float paramY, float dx, float dy) {
+ Point constrainedPoint = ViewUtils.constrainToScreen(
+ paramX + dx, paramY + dy,
+ screenWidth - multipleControlBarView.getWidth(),
+ screenHeight - multipleControlBarView.getHeight());
+ multipleControlBarParams.x = constrainedPoint.x;
+ multipleControlBarParams.y = constrainedPoint.y;
+ windowManager.updateViewLayout(multipleControlBarView, multipleControlBarParams);
+ }
+
+ private void updateLinePosition(SlideEvent slideEvent) {
+ PointEvent start = slideEvent.getStartPoint();
+ PointEvent end = slideEvent.getEndPoint();
+ ConnectingLineView lineView = slideEvent.getLineView();
+
+ float startCenterX = start.getX() + (float) TOUCH_POINT_SIZE / 2;
+ float startCenterY = start.getY() + (float) TOUCH_POINT_SIZE / 2;
+ float endCenterX = end.getX() + (float) TOUCH_POINT_SIZE / 2;
+ float endCenterY = end.getY() + (float) TOUCH_POINT_SIZE / 2;
+
+ lineView.setPoints(startCenterX, startCenterY, endCenterX, endCenterY);
+ }
+
+ public void showInputDialog() {
+ EditText input = new EditText(context);
+ input.setInputType(InputType.TYPE_CLASS_NUMBER);
+ input.setHint("输入点击间隔(ms)");
+ input.setFocusableInTouchMode(true);
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(context);
+ builder.setTitle("设置点击间隔")
+ .setView(input)
+ .setPositiveButton("确定", (dialog, which) -> {
+ String value = input.getText().toString();
+ try {
+ long interval = Long.parseLong(value);
+ AutoClickService service = AutoClickService.getInstance();
+ if (service != null) {
+ service.setClickInterval(interval);
+ } else {
+ Log.w("InputDialog", "AutoClickService 未初始化");
+ }
+ } catch (NumberFormatException e) {
+ Toast.makeText(context, "无效输入", Toast.LENGTH_SHORT).show();
+ }
+ dialog.dismiss();
+ })
+ .setNegativeButton("取消", (dialog, which) -> dialog.dismiss());
+
+ AlertDialog dialog = builder.create();
+
+ if (!(context instanceof Activity)) {
+ if (dialog.getWindow() != null) {
+ dialog.getWindow().setType(
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ?
+ WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY :
+ WindowManager.LayoutParams.TYPE_PHONE
+ );
+ }
+ }
+
+ dialog.show();
+
+ input.requestFocus();
+ input.post(() -> {
+ InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
+ if (imm != null) {
+ imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0);
+ }
+ });
+ }
+
+ public void flashTouchPoint(int index) {
+ if (index >= 0 && index < runtimeEvents.size()) {
+ Event event = runtimeEvents.get(index).getEvent();
+ Log.d(TAG, "index : " + index);
+
+ if (event instanceof PointEvent) {
+ flashView(((PointEvent) event).getView());
+ } else if (event instanceof SlideEvent) {
+ SlideEvent slide = (SlideEvent) event;
+ flashView(slide.getStartPoint().getView());
+ flashView(slide.getEndPoint().getView());
+ }
+ }
+ }
+
+ private void flashView(View view) {
+ if (view != null) {
+ view.animate()
+ .alpha(0.3f)
+ .setDuration(100)
+ .withEndAction(() -> view.animate()
+ .alpha(1.0f)
+ .setDuration(100)
+ .start())
+ .start();
+ }
+ }
+
+ private void registerBroadcastReceiver() {
+ LocalBroadcastManager.getInstance(context).registerReceiver(
+ new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if ("com.auto.autoclicker.FLASH_TOUCH_POINT".equals(intent.getAction())) {
+ int index = intent.getIntExtra("index", -1);
+ flashTouchPoint(index);
+ }
+ }
+ },
+ new IntentFilter("com.auto.autoclicker.FLASH_TOUCH_POINT")
+ );
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/res/color/switch_thumb_selector.xml b/app/src/main/res/color/switch_thumb_selector.xml
new file mode 100644
index 0000000..34a52b2
--- /dev/null
+++ b/app/src/main/res/color/switch_thumb_selector.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/color/switch_track_selector.xml b/app/src/main/res/color/switch_track_selector.xml
new file mode 100644
index 0000000..a168593
--- /dev/null
+++ b/app/src/main/res/color/switch_track_selector.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/color/tab_text_color_selector.xml b/app/src/main/res/color/tab_text_color_selector.xml
new file mode 100644
index 0000000..6c0bb9c
--- /dev/null
+++ b/app/src/main/res/color/tab_text_color_selector.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/add.xml b/app/src/main/res/drawable/add.xml
new file mode 100644
index 0000000..476631d
--- /dev/null
+++ b/app/src/main/res/drawable/add.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/bg_rounded_rect.xml b/app/src/main/res/drawable/bg_rounded_rect.xml
new file mode 100644
index 0000000..1105890
--- /dev/null
+++ b/app/src/main/res/drawable/bg_rounded_rect.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/blue_arrow_down.xml b/app/src/main/res/drawable/blue_arrow_down.xml
new file mode 100644
index 0000000..737a25e
--- /dev/null
+++ b/app/src/main/res/drawable/blue_arrow_down.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/blue_ring.xml b/app/src/main/res/drawable/blue_ring.xml
new file mode 100644
index 0000000..41f4468
--- /dev/null
+++ b/app/src/main/res/drawable/blue_ring.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/blue_ring_bg.xml b/app/src/main/res/drawable/blue_ring_bg.xml
new file mode 100644
index 0000000..44d1e75
--- /dev/null
+++ b/app/src/main/res/drawable/blue_ring_bg.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/blue_roundend_rectangle.xml b/app/src/main/res/drawable/blue_roundend_rectangle.xml
new file mode 100644
index 0000000..89cb6cb
--- /dev/null
+++ b/app/src/main/res/drawable/blue_roundend_rectangle.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/cancel.xml b/app/src/main/res/drawable/cancel.xml
new file mode 100644
index 0000000..11dad8e
--- /dev/null
+++ b/app/src/main/res/drawable/cancel.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/control_bar_bg.xml b/app/src/main/res/drawable/control_bar_bg.xml
new file mode 100644
index 0000000..c5dd885
--- /dev/null
+++ b/app/src/main/res/drawable/control_bar_bg.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/control_setting.xml b/app/src/main/res/drawable/control_setting.xml
new file mode 100644
index 0000000..9ce3d8b
--- /dev/null
+++ b/app/src/main/res/drawable/control_setting.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/cut_off_line.xml b/app/src/main/res/drawable/cut_off_line.xml
new file mode 100644
index 0000000..fafc1c5
--- /dev/null
+++ b/app/src/main/res/drawable/cut_off_line.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/cycle.xml b/app/src/main/res/drawable/cycle.xml
new file mode 100644
index 0000000..4b60c51
--- /dev/null
+++ b/app/src/main/res/drawable/cycle.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/dash_line_1.xml b/app/src/main/res/drawable/dash_line_1.xml
new file mode 100644
index 0000000..f52e666
--- /dev/null
+++ b/app/src/main/res/drawable/dash_line_1.xml
@@ -0,0 +1,13 @@
+
+
+
diff --git a/app/src/main/res/drawable/detection.xml b/app/src/main/res/drawable/detection.xml
new file mode 100644
index 0000000..dd8950b
--- /dev/null
+++ b/app/src/main/res/drawable/detection.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/eye.xml b/app/src/main/res/drawable/eye.xml
new file mode 100644
index 0000000..c2dd283
--- /dev/null
+++ b/app/src/main/res/drawable/eye.xml
@@ -0,0 +1,12 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/hide_eye.xml b/app/src/main/res/drawable/hide_eye.xml
new file mode 100644
index 0000000..efc3eff
--- /dev/null
+++ b/app/src/main/res/drawable/hide_eye.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/hourglass.xml b/app/src/main/res/drawable/hourglass.xml
new file mode 100644
index 0000000..988d8c9
--- /dev/null
+++ b/app/src/main/res/drawable/hourglass.xml
@@ -0,0 +1,12 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/left_swipe.xml b/app/src/main/res/drawable/left_swipe.xml
new file mode 100644
index 0000000..2d5c815
--- /dev/null
+++ b/app/src/main/res/drawable/left_swipe.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/line_4.xml b/app/src/main/res/drawable/line_4.xml
new file mode 100644
index 0000000..f52e666
--- /dev/null
+++ b/app/src/main/res/drawable/line_4.xml
@@ -0,0 +1,13 @@
+
+
+
diff --git a/app/src/main/res/drawable/notification.xml b/app/src/main/res/drawable/notification.xml
new file mode 100644
index 0000000..2ca1304
--- /dev/null
+++ b/app/src/main/res/drawable/notification.xml
@@ -0,0 +1,14 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/play.xml b/app/src/main/res/drawable/play.xml
new file mode 100644
index 0000000..7f4adca
--- /dev/null
+++ b/app/src/main/res/drawable/play.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/remove.xml b/app/src/main/res/drawable/remove.xml
new file mode 100644
index 0000000..3420c18
--- /dev/null
+++ b/app/src/main/res/drawable/remove.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ring_has_bg.xml b/app/src/main/res/drawable/ring_has_bg.xml
new file mode 100644
index 0000000..7029461
--- /dev/null
+++ b/app/src/main/res/drawable/ring_has_bg.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/rounded_edittext_background.xml b/app/src/main/res/drawable/rounded_edittext_background.xml
new file mode 100644
index 0000000..10356e7
--- /dev/null
+++ b/app/src/main/res/drawable/rounded_edittext_background.xml
@@ -0,0 +1,4 @@
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/rounded_edittext_dotted_background.xml b/app/src/main/res/drawable/rounded_edittext_dotted_background.xml
new file mode 100644
index 0000000..24aaf84
--- /dev/null
+++ b/app/src/main/res/drawable/rounded_edittext_dotted_background.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/save.xml b/app/src/main/res/drawable/save.xml
new file mode 100644
index 0000000..336313b
--- /dev/null
+++ b/app/src/main/res/drawable/save.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/seek_bar_color_white.xml b/app/src/main/res/drawable/seek_bar_color_white.xml
new file mode 100644
index 0000000..a4674c9
--- /dev/null
+++ b/app/src/main/res/drawable/seek_bar_color_white.xml
@@ -0,0 +1,15 @@
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/seekbar_thumb_white.xml b/app/src/main/res/drawable/seekbar_thumb_white.xml
new file mode 100644
index 0000000..749191d
--- /dev/null
+++ b/app/src/main/res/drawable/seekbar_thumb_white.xml
@@ -0,0 +1,4 @@
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/slide.xml b/app/src/main/res/drawable/slide.xml
new file mode 100644
index 0000000..2cc21e7
--- /dev/null
+++ b/app/src/main/res/drawable/slide.xml
@@ -0,0 +1,12 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/stop.xml b/app/src/main/res/drawable/stop.xml
new file mode 100644
index 0000000..3e8e1c6
--- /dev/null
+++ b/app/src/main/res/drawable/stop.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/touch_point.xml b/app/src/main/res/drawable/touch_point.xml
new file mode 100644
index 0000000..53630de
--- /dev/null
+++ b/app/src/main/res/drawable/touch_point.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/un_touch_point.xml b/app/src/main/res/drawable/un_touch_point.xml
new file mode 100644
index 0000000..41f4468
--- /dev/null
+++ b/app/src/main/res/drawable/un_touch_point.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/white_arrow_right.xml b/app/src/main/res/drawable/white_arrow_right.xml
new file mode 100644
index 0000000..de5d78b
--- /dev/null
+++ b/app/src/main/res/drawable/white_arrow_right.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_accessibility.xml b/app/src/main/res/layout/activity_accessibility.xml
index 245a56c..85a6353 100644
--- a/app/src/main/res/layout/activity_accessibility.xml
+++ b/app/src/main/res/layout/activity_accessibility.xml
@@ -7,7 +7,7 @@
android:layout_height="match_parent"
android:background="@color/black"
android:padding="24dp"
- tools:context=".presentation.ui.activity.AccessibilityActivity">
+ tools:context=".ui.activity.main.AccessibilityActivity">
+ tools:context=".ui.activity.welcome.AgreementActivity">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_feedback.xml b/app/src/main/res/layout/activity_feedback.xml
new file mode 100644
index 0000000..ea8723b
--- /dev/null
+++ b/app/src/main/res/layout/activity_feedback.xml
@@ -0,0 +1,203 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_how_to_use.xml b/app/src/main/res/layout/activity_how_to_use.xml
new file mode 100644
index 0000000..3c14a71
--- /dev/null
+++ b/app/src/main/res/layout/activity_how_to_use.xml
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_launcher.xml b/app/src/main/res/layout/activity_launcher.xml
index b49a1bd..15eec01 100644
--- a/app/src/main/res/layout/activity_launcher.xml
+++ b/app/src/main/res/layout/activity_launcher.xml
@@ -5,6 +5,6 @@
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
- tools:context=".presentation.ui.activity.welcome.LauncherActivity">
+ tools:context=".ui.activity.welcome.LauncherActivity">
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index 02dc5e3..490936c 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -5,27 +5,27 @@
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
- tools:context=".presentation.ui.activity.MainActivity">
+ tools:context=".ui.activity.main.MainActivity">
+ app:layout_constraintTop_toTopOf="@+id/title" />
+ app:layout_constraintTop_toTopOf="@+id/title" />
-
+ app:layout_constraintVertical_bias="0.3" />
-
-
-
-
-
-
-
-
-
+
+ app:layout_constraintTop_toBottomOf="@id/animated_video">
+ tools:context=".ui.activity.menu.MenuActivity">
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_scripts.xml b/app/src/main/res/layout/activity_scripts.xml
index e307b82..828f499 100644
--- a/app/src/main/res/layout/activity_scripts.xml
+++ b/app/src/main/res/layout/activity_scripts.xml
@@ -5,7 +5,7 @@
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
- tools:context=".presentation.ui.activity.menu.ScriptsActivity">
+ tools:context=".ui.activity.menu.ScriptsActivity">
+ tools:context=".ui.activity.setting.SettingActivity">
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_splash.xml b/app/src/main/res/layout/activity_splash.xml
index aa3df98..0b4e0be 100644
--- a/app/src/main/res/layout/activity_splash.xml
+++ b/app/src/main/res/layout/activity_splash.xml
@@ -5,7 +5,7 @@
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
- tools:context=".presentation.ui.activity.welcome.SplashActivity">
+ tools:context=".ui.activity.welcome.SplashActivity">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_action.xml b/app/src/main/res/layout/fragment_action.xml
new file mode 100644
index 0000000..610332f
--- /dev/null
+++ b/app/src/main/res/layout/fragment_action.xml
@@ -0,0 +1,379 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_how_multi.xml b/app/src/main/res/layout/fragment_how_multi.xml
new file mode 100644
index 0000000..d9f524c
--- /dev/null
+++ b/app/src/main/res/layout/fragment_how_multi.xml
@@ -0,0 +1,410 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_how_single.xml b/app/src/main/res/layout/fragment_how_single.xml
new file mode 100644
index 0000000..aeb2a2c
--- /dev/null
+++ b/app/src/main/res/layout/fragment_how_single.xml
@@ -0,0 +1,221 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_u_i_size.xml b/app/src/main/res/layout/fragment_u_i_size.xml
new file mode 100644
index 0000000..a6618c4
--- /dev/null
+++ b/app/src/main/res/layout/fragment_u_i_size.xml
@@ -0,0 +1,146 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/how_tab.xml b/app/src/main/res/layout/how_tab.xml
new file mode 100644
index 0000000..f31ff47
--- /dev/null
+++ b/app/src/main/res/layout/how_tab.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_menu.xml b/app/src/main/res/layout/item_menu.xml
index 3bfabfb..2f7fd6b 100644
--- a/app/src/main/res/layout/item_menu.xml
+++ b/app/src/main/res/layout/item_menu.xml
@@ -1,10 +1,10 @@
diff --git a/app/src/main/res/layout/multiple_control_bar.xml b/app/src/main/res/layout/multiple_control_bar.xml
new file mode 100644
index 0000000..85780cb
--- /dev/null
+++ b/app/src/main/res/layout/multiple_control_bar.xml
@@ -0,0 +1,89 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-hdpi/line_1.png b/app/src/main/res/mipmap-hdpi/line_1.png
new file mode 100644
index 0000000..8c530da
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/line_1.png differ
diff --git a/app/src/main/res/mipmap-hdpi/line_2.png b/app/src/main/res/mipmap-hdpi/line_2.png
new file mode 100644
index 0000000..9f48cae
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/line_2.png differ
diff --git a/app/src/main/res/mipmap-hdpi/multi_control_bar.png b/app/src/main/res/mipmap-hdpi/multi_control_bar.png
new file mode 100644
index 0000000..47b3835
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/multi_control_bar.png differ
diff --git a/app/src/main/res/mipmap-hdpi/single_control_bar.png b/app/src/main/res/mipmap-hdpi/single_control_bar.png
new file mode 100644
index 0000000..20433a7
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/single_control_bar.png differ
diff --git a/app/src/main/res/raw/bolangblue.mp4 b/app/src/main/res/raw/bolangblue.mp4
new file mode 100644
index 0000000..c1872d1
Binary files /dev/null and b/app/src/main/res/raw/bolangblue.mp4 differ
diff --git a/app/src/main/res/raw/bolangred.mp4 b/app/src/main/res/raw/bolangred.mp4
new file mode 100644
index 0000000..e19bc0b
Binary files /dev/null and b/app/src/main/res/raw/bolangred.mp4 differ
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index d91292b..83c7a0d 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -4,4 +4,7 @@
#FFFFFFFF
#0BC4FC
#616161
+ #113945
+ #8DA5A7
+ #446672
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index c9e51c3..9e0e1bb 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -36,5 +36,14 @@ functionalities.
Not Now
Please allow Auto Clicker to use the\nAccessibility Service for app features.
while app is running.
+ 1. Click on \"START\" button to run the service
+ 2. Drag the \"Action Point\" to move it where\nyou want to click
+ 3. Click \"Play\" to start Clicking
+ Enable this permission to run the app in the background under Ultra power saving mode Grant
+ App was forcibly stopped
+
+ Hello blank fragment
+ Preview
+ This service enables auto-clicking on the screen.
\ No newline at end of file
diff --git a/app/src/main/res/xml/accessibility_service_config.xml b/app/src/main/res/xml/accessibility_service_config.xml
new file mode 100644
index 0000000..c2f6170
--- /dev/null
+++ b/app/src/main/res/xml/accessibility_service_config.xml
@@ -0,0 +1,9 @@
+
\ No newline at end of file
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 15a84c8..acf0863 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -1,5 +1,5 @@
[versions]
-agp = "8.10.0"
+agp = "8.10.1"
junit = "4.13.2"
junitVersion = "1.2.1"
espressoCore = "3.6.1"