Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 5e22a559 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "[3FT] Replace customized stroke picker with standard Preference" into main

parents 6655257c a201ccc7
Loading
Loading
Loading
Loading
+47 −0
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->

<PreferenceScreen
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:settings="http://schemas.android.com/apk/res-auto"
    android:key="accessibility_pointer_color_customization"
    android:persistent="false"
    android:title="@string/accessibility_pointer_color_customization_title">

    <com.android.settings.inputmethod.PointerFillStylePreference
        android:key="pointer_fill_style"
        android:title="@string/pointer_fill_style"
        settings:controller="com.android.settings.inputmethod.PointerFillStylePreferenceController"/>

    <PreferenceCategory
        android:key="touchpad_category"
        android:persistent="false"
        android:title="@string/pointer_stroke_style">
        <com.android.settingslib.widget.SelectorWithWidgetPreference
            android:key="stroke_style_white"
            android:title="@string/pointer_stroke_style_name_white"
            settings:controller="com.android.settings.inputmethod.PointerStrokePreferenceController"/>
        <com.android.settingslib.widget.SelectorWithWidgetPreference
            android:key="stroke_style_black"
            android:title="@string/pointer_stroke_style_name_black"
            settings:controller="com.android.settings.inputmethod.PointerStrokePreferenceController"/>
        <com.android.settingslib.widget.SelectorWithWidgetPreference
            android:key="stroke_style_none"
            android:title="@string/pointer_stroke_style_name_none"
            settings:controller="com.android.settings.inputmethod.PointerStrokePreferenceController"/>
    </PreferenceCategory>

</PreferenceScreen>
+7 −2
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.settings.inputmethod;

import static com.android.settings.flags.Flags.touchpadSettingsDesignUpdate;
import static com.android.settings.inputmethod.InputPeripheralsSettingsUtils.isMouse;
import static com.android.settings.inputmethod.InputPeripheralsSettingsUtils.isTouchpad;

@@ -32,6 +33,10 @@ public class PointerColorCustomizationFragment extends InputDeviceDashboardFragm

    private static final String TAG = "PointerColorCustomizationFragment";

    private static final int RES = touchpadSettingsDesignUpdate()
            ? R.xml.accessibility_pointer_fill_and_stroke_customization :
            R.xml.accessibility_pointer_color_customization;

    @Override
    public int getMetricsCategory() {
        return SettingsEnums.ACCESSIBILITY_POINTER_COLOR_CUSTOMIZATION;
@@ -39,7 +44,7 @@ public class PointerColorCustomizationFragment extends InputDeviceDashboardFragm

    @Override
    protected int getPreferenceScreenResId() {
        return R.xml.accessibility_pointer_color_customization;
        return RES;
    }

    @Override
@@ -49,7 +54,7 @@ public class PointerColorCustomizationFragment extends InputDeviceDashboardFragm


    public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
            new BaseSearchIndexProvider(R.xml.accessibility_pointer_color_customization) {
            new BaseSearchIndexProvider(RES) {
                @Override
                protected boolean isPageSearchEnabled(Context context) {
                    return isTouchpad() || isMouse();
+155 −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.settings.inputmethod;

import static android.view.PointerIcon.POINTER_ICON_VECTOR_STYLE_STROKE_BLACK;
import static android.view.PointerIcon.POINTER_ICON_VECTOR_STYLE_STROKE_NONE;
import static android.view.PointerIcon.POINTER_ICON_VECTOR_STYLE_STROKE_WHITE;

import android.content.Context;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.os.UserHandle;
import android.provider.Settings;
import android.view.flags.Flags;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleEventObserver;
import androidx.lifecycle.LifecycleOwner;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;

import com.android.settings.core.BasePreferenceController;
import com.android.settingslib.widget.SelectorWithWidgetPreference;

/** Preference controller that updates the cursor stroke's color. */
public class PointerStrokePreferenceController extends BasePreferenceController
        implements LifecycleEventObserver, SelectorWithWidgetPreference.OnClickListener {

    @Nullable
    private SelectorWithWidgetPreference mPreference;

    private final Uri mUri = Settings.System.getUriFor(Settings.System.POINTER_STROKE_STYLE);

    private ContentObserver mObserver =
            new ContentObserver(new Handler(Looper.getMainLooper())) {
                @Override
                public void onChange(boolean selfChange, @Nullable Uri uri) {
                    if (mPreference == null || uri == null) {
                        return;
                    }
                    if (uri.equals(mUri)) {
                        updateState(mPreference);
                    }
                }
            };

    public PointerStrokePreferenceController(@NonNull Context context,
            @NonNull String key) {
        super(context, key);
    }

    @VisibleForTesting
    PointerStrokePreferenceController(@NonNull Context context,
            @NonNull String key,
            ContentObserver contentObserver) {
        this(context, key);
        mObserver = contentObserver;
    }

    @Override
    public int getAvailabilityStatus() {
        return Flags.enableVectorCursorA11ySettings() ? AVAILABLE
                : CONDITIONALLY_UNAVAILABLE;
    }

    @Override
    public void displayPreference(@NonNull PreferenceScreen screen) {
        super.displayPreference(screen);
        mPreference = screen.findPreference(mPreferenceKey);
        if (mPreference != null) {
            mPreference.setOnClickListener(this);
        }
    }

    @Override
    public void onRadioButtonClicked(@NonNull SelectorWithWidgetPreference preference) {
        final int stroke = getStrokeByPrefKey(mPreferenceKey);
        setStroke(stroke);
    }

    @Override
    public void onStateChanged(@NonNull LifecycleOwner lifecycleOwner,
            @NonNull Lifecycle.Event event) {
        if (event == Lifecycle.Event.ON_START) {
            mContext.getContentResolver().registerContentObserver(
                    mUri, /* notifyForDescendants = */ true, mObserver);
        } else if (event == Lifecycle.Event.ON_STOP) {
            mContext.getContentResolver().unregisterContentObserver(mObserver);
        }
    }

    @Override
    public void updateState(@NonNull Preference preference) {
        super.updateState(preference);

        int prefValue = getStrokeByPrefKey(mPreferenceKey);
        int currentValue = getCurrentStroke();
        if (mPreference != null) {
            mPreference.setChecked(prefValue == currentValue);
        }
    }

    /**
     * Method to set stroke colour. Should only be used for testing.
     */
    @VisibleForTesting
    public void setStroke(int stroke) {
        Settings.System.putIntForUser(
                mContext.getContentResolver(),
                Settings.System.POINTER_STROKE_STYLE,
                stroke,
                UserHandle.USER_CURRENT);
    }

    /**
     * Method to get current stroke colour. Should only be used for testing.
     */
    @VisibleForTesting
    public int getCurrentStroke() {
        return Settings.System.getIntForUser(
                mContext.getContentResolver(),
                Settings.System.POINTER_STROKE_STYLE,
                /* default = */ POINTER_ICON_VECTOR_STYLE_STROKE_WHITE,
                UserHandle.USER_CURRENT);
    }

    private int getStrokeByPrefKey(@NonNull String prefKey) {
        if (prefKey.equals("stroke_style_black")) {
            return POINTER_ICON_VECTOR_STYLE_STROKE_BLACK;
        } else if (prefKey.equals("stroke_style_none")) {
            return POINTER_ICON_VECTOR_STYLE_STROKE_NONE;
        }
        // white or default
        return POINTER_ICON_VECTOR_STYLE_STROKE_WHITE;
    }
}
+105 −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.settings.inputmethod;

import static android.view.PointerIcon.POINTER_ICON_VECTOR_STYLE_STROKE_BLACK;
import static android.view.PointerIcon.POINTER_ICON_VECTOR_STYLE_STROKE_NONE;

import static com.google.common.truth.Truth.assertThat;

import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.content.Context;
import android.database.ContentObserver;

import androidx.preference.PreferenceScreen;
import androidx.test.core.app.ApplicationProvider;

import com.android.settings.testutils.shadow.ShadowSystemSettings;
import com.android.settingslib.widget.SelectorWithWidgetPreference;

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;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;

/** Tests for {@link PointerStrokePreferenceController} */
@RunWith(RobolectricTestRunner.class)
@Config(shadows = {
        ShadowSystemSettings.class,
})
public class PointerStrokePreferenceControllerTest {

    private static final String PREF_KEY = "stroke_style_black";
    private static final int PREF_STROKE = POINTER_ICON_VECTOR_STYLE_STROKE_BLACK;
    private static final int OTHER_STROKE = POINTER_ICON_VECTOR_STYLE_STROKE_NONE;

    @Rule
    public MockitoRule mMockitoRule = MockitoJUnit.rule();

    @Mock
    private PreferenceScreen mMockScreen;

    @Mock
    private SelectorWithWidgetPreference mMockPref;
    @Mock
    private ContentObserver mMockContentObserver;

    private final Context mContext = ApplicationProvider.getApplicationContext();
    private PointerStrokePreferenceController mController;

    @Before
    public void setup() {
        mController = new PointerStrokePreferenceController(
                mContext, PREF_KEY, mMockContentObserver);
        when(mMockScreen.findPreference(mController.getPreferenceKey())).thenReturn(mMockPref);
        when(mMockPref.getKey()).thenReturn(PREF_KEY);
        mController.displayPreference(mMockScreen);
    }

    @Test
    public void updateState_whenPreferenceIsNotCurrentStroke_preferenceNotChecked() {
        mController.setStroke(OTHER_STROKE);
        mController.updateState(mMockPref);

        verify(mMockPref).setChecked(false);
    }


    @Test
    public void updateState_whenPreferenceMatchesCurrentStroke_preferenceChecked() {
        mController.setStroke(PREF_STROKE);
        mController.updateState(mMockPref);

        verify(mMockPref).setChecked(true);
    }


    @Test
    public void onRadioButtonClick_strokeUpdated() {
        mController.onRadioButtonClicked(mMockPref);

        int value = mController.getCurrentStroke();
        assertThat(value).isEqualTo(PREF_STROKE);
    }
}