summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/provider/Settings.java12
-rw-r--r--core/proto/android/providers/settings/secure.proto1
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java1
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java1
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java2
-rw-r--r--services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java2
-rw-r--r--services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java89
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickTypePanelTest.java98
8 files changed, 204 insertions, 2 deletions
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 538283e10738..89f66c010976 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -9345,6 +9345,18 @@ public final class Settings {
"accessibility_autoclick_ignore_minor_cursor_movement";
/**
+ * String setting that stores the position of the autoclick panel when
+ * {@link #ACCESSIBILITY_AUTOCLICK_ENABLED} is set. The position is stored as a
+ * comma-separated string containing gravity, x-coordinate, y-coordinate, and corner index.
+ * For example, "8388659,15,30,0", where 8388659 means gravity Gravity.START | Gravity.TOP.
+ *
+ * @see #ACCESSIBILITY_AUTOCLICK_ENABLED
+ * @hide
+ */
+ public static final String ACCESSIBILITY_AUTOCLICK_PANEL_POSITION =
+ "accessibility_autoclick_panel_position";
+
+ /**
* Whether or not larger size icons are used for the pointer of mouse/trackpad for
* accessibility.
* (0 = false, 1 = true)
diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto
index 69c812c6fb41..34ec1481f697 100644
--- a/core/proto/android/providers/settings/secure.proto
+++ b/core/proto/android/providers/settings/secure.proto
@@ -110,6 +110,7 @@ message SecureSettingsProto {
// Settings for accessibility autoclick
optional SettingProto autoclick_cursor_area_size = 62 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto autoclick_ignore_minor_cursor_movement = 63 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ optional SettingProto autoclick_panel_position = 64 [ (android.privacy).dest = DEST_AUTOMATIC ];
}
optional Accessibility accessibility = 2;
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 60fe15662980..f692601edcb7 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -95,6 +95,7 @@ public class SecureSettings {
Settings.Secure.ACCESSIBILITY_AUTOCLICK_IGNORE_MINOR_CURSOR_MOVEMENT,
Settings.Secure.ACCESSIBILITY_AUTOCLICK_ENABLED,
Settings.Secure.ACCESSIBILITY_AUTOCLICK_DELAY,
+ Settings.Secure.ACCESSIBILITY_AUTOCLICK_PANEL_POSITION,
Settings.Secure.ACCESSIBILITY_LARGE_POINTER_ICON,
Settings.Secure.PREFERRED_TTY_MODE,
Settings.Secure.ENHANCED_VOICE_PRIVACY_ENABLED,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index 3d941e82727f..e42d3fb9da41 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -146,6 +146,7 @@ public class SecureSettingsValidators {
Secure.ACCESSIBILITY_AUTOCLICK_IGNORE_MINOR_CURSOR_MOVEMENT, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.ACCESSIBILITY_AUTOCLICK_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.ACCESSIBILITY_AUTOCLICK_DELAY, NON_NEGATIVE_INTEGER_VALIDATOR);
+ VALIDATORS.put(Secure.ACCESSIBILITY_AUTOCLICK_PANEL_POSITION, ANY_STRING_VALIDATOR);
VALIDATORS.put(Secure.ACCESSIBILITY_LARGE_POINTER_ICON, BOOLEAN_VALIDATOR);
VALIDATORS.put(
Secure.PREFERRED_TTY_MODE,
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index f03a5fb454fa..c29a5a20a96f 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -1727,6 +1727,8 @@ class SettingsProtoDumpUtil {
dumpSetting(s, p,
Settings.Secure.ACCESSIBILITY_AUTOCLICK_DELAY,
SecureSettingsProto.Accessibility.AUTOCLICK_DELAY);
+ dumpSetting(s, p, Settings.Secure.ACCESSIBILITY_AUTOCLICK_PANEL_POSITION,
+ SecureSettingsProto.Accessibility.AUTOCLICK_PANEL_POSITION);
dumpSetting(s, p,
Settings.Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT,
SecureSettingsProto.Accessibility.BUTTON_TARGET_COMPONENT);
diff --git a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java
index aa82df493f84..ca9017408501 100644
--- a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java
+++ b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java
@@ -156,7 +156,7 @@ public class AutoclickController extends BaseEventStreamTransformation {
mWindowManager = mContext.getSystemService(WindowManager.class);
mAutoclickTypePanel =
- new AutoclickTypePanel(mContext, mWindowManager, clickPanelController);
+ new AutoclickTypePanel(mContext, mWindowManager, mUserId, clickPanelController);
mAutoclickTypePanel.show();
mWindowManager.addView(mAutoclickIndicatorView, mAutoclickIndicatorView.getLayoutParams());
diff --git a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java
index 342675a65360..ab4b3b13eece 100644
--- a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java
+++ b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java
@@ -16,6 +16,7 @@
package com.android.server.accessibility.autoclick;
+import static android.provider.Settings.Secure.ACCESSIBILITY_AUTOCLICK_PANEL_POSITION;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
import android.annotation.IntDef;
@@ -23,6 +24,8 @@ import android.content.Context;
import android.graphics.PixelFormat;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
+import android.provider.Settings;
+import android.text.TextUtils;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
@@ -52,6 +55,9 @@ public class AutoclickTypePanel {
public static final int CORNER_TOP_LEFT = 2;
public static final int CORNER_TOP_RIGHT = 3;
+ // Used to remember and restore panel's position.
+ protected static final String POSITION_DELIMITER = ",";
+
// Distance between panel and screen edge.
// TODO(b/396402941): Finalize edge margin.
private static final int PANEL_EDGE_MARGIN = 15;
@@ -112,6 +118,8 @@ public class AutoclickTypePanel {
private final WindowManager mWindowManager;
+ private final int mUserId;
+
private WindowManager.LayoutParams mParams;
private final ClickPanelControllerInterface mClickPanelController;
@@ -142,9 +150,11 @@ public class AutoclickTypePanel {
public AutoclickTypePanel(
Context context,
WindowManager windowManager,
+ int userId,
ClickPanelControllerInterface clickPanelController) {
mContext = context;
mWindowManager = windowManager;
+ mUserId = userId;
mClickPanelController = clickPanelController;
mParams = getDefaultLayoutParams();
@@ -308,6 +318,9 @@ public class AutoclickTypePanel {
}
public void show() {
+ // Restores the panel position from saved settings. If no valid position is saved,
+ // defaults to bottom-right corner.
+ restorePanelPosition();
mWindowManager.addView(mContentView, mParams);
}
@@ -316,6 +329,9 @@ public class AutoclickTypePanel {
// button background styling is correct when the panel shows up next time.
toggleSelectedButtonStyle(mSelectedButton, /* isSelected= */ false);
+ // Save the panel's position when user turns off the autoclick.
+ savePanelPosition();
+
mWindowManager.removeView(mContentView);
}
@@ -424,6 +440,79 @@ public class AutoclickTypePanel {
}
}
+ private void savePanelPosition() {
+ String positionString = TextUtils.join(POSITION_DELIMITER, new String[]{
+ String.valueOf(mParams.gravity),
+ String.valueOf(mParams.x),
+ String.valueOf(mParams.y),
+ String.valueOf(mCurrentCornerIndex)
+ });
+ Settings.Secure.putStringForUser(mContext.getContentResolver(),
+ ACCESSIBILITY_AUTOCLICK_PANEL_POSITION, positionString, mUserId);
+ }
+
+ /**
+ * Restores the panel position from saved settings. If no valid position is saved,
+ * defaults to bottom-right corner.
+ */
+ private void restorePanelPosition() {
+ // Try to get saved position from settings.
+ String savedPosition = Settings.Secure.getStringForUser(mContext.getContentResolver(),
+ ACCESSIBILITY_AUTOCLICK_PANEL_POSITION, mUserId);
+ if (savedPosition == null) {
+ setPanelPositionForCorner(mParams, CORNER_BOTTOM_RIGHT);
+ mCurrentCornerIndex = 0;
+ return;
+ }
+
+ // Parse saved position string in "gravity,x,y,corner" format.
+ String[] parts = TextUtils.split(savedPosition, POSITION_DELIMITER);
+ if (!isValidPositionParts(parts)) {
+ setPanelPositionForCorner(mParams, CORNER_BOTTOM_RIGHT);
+ mCurrentCornerIndex = 0;
+ return;
+ }
+
+ // Restore the saved position values.
+ mParams.gravity = Integer.parseInt(parts[0]);
+ mParams.x = Integer.parseInt(parts[1]);
+ mParams.y = Integer.parseInt(parts[2]);
+ mCurrentCornerIndex = Integer.parseInt(parts[3]);
+ }
+
+ private boolean isValidPositionParts(String[] parts) {
+ // Check basic array validity.
+ if (parts == null || parts.length != 4) {
+ return false;
+ }
+
+ // Parse values after validating they are numbers.
+ int gravity = Integer.parseInt(parts[0]);
+ int x = Integer.parseInt(parts[1]);
+ int y = Integer.parseInt(parts[2]);
+ int cornerIndex = Integer.parseInt(parts[3]);
+
+ // Check gravity is valid (START/END | TOP/BOTTOM).
+ if (gravity != (Gravity.START | Gravity.TOP) && gravity != (Gravity.END | Gravity.TOP)
+ && gravity != (Gravity.START | Gravity.BOTTOM) && gravity != (Gravity.END
+ | Gravity.BOTTOM)) {
+ return false;
+ }
+
+ // Check coordinates are positive and within screen bounds.
+ int screenWidth = mContext.getResources().getDisplayMetrics().widthPixels;
+ int screenHeight = mContext.getResources().getDisplayMetrics().heightPixels;
+ if (x < 0 || x > screenWidth || y < 0 || y > screenHeight) {
+ return false;
+ }
+
+ // Check corner index is valid.
+ if (cornerIndex < 0 || cornerIndex >= 4) {
+ return false;
+ }
+ return true;
+ }
+
@VisibleForTesting
boolean getExpansionStateForTesting() {
return mExpanded;
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickTypePanelTest.java b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickTypePanelTest.java
index c60c4b60d081..9e123406dff5 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickTypePanelTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickTypePanelTest.java
@@ -16,6 +16,8 @@
package com.android.server.accessibility.autoclick;
+import static android.provider.Settings.Secure.ACCESSIBILITY_AUTOCLICK_PANEL_POSITION;
+
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static com.android.server.accessibility.autoclick.AutoclickTypePanel.AUTOCLICK_TYPE_LEFT_CLICK;
@@ -23,13 +25,16 @@ import static com.android.server.accessibility.autoclick.AutoclickTypePanel.AUTO
import static com.android.server.accessibility.autoclick.AutoclickTypePanel.AutoclickType;
import static com.android.server.accessibility.autoclick.AutoclickTypePanel.CORNER_BOTTOM_LEFT;
import static com.android.server.accessibility.autoclick.AutoclickTypePanel.CORNER_BOTTOM_RIGHT;
+import static com.android.server.accessibility.autoclick.AutoclickTypePanel.CORNER_TOP_LEFT;
import static com.android.server.accessibility.autoclick.AutoclickTypePanel.CORNER_TOP_RIGHT;
import static com.android.server.accessibility.autoclick.AutoclickTypePanel.ClickPanelControllerInterface;
+import static com.android.server.accessibility.autoclick.AutoclickTypePanel.POSITION_DELIMITER;
import static com.google.common.truth.Truth.assertThat;
import android.content.Context;
import android.graphics.drawable.GradientDrawable;
+import android.provider.Settings;
import android.testing.AndroidTestingRunner;
import android.testing.TestableContext;
import android.testing.TestableLooper;
@@ -92,7 +97,8 @@ public class AutoclickTypePanelTest {
mTestableContext.addMockSystemService(Context.WINDOW_SERVICE, mMockWindowManager);
mAutoclickTypePanel =
- new AutoclickTypePanel(mTestableContext, mMockWindowManager, clickPanelController);
+ new AutoclickTypePanel(mTestableContext, mMockWindowManager,
+ mTestableContext.getUserId(), clickPanelController);
View contentView = mAutoclickTypePanel.getContentViewForTesting();
mLeftClickButton = contentView.findViewById(R.id.accessibility_autoclick_left_click_layout);
mRightClickButton =
@@ -294,6 +300,96 @@ public class AutoclickTypePanelTest {
.isEqualTo(CORNER_BOTTOM_LEFT);
}
+ @Test
+ public void restorePanelPosition_noSavedPosition_useDefault() {
+ // Given no saved position in Settings.
+ Settings.Secure.putString(mTestableContext.getContentResolver(),
+ ACCESSIBILITY_AUTOCLICK_PANEL_POSITION, null);
+
+ // Create panel which triggers position restoration internally.
+ AutoclickTypePanel panel = new AutoclickTypePanel(mTestableContext, mMockWindowManager,
+ mTestableContext.getUserId(),
+ clickPanelController);
+
+ // Verify panel is positioned at default bottom-right corner.
+ WindowManager.LayoutParams params = panel.getLayoutParamsForTesting();
+ assertThat(panel.getCurrentCornerIndexForTesting()).isEqualTo(CORNER_BOTTOM_RIGHT);
+ assertThat(params.gravity).isEqualTo(Gravity.END | Gravity.BOTTOM);
+ assertThat(params.x).isEqualTo(15); // Default edge margin.
+ assertThat(params.y).isEqualTo(90); // Default bottom offset.
+ }
+
+ @Test
+ public void restorePanelPosition_position_button() {
+ // Move panel to top-left by clicking position button twice.
+ mPositionButton.callOnClick();
+ mPositionButton.callOnClick();
+
+ // Hide panel to trigger position saving.
+ mAutoclickTypePanel.hide();
+
+ // Verify position is correctly saved in Settings.
+ String savedPosition = Settings.Secure.getStringForUser(
+ mTestableContext.getContentResolver(),
+ ACCESSIBILITY_AUTOCLICK_PANEL_POSITION, mTestableContext.getUserId());
+ String[] parts = savedPosition.split(POSITION_DELIMITER);
+ assertThat(parts).hasLength(4);
+ assertThat(Integer.parseInt(parts[0])).isEqualTo(Gravity.START | Gravity.TOP);
+ assertThat(Integer.parseInt(parts[1])).isEqualTo(15);
+ assertThat(Integer.parseInt(parts[2])).isEqualTo(30);
+ assertThat(Integer.parseInt(parts[3])).isEqualTo(CORNER_TOP_LEFT);
+
+ // Show panel to trigger position restoration.
+ mAutoclickTypePanel.show();
+
+ // Then verify position is restored correctly.
+ WindowManager.LayoutParams params = mAutoclickTypePanel.getLayoutParamsForTesting();
+ assertThat(params.gravity).isEqualTo(Gravity.START | Gravity.TOP);
+ assertThat(params.x).isEqualTo(15);
+ assertThat(params.y).isEqualTo(30);
+ assertThat(mAutoclickTypePanel.getCurrentCornerIndexForTesting()).isEqualTo(
+ CORNER_TOP_LEFT);
+ }
+
+ @Test
+ public void restorePanelPosition_dragToLeft() {
+ // Get initial panel position.
+ View contentView = mAutoclickTypePanel.getContentViewForTesting();
+ int[] panelLocation = new int[2];
+ contentView.getLocationOnScreen(panelLocation);
+
+ // Simulate drag from initial position to left side of screen.
+ int screenWidth = mTestableContext.getResources().getDisplayMetrics().widthPixels;
+ dispatchDragSequence(contentView,
+ /* startX =*/ panelLocation[0], /* startY =*/ panelLocation[1],
+ /* endX =*/ (float) screenWidth / 4, /* endY =*/ panelLocation[1] + 10);
+
+ // Hide panel to trigger position saving.
+ mAutoclickTypePanel.hide();
+
+ // Verify position is saved correctly.
+ String savedPosition = Settings.Secure.getStringForUser(
+ mTestableContext.getContentResolver(),
+ ACCESSIBILITY_AUTOCLICK_PANEL_POSITION, mTestableContext.getUserId());
+ String[] parts = savedPosition.split(POSITION_DELIMITER);
+ assertThat(parts).hasLength(4);
+ assertThat(Integer.parseInt(parts[0])).isEqualTo(Gravity.START | Gravity.TOP);
+ assertThat(Integer.parseInt(parts[1])).isEqualTo(15);
+ assertThat(Integer.parseInt(parts[2])).isEqualTo(panelLocation[1] + 10);
+ assertThat(Integer.parseInt(parts[3])).isEqualTo(CORNER_BOTTOM_LEFT);
+
+ // Show panel to trigger position restoration.
+ mAutoclickTypePanel.show();
+
+ // Then verify dragged position is restored.
+ WindowManager.LayoutParams params = mAutoclickTypePanel.getLayoutParamsForTesting();
+ assertThat(params.gravity).isEqualTo(Gravity.START | Gravity.TOP);
+ assertThat(params.x).isEqualTo(15); // PANEL_EDGE_MARGIN
+ assertThat(params.y).isEqualTo(panelLocation[1] + 10);
+ assertThat(mAutoclickTypePanel.getCurrentCornerIndexForTesting()).isEqualTo(
+ CORNER_BOTTOM_LEFT);
+ }
+
// Helper method to handle drag event sequences
private void dispatchDragSequence(View view, float startX, float startY, float endX,
float endY) {