Loading services/accessibility/java/com/android/server/accessibility/AutoclickController.java +105 −0 Original line number Diff line number Diff line Loading @@ -16,11 +16,14 @@ package com.android.server.accessibility; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; import android.accessibilityservice.AccessibilityTrace; import android.annotation.NonNull; import android.content.ContentResolver; import android.content.Context; import android.database.ContentObserver; import android.graphics.PixelFormat; import android.net.Uri; import android.os.Handler; import android.os.SystemClock; Loading @@ -30,6 +33,7 @@ import android.view.KeyEvent; import android.view.MotionEvent; import android.view.MotionEvent.PointerCoords; import android.view.MotionEvent.PointerProperties; import android.view.WindowManager; import android.view.accessibility.AccessibilityManager; /** Loading Loading @@ -64,6 +68,9 @@ public class AutoclickController extends BaseEventStreamTransformation { // Lazily created on the first mouse motion event. private ClickScheduler mClickScheduler; private ClickDelayObserver mClickDelayObserver; private AutoclickIndicatorScheduler mAutoclickIndicatorScheduler; private AutoclickIndicatorView mAutoclickIndicatorView; private WindowManager mWindowManager; public AutoclickController(Context context, int userId, AccessibilityTraceManager trace) { mTrace = trace; Loading @@ -84,6 +91,10 @@ public class AutoclickController extends BaseEventStreamTransformation { new ClickScheduler(handler, AccessibilityManager.AUTOCLICK_DELAY_DEFAULT); mClickDelayObserver = new ClickDelayObserver(mUserId, handler); mClickDelayObserver.start(mContext.getContentResolver(), mClickScheduler); if (Flags.enableAutoclickIndicator()) { initiateAutoclickIndicator(handler); } } handleMouseMotion(event, policyFlags); Loading @@ -94,6 +105,27 @@ public class AutoclickController extends BaseEventStreamTransformation { super.onMotionEvent(event, rawEvent, policyFlags); } private void initiateAutoclickIndicator(Handler handler) { mAutoclickIndicatorScheduler = new AutoclickIndicatorScheduler(handler); mAutoclickIndicatorView = new AutoclickIndicatorView(mContext); final WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(); layoutParams.type = WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY; layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; layoutParams.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS; layoutParams.setFitInsetsTypes(0); layoutParams.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; layoutParams.format = PixelFormat.TRANSLUCENT; layoutParams.setTitle(AutoclickIndicatorView.class.getSimpleName()); layoutParams.inputFeatures |= WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL; mWindowManager = mContext.getSystemService(WindowManager.class); mWindowManager.addView(mAutoclickIndicatorView, layoutParams); } @Override public void onKeyEvent(KeyEvent event, int policyFlags) { if (mTrace.isA11yTracingEnabledForTypes(AccessibilityTrace.FLAGS_INPUT_FILTER)) { Loading Loading @@ -130,6 +162,12 @@ public class AutoclickController extends BaseEventStreamTransformation { mClickScheduler.cancel(); mClickScheduler = null; } if (mAutoclickIndicatorScheduler != null) { mAutoclickIndicatorScheduler.cancel(); mAutoclickIndicatorScheduler = null; mWindowManager.removeView(mAutoclickIndicatorView); } } private void handleMouseMotion(MotionEvent event, int policyFlags) { Loading Loading @@ -225,6 +263,62 @@ public class AutoclickController extends BaseEventStreamTransformation { } } private final class AutoclickIndicatorScheduler implements Runnable { private final Handler mHandler; private long mScheduledShowIndicatorTime; private boolean mIndicatorCallbackActive = false; public AutoclickIndicatorScheduler(Handler handler) { mHandler = handler; } @Override public void run() { long now = SystemClock.uptimeMillis(); // Indicator was rescheduled after task was posted. Post new run task at updated time. if (now < mScheduledShowIndicatorTime) { mHandler.postDelayed(this, mScheduledShowIndicatorTime - now); return; } mAutoclickIndicatorView.redrawIndicator(); mIndicatorCallbackActive = false; } public void update() { // TODO(b/383901288): update delay time once determined by UX. long SHOW_INDICATOR_DELAY_TIME = 150; long scheduledShowIndicatorTime = SystemClock.uptimeMillis() + SHOW_INDICATOR_DELAY_TIME; // If there already is a scheduled show indicator at time before the updated time, just // update scheduled time. if (mIndicatorCallbackActive && scheduledShowIndicatorTime > mScheduledShowIndicatorTime) { mScheduledShowIndicatorTime = scheduledShowIndicatorTime; return; } if (mIndicatorCallbackActive) { mHandler.removeCallbacks(this); } mIndicatorCallbackActive = true; mScheduledShowIndicatorTime = scheduledShowIndicatorTime; mHandler.postDelayed(this, SHOW_INDICATOR_DELAY_TIME); } public void cancel() { if (!mIndicatorCallbackActive) { return; } mIndicatorCallbackActive = false; mScheduledShowIndicatorTime = -1; mHandler.removeCallbacks(this); } } /** * Schedules and performs click event sequence that should be initiated when mouse pointer stops * moving. The click is first scheduled when a mouse movement is detected, and then further Loading Loading @@ -305,6 +399,13 @@ public class AutoclickController extends BaseEventStreamTransformation { if (moved) { rescheduleClick(mDelay); if (Flags.enableAutoclickIndicator()) { final int pointerIndex = event.getActionIndex(); mAutoclickIndicatorView.setCoordination( event.getX(pointerIndex), event.getY(pointerIndex)); mAutoclickIndicatorScheduler.update(); } } } Loading Loading @@ -385,6 +486,10 @@ public class AutoclickController extends BaseEventStreamTransformation { mLastMotionEvent = null; } mScheduledClickTime = -1; if (Flags.enableAutoclickIndicator() && mAutoclickIndicatorView != null) { mAutoclickIndicatorView.clearIndicator(); } } /** Loading services/accessibility/java/com/android/server/accessibility/AutoclickIndicatorView.java 0 → 100644 +84 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.accessibility; import android.content.Context; import android.graphics.Canvas; import android.graphics.Paint; import android.util.DisplayMetrics; import android.view.View; // A visual indicator for the autoclick feature. public class AutoclickIndicatorView extends View { private static final String TAG = AutoclickIndicatorView.class.getSimpleName(); // TODO(b/383901288): allow users to customize the indicator area. static final float RADIUS = 50; private final Paint mPaint; // x and y coordinates of the visual indicator. private float mX; private float mY; // Status of whether the visual indicator should display or not. private boolean showIndicator = false; public AutoclickIndicatorView(Context context) { super(context); mPaint = new Paint(); // TODO(b/383901288): update styling once determined by UX. mPaint.setARGB(255, 52, 103, 235); mPaint.setStyle(Paint.Style.STROKE); mPaint.setStrokeWidth(10); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if (showIndicator) { canvas.drawCircle(mX, mY, RADIUS, mPaint); } } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // Get the screen dimensions. DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); int screenWidth = displayMetrics.widthPixels; int screenHeight = displayMetrics.heightPixels; setMeasuredDimension(screenWidth, screenHeight); } public void setCoordination(float x, float y) { mX = x; mY = y; } public void redrawIndicator() { showIndicator = true; invalidate(); } public void clearIndicator() { showIndicator = false; invalidate(); } } Loading
services/accessibility/java/com/android/server/accessibility/AutoclickController.java +105 −0 Original line number Diff line number Diff line Loading @@ -16,11 +16,14 @@ package com.android.server.accessibility; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; import android.accessibilityservice.AccessibilityTrace; import android.annotation.NonNull; import android.content.ContentResolver; import android.content.Context; import android.database.ContentObserver; import android.graphics.PixelFormat; import android.net.Uri; import android.os.Handler; import android.os.SystemClock; Loading @@ -30,6 +33,7 @@ import android.view.KeyEvent; import android.view.MotionEvent; import android.view.MotionEvent.PointerCoords; import android.view.MotionEvent.PointerProperties; import android.view.WindowManager; import android.view.accessibility.AccessibilityManager; /** Loading Loading @@ -64,6 +68,9 @@ public class AutoclickController extends BaseEventStreamTransformation { // Lazily created on the first mouse motion event. private ClickScheduler mClickScheduler; private ClickDelayObserver mClickDelayObserver; private AutoclickIndicatorScheduler mAutoclickIndicatorScheduler; private AutoclickIndicatorView mAutoclickIndicatorView; private WindowManager mWindowManager; public AutoclickController(Context context, int userId, AccessibilityTraceManager trace) { mTrace = trace; Loading @@ -84,6 +91,10 @@ public class AutoclickController extends BaseEventStreamTransformation { new ClickScheduler(handler, AccessibilityManager.AUTOCLICK_DELAY_DEFAULT); mClickDelayObserver = new ClickDelayObserver(mUserId, handler); mClickDelayObserver.start(mContext.getContentResolver(), mClickScheduler); if (Flags.enableAutoclickIndicator()) { initiateAutoclickIndicator(handler); } } handleMouseMotion(event, policyFlags); Loading @@ -94,6 +105,27 @@ public class AutoclickController extends BaseEventStreamTransformation { super.onMotionEvent(event, rawEvent, policyFlags); } private void initiateAutoclickIndicator(Handler handler) { mAutoclickIndicatorScheduler = new AutoclickIndicatorScheduler(handler); mAutoclickIndicatorView = new AutoclickIndicatorView(mContext); final WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(); layoutParams.type = WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY; layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; layoutParams.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS; layoutParams.setFitInsetsTypes(0); layoutParams.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; layoutParams.format = PixelFormat.TRANSLUCENT; layoutParams.setTitle(AutoclickIndicatorView.class.getSimpleName()); layoutParams.inputFeatures |= WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL; mWindowManager = mContext.getSystemService(WindowManager.class); mWindowManager.addView(mAutoclickIndicatorView, layoutParams); } @Override public void onKeyEvent(KeyEvent event, int policyFlags) { if (mTrace.isA11yTracingEnabledForTypes(AccessibilityTrace.FLAGS_INPUT_FILTER)) { Loading Loading @@ -130,6 +162,12 @@ public class AutoclickController extends BaseEventStreamTransformation { mClickScheduler.cancel(); mClickScheduler = null; } if (mAutoclickIndicatorScheduler != null) { mAutoclickIndicatorScheduler.cancel(); mAutoclickIndicatorScheduler = null; mWindowManager.removeView(mAutoclickIndicatorView); } } private void handleMouseMotion(MotionEvent event, int policyFlags) { Loading Loading @@ -225,6 +263,62 @@ public class AutoclickController extends BaseEventStreamTransformation { } } private final class AutoclickIndicatorScheduler implements Runnable { private final Handler mHandler; private long mScheduledShowIndicatorTime; private boolean mIndicatorCallbackActive = false; public AutoclickIndicatorScheduler(Handler handler) { mHandler = handler; } @Override public void run() { long now = SystemClock.uptimeMillis(); // Indicator was rescheduled after task was posted. Post new run task at updated time. if (now < mScheduledShowIndicatorTime) { mHandler.postDelayed(this, mScheduledShowIndicatorTime - now); return; } mAutoclickIndicatorView.redrawIndicator(); mIndicatorCallbackActive = false; } public void update() { // TODO(b/383901288): update delay time once determined by UX. long SHOW_INDICATOR_DELAY_TIME = 150; long scheduledShowIndicatorTime = SystemClock.uptimeMillis() + SHOW_INDICATOR_DELAY_TIME; // If there already is a scheduled show indicator at time before the updated time, just // update scheduled time. if (mIndicatorCallbackActive && scheduledShowIndicatorTime > mScheduledShowIndicatorTime) { mScheduledShowIndicatorTime = scheduledShowIndicatorTime; return; } if (mIndicatorCallbackActive) { mHandler.removeCallbacks(this); } mIndicatorCallbackActive = true; mScheduledShowIndicatorTime = scheduledShowIndicatorTime; mHandler.postDelayed(this, SHOW_INDICATOR_DELAY_TIME); } public void cancel() { if (!mIndicatorCallbackActive) { return; } mIndicatorCallbackActive = false; mScheduledShowIndicatorTime = -1; mHandler.removeCallbacks(this); } } /** * Schedules and performs click event sequence that should be initiated when mouse pointer stops * moving. The click is first scheduled when a mouse movement is detected, and then further Loading Loading @@ -305,6 +399,13 @@ public class AutoclickController extends BaseEventStreamTransformation { if (moved) { rescheduleClick(mDelay); if (Flags.enableAutoclickIndicator()) { final int pointerIndex = event.getActionIndex(); mAutoclickIndicatorView.setCoordination( event.getX(pointerIndex), event.getY(pointerIndex)); mAutoclickIndicatorScheduler.update(); } } } Loading Loading @@ -385,6 +486,10 @@ public class AutoclickController extends BaseEventStreamTransformation { mLastMotionEvent = null; } mScheduledClickTime = -1; if (Flags.enableAutoclickIndicator() && mAutoclickIndicatorView != null) { mAutoclickIndicatorView.clearIndicator(); } } /** Loading
services/accessibility/java/com/android/server/accessibility/AutoclickIndicatorView.java 0 → 100644 +84 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.accessibility; import android.content.Context; import android.graphics.Canvas; import android.graphics.Paint; import android.util.DisplayMetrics; import android.view.View; // A visual indicator for the autoclick feature. public class AutoclickIndicatorView extends View { private static final String TAG = AutoclickIndicatorView.class.getSimpleName(); // TODO(b/383901288): allow users to customize the indicator area. static final float RADIUS = 50; private final Paint mPaint; // x and y coordinates of the visual indicator. private float mX; private float mY; // Status of whether the visual indicator should display or not. private boolean showIndicator = false; public AutoclickIndicatorView(Context context) { super(context); mPaint = new Paint(); // TODO(b/383901288): update styling once determined by UX. mPaint.setARGB(255, 52, 103, 235); mPaint.setStyle(Paint.Style.STROKE); mPaint.setStrokeWidth(10); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if (showIndicator) { canvas.drawCircle(mX, mY, RADIUS, mPaint); } } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // Get the screen dimensions. DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); int screenWidth = displayMetrics.widthPixels; int screenHeight = displayMetrics.heightPixels; setMeasuredDimension(screenWidth, screenHeight); } public void setCoordination(float x, float y) { mX = x; mY = y; } public void redrawIndicator() { showIndicator = true; invalidate(); } public void clearIndicator() { showIndicator = false; invalidate(); } }