Loading services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickScrollPanel.java +4 −0 Original line number Diff line number Diff line Loading @@ -69,6 +69,7 @@ public class AutoclickScrollPanel { private final WindowManager mWindowManager; private final WindowManager.LayoutParams mParams; private ScrollPanelControllerInterface mScrollPanelController; private final AutoclickScrollPointIndicator mAutoclickScrollPointIndicator; // Scroll panel buttons. private final ImageButton mUpButton; Loading Loading @@ -101,6 +102,7 @@ public class AutoclickScrollPanel { mContext = context; mWindowManager = windowManager; mScrollPanelController = controller; mAutoclickScrollPointIndicator = new AutoclickScrollPointIndicator(context); mContentView = (AutoclickLinearLayout) LayoutInflater.from(context).inflate( R.layout.accessibility_autoclick_scroll_panel, null); mParams = getDefaultLayoutParams(); Loading Loading @@ -160,6 +162,7 @@ public class AutoclickScrollPanel { } // Position the panel at the cursor location positionPanelAtCursor(cursorX, cursorY); mAutoclickScrollPointIndicator.show(cursorX, cursorY); mWindowManager.addView(mContentView, mParams); mInScrollMode = true; } Loading Loading @@ -205,6 +208,7 @@ public class AutoclickScrollPanel { if (!mInScrollMode) { return; } mAutoclickScrollPointIndicator.hide(); mWindowManager.removeView(mContentView); mInScrollMode = false; } Loading services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickScrollPointIndicator.java 0 → 100644 +135 −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.autoclick; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; import android.content.Context; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.PixelFormat; import android.util.DisplayMetrics; import android.view.View; import android.view.WindowManager; import androidx.annotation.VisibleForTesting; import com.android.internal.R; /** * A visual indicator that displays a point at the scroll cursor location. */ public class AutoclickScrollPointIndicator extends View { // 16dp diameter (8dp radius). private static final float POINT_RADIUS_DP = 8f; private final WindowManager mWindowManager; private final Paint mPaint; private final float mPointSizePx; // x and y coordinates of the cursor point indicator. private float mX; private float mY; private boolean mIsVisible = false; public AutoclickScrollPointIndicator(Context context) { super(context); mWindowManager = context.getSystemService(WindowManager.class); // Convert dp to pixels based on screen density. float density = getResources().getDisplayMetrics().density; mPointSizePx = POINT_RADIUS_DP * density; // Setup paint for drawing. mPaint = new Paint(); mPaint.setAntiAlias(true); } @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); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); // Draw a solid circle with materialColorPrimary. mPaint.setStyle(Paint.Style.FILL); mPaint.setColor(getContext().getColor(R.color.materialColorPrimary)); canvas.drawCircle(mX, mY, mPointSizePx, mPaint); } /** * Shows the cursor point indicator at the specified coordinates. * * @param x The x-coordinate of the cursor. * @param y The y-coordinate of the cursor. */ public void show(float x, float y) { mX = x; mY = y; if (!mIsVisible) { mWindowManager.addView(this, getLayoutParams()); mIsVisible = true; } invalidate(); } /** * Hides the cursor point indicator. */ public void hide() { if (mIsVisible) { mWindowManager.removeView(this); mIsVisible = false; } } /** * Retrieves the layout params for AutoclickScrollPointIndicator, used when it's added to the * Window Manager. */ public WindowManager.LayoutParams getLayoutParams() { final WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(); layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; layoutParams.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS; layoutParams.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; layoutParams.format = PixelFormat.TRANSLUCENT; layoutParams.setTitle(AutoclickScrollPointIndicator.class.getSimpleName()); layoutParams.inputFeatures |= WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL; return layoutParams; } @VisibleForTesting public boolean isVisible() { return mIsVisible; } } services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickScrollPanelTest.java +4 −3 Original line number Diff line number Diff line Loading @@ -241,8 +241,8 @@ public class AutoclickScrollPanelTest { mScrollPanel.show(cursorX, cursorY); // Verify view is added to window manager. verify(mMockWindowManager).addView(any(), any(WindowManager.LayoutParams.class)); verify(mMockWindowManager).addView(eq(mScrollPanel.getContentViewForTesting()), any(WindowManager.LayoutParams.class)); // Verify panel is visible. assertThat(mScrollPanel.isVisible()).isTrue(); } Loading @@ -268,7 +268,8 @@ public class AutoclickScrollPanelTest { assertThat(mScrollPanel.isVisible()).isTrue(); // Verify view was added twice to window manager. verify(mMockWindowManager, times(2)).addView(any(), any(WindowManager.LayoutParams.class)); verify(mMockWindowManager, times(2)).addView(eq(mScrollPanel.getContentViewForTesting()), any(WindowManager.LayoutParams.class)); } @Test Loading services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickScrollPointIndicatorTest.java 0 → 100644 +114 −0 Original line number Diff line number Diff line /* * Copyright 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.autoclick; import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import android.content.Context; import android.testing.AndroidTestingRunner; import android.testing.TestableContext; import android.testing.TestableLooper; import android.view.WindowManager; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; /** Test cases for {@link AutoclickScrollPointIndicator}. */ @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper(setAsMainLooper = true) public class AutoclickScrollPointIndicatorTest { @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule(); @Rule public TestableContext mTestableContext = new TestableContext(getInstrumentation().getContext()); @Mock private WindowManager mMockWindowManager; private AutoclickScrollPointIndicator mPointIndicator; @Before public void setUp() { mTestableContext.addMockSystemService(Context.WINDOW_SERVICE, mMockWindowManager); mPointIndicator = new AutoclickScrollPointIndicator(mTestableContext); } @Test public void initialState_isNotVisible() { assertThat(mPointIndicator.isVisible()).isFalse(); } @Test public void show_addsViewToWindowManager() { float testX = 100.0f; float testY = 200.0f; mPointIndicator.show(testX, testY); // Verify view is added to window manager. verify(mMockWindowManager).addView(eq(mPointIndicator), any(WindowManager.LayoutParams.class)); // Verify isVisible reflects correct state. assertThat(mPointIndicator.isVisible()).isTrue(); } @Test public void show_alreadyVisible_doesNotAddAgain() { float testX = 100.0f; float testY = 200.0f; // Show twice. mPointIndicator.show(testX, testY); mPointIndicator.show(testX, testY); // Verify addView was only called once. verify(mMockWindowManager, times(1)).addView(any(), any()); } @Test public void hide_removesViewFromWindowManager() { float testX = 100.0f; float testY = 200.0f; // First show the indicator. mPointIndicator.show(testX, testY); // Then hide it. mPointIndicator.hide(); // Verify view is removed from window manager. verify(mMockWindowManager).removeView(eq(mPointIndicator)); // Verify indicator is hidden. assertThat(mPointIndicator.isVisible()).isFalse(); } } Loading
services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickScrollPanel.java +4 −0 Original line number Diff line number Diff line Loading @@ -69,6 +69,7 @@ public class AutoclickScrollPanel { private final WindowManager mWindowManager; private final WindowManager.LayoutParams mParams; private ScrollPanelControllerInterface mScrollPanelController; private final AutoclickScrollPointIndicator mAutoclickScrollPointIndicator; // Scroll panel buttons. private final ImageButton mUpButton; Loading Loading @@ -101,6 +102,7 @@ public class AutoclickScrollPanel { mContext = context; mWindowManager = windowManager; mScrollPanelController = controller; mAutoclickScrollPointIndicator = new AutoclickScrollPointIndicator(context); mContentView = (AutoclickLinearLayout) LayoutInflater.from(context).inflate( R.layout.accessibility_autoclick_scroll_panel, null); mParams = getDefaultLayoutParams(); Loading Loading @@ -160,6 +162,7 @@ public class AutoclickScrollPanel { } // Position the panel at the cursor location positionPanelAtCursor(cursorX, cursorY); mAutoclickScrollPointIndicator.show(cursorX, cursorY); mWindowManager.addView(mContentView, mParams); mInScrollMode = true; } Loading Loading @@ -205,6 +208,7 @@ public class AutoclickScrollPanel { if (!mInScrollMode) { return; } mAutoclickScrollPointIndicator.hide(); mWindowManager.removeView(mContentView); mInScrollMode = false; } Loading
services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickScrollPointIndicator.java 0 → 100644 +135 −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.autoclick; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; import android.content.Context; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.PixelFormat; import android.util.DisplayMetrics; import android.view.View; import android.view.WindowManager; import androidx.annotation.VisibleForTesting; import com.android.internal.R; /** * A visual indicator that displays a point at the scroll cursor location. */ public class AutoclickScrollPointIndicator extends View { // 16dp diameter (8dp radius). private static final float POINT_RADIUS_DP = 8f; private final WindowManager mWindowManager; private final Paint mPaint; private final float mPointSizePx; // x and y coordinates of the cursor point indicator. private float mX; private float mY; private boolean mIsVisible = false; public AutoclickScrollPointIndicator(Context context) { super(context); mWindowManager = context.getSystemService(WindowManager.class); // Convert dp to pixels based on screen density. float density = getResources().getDisplayMetrics().density; mPointSizePx = POINT_RADIUS_DP * density; // Setup paint for drawing. mPaint = new Paint(); mPaint.setAntiAlias(true); } @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); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); // Draw a solid circle with materialColorPrimary. mPaint.setStyle(Paint.Style.FILL); mPaint.setColor(getContext().getColor(R.color.materialColorPrimary)); canvas.drawCircle(mX, mY, mPointSizePx, mPaint); } /** * Shows the cursor point indicator at the specified coordinates. * * @param x The x-coordinate of the cursor. * @param y The y-coordinate of the cursor. */ public void show(float x, float y) { mX = x; mY = y; if (!mIsVisible) { mWindowManager.addView(this, getLayoutParams()); mIsVisible = true; } invalidate(); } /** * Hides the cursor point indicator. */ public void hide() { if (mIsVisible) { mWindowManager.removeView(this); mIsVisible = false; } } /** * Retrieves the layout params for AutoclickScrollPointIndicator, used when it's added to the * Window Manager. */ public WindowManager.LayoutParams getLayoutParams() { final WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(); layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; layoutParams.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS; layoutParams.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; layoutParams.format = PixelFormat.TRANSLUCENT; layoutParams.setTitle(AutoclickScrollPointIndicator.class.getSimpleName()); layoutParams.inputFeatures |= WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL; return layoutParams; } @VisibleForTesting public boolean isVisible() { return mIsVisible; } }
services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickScrollPanelTest.java +4 −3 Original line number Diff line number Diff line Loading @@ -241,8 +241,8 @@ public class AutoclickScrollPanelTest { mScrollPanel.show(cursorX, cursorY); // Verify view is added to window manager. verify(mMockWindowManager).addView(any(), any(WindowManager.LayoutParams.class)); verify(mMockWindowManager).addView(eq(mScrollPanel.getContentViewForTesting()), any(WindowManager.LayoutParams.class)); // Verify panel is visible. assertThat(mScrollPanel.isVisible()).isTrue(); } Loading @@ -268,7 +268,8 @@ public class AutoclickScrollPanelTest { assertThat(mScrollPanel.isVisible()).isTrue(); // Verify view was added twice to window manager. verify(mMockWindowManager, times(2)).addView(any(), any(WindowManager.LayoutParams.class)); verify(mMockWindowManager, times(2)).addView(eq(mScrollPanel.getContentViewForTesting()), any(WindowManager.LayoutParams.class)); } @Test Loading
services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickScrollPointIndicatorTest.java 0 → 100644 +114 −0 Original line number Diff line number Diff line /* * Copyright 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.autoclick; import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import android.content.Context; import android.testing.AndroidTestingRunner; import android.testing.TestableContext; import android.testing.TestableLooper; import android.view.WindowManager; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; /** Test cases for {@link AutoclickScrollPointIndicator}. */ @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper(setAsMainLooper = true) public class AutoclickScrollPointIndicatorTest { @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule(); @Rule public TestableContext mTestableContext = new TestableContext(getInstrumentation().getContext()); @Mock private WindowManager mMockWindowManager; private AutoclickScrollPointIndicator mPointIndicator; @Before public void setUp() { mTestableContext.addMockSystemService(Context.WINDOW_SERVICE, mMockWindowManager); mPointIndicator = new AutoclickScrollPointIndicator(mTestableContext); } @Test public void initialState_isNotVisible() { assertThat(mPointIndicator.isVisible()).isFalse(); } @Test public void show_addsViewToWindowManager() { float testX = 100.0f; float testY = 200.0f; mPointIndicator.show(testX, testY); // Verify view is added to window manager. verify(mMockWindowManager).addView(eq(mPointIndicator), any(WindowManager.LayoutParams.class)); // Verify isVisible reflects correct state. assertThat(mPointIndicator.isVisible()).isTrue(); } @Test public void show_alreadyVisible_doesNotAddAgain() { float testX = 100.0f; float testY = 200.0f; // Show twice. mPointIndicator.show(testX, testY); mPointIndicator.show(testX, testY); // Verify addView was only called once. verify(mMockWindowManager, times(1)).addView(any(), any()); } @Test public void hide_removesViewFromWindowManager() { float testX = 100.0f; float testY = 200.0f; // First show the indicator. mPointIndicator.show(testX, testY); // Then hide it. mPointIndicator.hide(); // Verify view is removed from window manager. verify(mMockWindowManager).removeView(eq(mPointIndicator)); // Verify indicator is hidden. assertThat(mPointIndicator.isVisible()).isFalse(); } }