diff --git a/res/drawable/pointer_icon_fill_style_background.xml b/res/drawable/pointer_icon_fill_style_background.xml
new file mode 100644
index 00000000000..1e98cf5c17f
--- /dev/null
+++ b/res/drawable/pointer_icon_fill_style_background.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
diff --git a/res/layout/pointer_icon_fill_style_layout.xml b/res/layout/pointer_icon_fill_style_layout.xml
new file mode 100644
index 00000000000..1fa9baf7de4
--- /dev/null
+++ b/res/layout/pointer_icon_fill_style_layout.xml
@@ -0,0 +1,102 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 402fd042fab..c72c17d79b2 100755
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -174,6 +174,12 @@
28dp
16sp
+
+ 52dp
+ 8dp
+ 1dp
+ 3dp
+
40dp
14sp
diff --git a/res/values/strings.xml b/res/values/strings.xml
index a2975b5e7f8..1f5c11d82b3 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -4456,6 +4456,18 @@
Tap the bottom right corner of the touchpad for more options
Pointer speed
+
+ Pointer fill style
+
+ Change pointer fill style to black
+
+ Change pointer fill style to green
+
+ Change pointer fill style to yellow
+
+ Change pointer fill style to pink
+
+ Change pointer fill style to blue
Learn touchpad gestures
diff --git a/res/xml/trackpad_settings.xml b/res/xml/trackpad_settings.xml
index fcd43a575c9..1eb16b73156 100644
--- a/res/xml/trackpad_settings.xml
+++ b/res/xml/trackpad_settings.xml
@@ -62,6 +62,13 @@
android:selectable="false"
settings:controller="com.android.settings.inputmethod.TrackpadPointerSpeedPreferenceController"/>
+
+
true);
+ }
+
+ int currentStyle = getPreferenceDataStore().getInt(Settings.System.POINTER_FILL_STYLE,
+ POINTER_ICON_VECTOR_STYLE_FILL_BLACK);
+ initStyleButton(holder, R.id.button_black, POINTER_ICON_VECTOR_STYLE_FILL_BLACK,
+ currentStyle);
+ initStyleButton(holder, R.id.button_green, POINTER_ICON_VECTOR_STYLE_FILL_GREEN,
+ currentStyle);
+ initStyleButton(holder, R.id.button_yellow, POINTER_ICON_VECTOR_STYLE_FILL_YELLOW,
+ currentStyle);
+ initStyleButton(holder, R.id.button_pink, POINTER_ICON_VECTOR_STYLE_FILL_PINK,
+ currentStyle);
+ initStyleButton(holder, R.id.button_blue, POINTER_ICON_VECTOR_STYLE_FILL_BLUE,
+ currentStyle);
+ }
+
+ private void initStyleButton(@NonNull PreferenceViewHolder holder, int id, int style,
+ int currentStyle) {
+ ImageView button = (ImageView) holder.findViewById(id);
+ if (button == null) {
+ return;
+ }
+ int[] attrs = {com.android.internal.R.attr.pointerIconVectorFill};
+ try (TypedArray ta = getContext().obtainStyledAttributes(
+ PointerIcon.vectorFillStyleToResource(style), attrs)) {
+ button.setBackground(getBackgroundSelector(ta.getColor(0, Color.BLACK)));
+ }
+ button.setForeground(getForegroundDrawable(style, currentStyle));
+ button.setForegroundGravity(Gravity.CENTER);
+ button.setOnClickListener(
+ (v) -> {
+ getPreferenceDataStore().putInt(Settings.System.POINTER_FILL_STYLE, style);
+ setButtonChecked(id);
+ });
+ button.setPointerIcon(PointerIcon.getSystemIcon(getContext(), PointerIcon.TYPE_ARROW));
+ }
+
+ // Generate background instead of defining in XML so we can use res color from the platform.
+ private StateListDrawable getBackgroundSelector(int color) {
+ StateListDrawable background = new StateListDrawable();
+ Resources res = getContext().getResources();
+ int ovalSize = res.getDimensionPixelSize(R.dimen.pointer_fill_style_circle_diameter);
+ background.setBounds(0, 0, ovalSize, ovalSize);
+
+ // Add hovered state first! The order states are added matters for a StateListDrawable.
+ GradientDrawable hoveredOval = new GradientDrawable();
+ hoveredOval.setColor(color);
+ int textColor = Utils.getColorAttr(getContext(),
+ com.android.internal.R.attr.materialColorOutline).getDefaultColor();
+ hoveredOval.setStroke(
+ res.getDimensionPixelSize(R.dimen.pointer_fill_style_shape_hovered_stroke),
+ textColor);
+ hoveredOval.setSize(ovalSize, ovalSize);
+ hoveredOval.setBounds(0, 0, ovalSize, ovalSize);
+ hoveredOval.setCornerRadius(ovalSize / 2f);
+ background.addState(new int[]{android.R.attr.state_hovered}, hoveredOval);
+
+ GradientDrawable defaultOval = new GradientDrawable();
+ defaultOval.setColor(color);
+ defaultOval.setStroke(
+ res.getDimensionPixelSize(R.dimen.pointer_fill_style_shape_default_stroke),
+ textColor);
+ defaultOval.setSize(ovalSize, ovalSize);
+ defaultOval.setBounds(0, 0, ovalSize, ovalSize);
+ defaultOval.setCornerRadius(ovalSize / 2f);
+ background.addState(StateSet.WILD_CARD, defaultOval);
+
+ return background;
+ }
+
+ private Drawable getForegroundDrawable(int style, int currentStyle) {
+ Resources res = getContext().getResources();
+ int ovalSize = res.getDimensionPixelSize(R.dimen.pointer_fill_style_circle_diameter);
+ Drawable checkMark = getContext().getDrawable(R.drawable.ic_check_24dp);
+ int padding = res.getDimensionPixelSize(R.dimen.pointer_fill_style_circle_padding) / 2;
+ checkMark.setBounds(padding, padding, ovalSize - padding, ovalSize - padding);
+ checkMark.setColorFilter(new BlendModeColorFilter(Color.WHITE, BlendMode.SRC_IN));
+ checkMark.setAlpha(style == currentStyle ? 255 : 0);
+ return checkMark;
+ }
+
+ private void setButtonChecked(int id) {
+ if (mButtonHolder == null) {
+ return;
+ }
+ for (int i = 0; i < mButtonHolder.getChildCount(); i++) {
+ View child = mButtonHolder.getChildAt(i);
+ if (child != null) {
+ child.getForeground().setAlpha(child.getId() == id ? 255 : 0);
+ }
+ }
+ }
+}
diff --git a/src/com/android/settings/inputmethod/PointerFillStylePreferenceController.java b/src/com/android/settings/inputmethod/PointerFillStylePreferenceController.java
new file mode 100644
index 00000000000..5abc3833405
--- /dev/null
+++ b/src/com/android/settings/inputmethod/PointerFillStylePreferenceController.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2024 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.settings.inputmethod;
+
+import android.content.Context;
+import android.os.UserHandle;
+import android.provider.Settings;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceDataStore;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.core.BasePreferenceController;
+
+public class PointerFillStylePreferenceController extends BasePreferenceController {
+
+ @VisibleForTesting
+ static final String KEY_POINTER_FILL_STYLE = "pointer_fill_style";
+
+ public PointerFillStylePreferenceController(@NonNull Context context) {
+ super(context, KEY_POINTER_FILL_STYLE);
+ }
+
+ @AvailabilityStatus
+ public int getAvailabilityStatus() {
+ return android.view.flags.Flags.enableVectorCursorA11ySettings() ? AVAILABLE
+ : CONDITIONALLY_UNAVAILABLE;
+ }
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ super.displayPreference(screen);
+ Preference pointerFillStylePreference = screen.findPreference(KEY_POINTER_FILL_STYLE);
+ if (pointerFillStylePreference == null) {
+ return;
+ }
+ pointerFillStylePreference.setPreferenceDataStore(new PreferenceDataStore() {
+ @Override
+ public void putInt(@NonNull String key, int value) {
+ Settings.System.putIntForUser(mContext.getContentResolver(), key, value,
+ UserHandle.USER_CURRENT);
+ }
+
+ @Override
+ public int getInt(@NonNull String key, int defValue) {
+ return Settings.System.getIntForUser(mContext.getContentResolver(), key, defValue,
+ UserHandle.USER_CURRENT);
+ }
+ });
+ }
+}