diff --git a/res/values/strings.xml b/res/values/strings.xml index 31ea1bff92f..42eaed4b298 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -4705,9 +4705,13 @@ Sticky keys, Bounce keys, Mouse keys - Repeat Keys + Repeat Keys + + Delay before repeat + + Repeat Rate - Hold down a key to repeat its character until the key is released + Hold down a key to repeat its character until the key is released %s layout diff --git a/res/xml/physical_keyboard_settings.xml b/res/xml/physical_keyboard_settings.xml index 5d2c5fcd99b..56914052b28 100644 --- a/res/xml/physical_keyboard_settings.xml +++ b/res/xml/physical_keyboard_settings.xml @@ -33,11 +33,12 @@ android:summary="@string/modifier_keys_settings_summary" android:fragment="com.android.settings.inputmethod.ModifierKeysSettings" /> - + + + + + + + + \ No newline at end of file diff --git a/src/com/android/settings/inputmethod/KeyboardRepeatKeysController.java b/src/com/android/settings/inputmethod/KeyboardRepeatKeysController.java index a232098f696..28392beb34a 100644 --- a/src/com/android/settings/inputmethod/KeyboardRepeatKeysController.java +++ b/src/com/android/settings/inputmethod/KeyboardRepeatKeysController.java @@ -25,14 +25,20 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.lifecycle.LifecycleObserver; import androidx.preference.PreferenceScreen; -import androidx.preference.SwitchPreferenceCompat; + +import com.android.settingslib.PrimarySwitchPreference; +import com.android.settingslib.widget.MainSwitchPreference; public class KeyboardRepeatKeysController extends InputSettingPreferenceController implements LifecycleObserver { + private static final String KEY_REPEAT_KEY = "physical_keyboard_repeat_keys"; + private static final String KEY_REPEAT_KEY_MAIN_PAGE = "repeat_key_main_switch"; @Nullable - private SwitchPreferenceCompat mSwitchPreferenceCompat; + private PrimarySwitchPreference mPrimarySwitchPreference; + @Nullable + private MainSwitchPreference mMainSwitchPreference; public KeyboardRepeatKeysController(@NonNull Context context, @NonNull String key) { @@ -42,7 +48,11 @@ public class KeyboardRepeatKeysController extends @Override public void displayPreference(@NonNull PreferenceScreen screen) { super.displayPreference(screen); - mSwitchPreferenceCompat = screen.findPreference(getPreferenceKey()); + if (KEY_REPEAT_KEY.equals(getPreferenceKey())) { + mPrimarySwitchPreference = screen.findPreference(getPreferenceKey()); + } else if (KEY_REPEAT_KEY_MAIN_PAGE.equals(getPreferenceKey())) { + mMainSwitchPreference = screen.findPreference(getPreferenceKey()); + } } @Override @@ -63,8 +73,10 @@ public class KeyboardRepeatKeysController extends @Override protected void onInputSettingUpdated() { - if (mSwitchPreferenceCompat != null) { - mSwitchPreferenceCompat.setChecked(InputSettings.isRepeatKeysEnabled(mContext)); + if (mPrimarySwitchPreference != null) { + mPrimarySwitchPreference.setChecked(InputSettings.isRepeatKeysEnabled(mContext)); + } else if (mMainSwitchPreference != null) { + mMainSwitchPreference.setChecked(InputSettings.isRepeatKeysEnabled(mContext)); } } diff --git a/src/com/android/settings/inputmethod/KeyboardRepeatKeysDelayPreferenceController.java b/src/com/android/settings/inputmethod/KeyboardRepeatKeysDelayPreferenceController.java new file mode 100644 index 00000000000..bb074fd94f7 --- /dev/null +++ b/src/com/android/settings/inputmethod/KeyboardRepeatKeysDelayPreferenceController.java @@ -0,0 +1,65 @@ +/* + * 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.hardware.input.InputSettings; + +import androidx.annotation.NonNull; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.settings.core.SliderPreferenceController; + +import com.google.common.collect.ImmutableList; + +public class KeyboardRepeatKeysDelayPreferenceController extends SliderPreferenceController { + @VisibleForTesting + static final ImmutableList REPEAT_KEY_DELAY_VALUE_LIST = ImmutableList.of(2000, 1000, + 500, 300, 200, 100, 50, 30, 20); + + public KeyboardRepeatKeysDelayPreferenceController(@NonNull Context context, + @NonNull String preferenceKey) { + super(context, preferenceKey); + } + + @Override + public int getSliderPosition() { + return REPEAT_KEY_DELAY_VALUE_LIST.indexOf(InputSettings.getRepeatKeysDelay(mContext)); + } + + @Override + public boolean setSliderPosition(int position) { + InputSettings.setRepeatKeysDelay(mContext, REPEAT_KEY_DELAY_VALUE_LIST.get(position)); + return true; + } + + @Override + public int getMax() { + return REPEAT_KEY_DELAY_VALUE_LIST.size() - 1; + } + + @Override + public int getMin() { + return 0; + } + + @Override + public int getAvailabilityStatus() { + return InputSettings.isRepeatKeysFeatureFlagEnabled() + ? AVAILABLE : UNSUPPORTED_ON_DEVICE; + } +} diff --git a/src/com/android/settings/inputmethod/KeyboardRepeatKeysMainFragment.java b/src/com/android/settings/inputmethod/KeyboardRepeatKeysMainFragment.java new file mode 100644 index 00000000000..39e605d1d59 --- /dev/null +++ b/src/com/android/settings/inputmethod/KeyboardRepeatKeysMainFragment.java @@ -0,0 +1,170 @@ +/* + * 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 static com.android.settings.inputmethod.PhysicalKeyboardFragment.getHardKeyboards; + +import android.app.settings.SettingsEnums; +import android.content.ContentResolver; +import android.content.Context; +import android.database.ContentObserver; +import android.hardware.input.InputManager; +import android.hardware.input.InputSettings; +import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; +import android.os.UserHandle; +import android.provider.Settings; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.android.internal.util.Preconditions; +import com.android.settings.R; +import com.android.settings.dashboard.DashboardFragment; +import com.android.settings.keyboard.Flags; +import com.android.settings.search.BaseSearchIndexProvider; +import com.android.settings.widget.LabeledSeekBarPreference; +import com.android.settingslib.utils.ThreadUtils; + +import java.util.List; + +public class KeyboardRepeatKeysMainFragment extends DashboardFragment + implements InputManager.InputDeviceListener { + private static final String TAG = "RepeatKeysMainFragment"; + private static final String TIME_OUT_KEY = "repeat_keys_timeout_preference"; + private static final String DELAY_KEY = "repeat_keys_delay_preference"; + + private final Uri mRepeatKeyUri = Settings.Secure.getUriFor( + Settings.Secure.KEY_REPEAT_ENABLED); + private final ContentObserver mContentObserver = new ContentObserver(new Handler(true)) { + @Override + public void onChange(boolean selfChange, Uri uri) { + if (mRepeatKeyUri.equals(uri)) { + updatePreferencesState(); + } + } + }; + private InputManager mInputManager; + private ContentResolver mContentResolver; + @Nullable + private LabeledSeekBarPreference mRepeatTimeoutPreference; + @Nullable + private LabeledSeekBarPreference mRepeatDelayPreference; + + @Override + public int getMetricsCategory() { + return SettingsEnums.PHYSICAL_KEYBOARD_A11Y; + } + + @Override + public void onAttach(@NonNull Context context) { + super.onAttach(context); + mInputManager = Preconditions.checkNotNull(getActivity() + .getSystemService(InputManager.class)); + mContentResolver = context.getContentResolver(); + } + + @Override + public void onCreatePreferences(Bundle bundle, String s) { + super.onCreatePreferences(bundle, s); + mRepeatTimeoutPreference = findPreference(TIME_OUT_KEY); + mRepeatDelayPreference = findPreference(DELAY_KEY); + updatePreferencesState(); + } + + @Override + public void onResume() { + super.onResume(); + finishEarlyIfNeeded(); + mInputManager.registerInputDeviceListener(this, null); + registerSettingsObserver(); + } + + @Override + public void onPause() { + super.onPause(); + mInputManager.unregisterInputDeviceListener(this); + unregisterSettingsObserver(); + } + + @Override + protected String getLogTag() { + return TAG; + } + + @Override + protected int getPreferenceScreenResId() { + return R.xml.repeat_key_main_page; + } + + private void updatePreferencesState() { + boolean isRepeatKeyEnabled = InputSettings.isRepeatKeysEnabled(getContext()); + if (mRepeatTimeoutPreference != null && mRepeatDelayPreference != null) { + mRepeatTimeoutPreference.setEnabled(isRepeatKeyEnabled); + mRepeatDelayPreference.setEnabled(isRepeatKeyEnabled); + } + } + + private void registerSettingsObserver() { + unregisterSettingsObserver(); + mContentResolver.registerContentObserver( + mRepeatKeyUri, + false, + mContentObserver, + UserHandle.myUserId()); + } + + private void unregisterSettingsObserver() { + mContentResolver.unregisterContentObserver(mContentObserver); + } + + @Override + public void onInputDeviceAdded(int deviceId) { + finishEarlyIfNeeded(); + } + + @Override + public void onInputDeviceRemoved(int deviceId) { + finishEarlyIfNeeded(); + } + + @Override + public void onInputDeviceChanged(int deviceId) { + finishEarlyIfNeeded(); + } + + private void finishEarlyIfNeeded() { + final Context context = getContext(); + ThreadUtils.postOnBackgroundThread(() -> { + final List newHardKeyboards = + getHardKeyboards(context); + if (newHardKeyboards.isEmpty()) { + getActivity().finish(); + } + }); + } + + public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = + new BaseSearchIndexProvider(R.xml.repeat_key_main_page) { + @Override + protected boolean isPageSearchEnabled(Context context) { + return Flags.keyboardAndTouchpadA11yNewPageEnabled() + && !getHardKeyboards(context).isEmpty(); + } + }; +} diff --git a/src/com/android/settings/inputmethod/KeyboardRepeatKeysTimeOutPreferenceController.java b/src/com/android/settings/inputmethod/KeyboardRepeatKeysTimeOutPreferenceController.java new file mode 100644 index 00000000000..e766d48318e --- /dev/null +++ b/src/com/android/settings/inputmethod/KeyboardRepeatKeysTimeOutPreferenceController.java @@ -0,0 +1,66 @@ +/* + * 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.hardware.input.InputSettings; + +import androidx.annotation.NonNull; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.settings.core.SliderPreferenceController; + +import com.google.common.collect.ImmutableList; + +public class KeyboardRepeatKeysTimeOutPreferenceController extends SliderPreferenceController { + @VisibleForTesting + static final ImmutableList REPEAT_KEY_TIMEOUT_VALUE_LIST = ImmutableList.of(2000, 1500, + 1000, 400, 300, 200, 150); + + public KeyboardRepeatKeysTimeOutPreferenceController( + @NonNull Context context, + @NonNull String preferenceKey) { + super(context, preferenceKey); + } + + @Override + public int getSliderPosition() { + return REPEAT_KEY_TIMEOUT_VALUE_LIST.indexOf(InputSettings.getRepeatKeysTimeout(mContext)); + } + + @Override + public boolean setSliderPosition(int position) { + InputSettings.setRepeatKeysTimeout(mContext, REPEAT_KEY_TIMEOUT_VALUE_LIST.get(position)); + return true; + } + + @Override + public int getMax() { + return REPEAT_KEY_TIMEOUT_VALUE_LIST.size() - 1; + } + + @Override + public int getMin() { + return 0; + } + + @Override + public int getAvailabilityStatus() { + return InputSettings.isRepeatKeysFeatureFlagEnabled() + ? AVAILABLE : UNSUPPORTED_ON_DEVICE; + } +} diff --git a/tests/robotests/src/com/android/settings/inputmethod/KeyboardRepeatKeysControllerTest.java b/tests/robotests/src/com/android/settings/inputmethod/KeyboardRepeatKeysControllerTest.java index e1b4ffdc068..d511edcf4ee 100644 --- a/tests/robotests/src/com/android/settings/inputmethod/KeyboardRepeatKeysControllerTest.java +++ b/tests/robotests/src/com/android/settings/inputmethod/KeyboardRepeatKeysControllerTest.java @@ -51,7 +51,7 @@ public class KeyboardRepeatKeysControllerTest { public void setUp() { mContext = RuntimeEnvironment.application; mKeyboardRepeatKeysController = new KeyboardRepeatKeysController(mContext, - "physical_keyboard_repeat_key"); + "physical_keyboard_repeat_keys"); } @Test diff --git a/tests/robotests/src/com/android/settings/inputmethod/KeyboardRepeatKeysDelayPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/inputmethod/KeyboardRepeatKeysDelayPreferenceControllerTest.java new file mode 100644 index 00000000000..0f2deacbcdd --- /dev/null +++ b/tests/robotests/src/com/android/settings/inputmethod/KeyboardRepeatKeysDelayPreferenceControllerTest.java @@ -0,0 +1,86 @@ +/* + * 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 static com.android.input.flags.Flags.FLAG_KEYBOARD_REPEAT_KEYS; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.Context; +import android.hardware.input.InputSettings; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.SetFlagsRule; + +import com.android.settings.core.BasePreferenceController; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; + +@RunWith(RobolectricTestRunner.class) +@Config(shadows = { + com.android.settings.testutils.shadow.ShadowFragment.class, +}) +public class KeyboardRepeatKeysDelayPreferenceControllerTest { + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + private Context mContext; + private KeyboardRepeatKeysDelayPreferenceController mRepeatKeysDelayPreferenceController; + + @Before + public void setUp() { + mContext = RuntimeEnvironment.application; + mRepeatKeysDelayPreferenceController = new KeyboardRepeatKeysDelayPreferenceController( + mContext, "repeat_keys_delay_preference"); + } + + @Test + @EnableFlags(FLAG_KEYBOARD_REPEAT_KEYS) + public void getAvailabilityStatus_flagIsEnabled_isAvailable() { + assertThat(mRepeatKeysDelayPreferenceController.getAvailabilityStatus()) + .isEqualTo(BasePreferenceController.AVAILABLE); + } + + @Test + @DisableFlags(FLAG_KEYBOARD_REPEAT_KEYS) + public void getAvailabilityStatus_flagIsDisabled_notSupport() { + assertThat(mRepeatKeysDelayPreferenceController.getAvailabilityStatus()) + .isEqualTo(BasePreferenceController.UNSUPPORTED_ON_DEVICE); + } + + @Test + public void setSliderPosition_updatesInputSettingValue() { + int sliderPosition = 1; + mRepeatKeysDelayPreferenceController.setSliderPosition(sliderPosition); + assertThat(InputSettings.getRepeatKeysDelay(mContext)).isEqualTo( + KeyboardRepeatKeysDelayPreferenceController.REPEAT_KEY_DELAY_VALUE_LIST.get( + sliderPosition)); + } + + @Test + public void getSliderPosition_matchesWithDelayValue() { + int timeout = InputSettings.getRepeatKeysDelay(mContext); + assertThat(mRepeatKeysDelayPreferenceController.getSliderPosition()).isEqualTo( + KeyboardRepeatKeysDelayPreferenceController.REPEAT_KEY_DELAY_VALUE_LIST.indexOf( + timeout)); + } +} diff --git a/tests/robotests/src/com/android/settings/inputmethod/KeyboardRepeatKeysTimeOutPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/inputmethod/KeyboardRepeatKeysTimeOutPreferenceControllerTest.java new file mode 100644 index 00000000000..f192c0ca5ed --- /dev/null +++ b/tests/robotests/src/com/android/settings/inputmethod/KeyboardRepeatKeysTimeOutPreferenceControllerTest.java @@ -0,0 +1,88 @@ +/* + * 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 static com.android.input.flags.Flags.FLAG_KEYBOARD_REPEAT_KEYS; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.Context; +import android.hardware.input.InputSettings; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.SetFlagsRule; + +import com.android.settings.core.BasePreferenceController; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; + +@RunWith(RobolectricTestRunner.class) +@Config(shadows = { + com.android.settings.testutils.shadow.ShadowFragment.class, +}) +public class KeyboardRepeatKeysTimeOutPreferenceControllerTest { + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + private Context mContext; + private KeyboardRepeatKeysTimeOutPreferenceController + mKeyboardRepeatKeysTimeOutPreferenceController; + + @Before + public void setUp() { + mContext = RuntimeEnvironment.application; + mKeyboardRepeatKeysTimeOutPreferenceController = + new KeyboardRepeatKeysTimeOutPreferenceController(mContext, + "repeat_keys_timeout_preference"); + } + + @Test + @EnableFlags(FLAG_KEYBOARD_REPEAT_KEYS) + public void getAvailabilityStatus_flagIsEnabled_isAvailable() { + assertThat(mKeyboardRepeatKeysTimeOutPreferenceController.getAvailabilityStatus()) + .isEqualTo(BasePreferenceController.AVAILABLE); + } + + @Test + @DisableFlags(FLAG_KEYBOARD_REPEAT_KEYS) + public void getAvailabilityStatus_flagIsDisabled_notSupport() { + assertThat(mKeyboardRepeatKeysTimeOutPreferenceController.getAvailabilityStatus()) + .isEqualTo(BasePreferenceController.UNSUPPORTED_ON_DEVICE); + } + + @Test + public void setSliderPosition_updatesInputSettingValue() { + int sliderPosition = 1; + mKeyboardRepeatKeysTimeOutPreferenceController.setSliderPosition(sliderPosition); + assertThat(InputSettings.getRepeatKeysTimeout(mContext)).isEqualTo( + KeyboardRepeatKeysTimeOutPreferenceController.REPEAT_KEY_TIMEOUT_VALUE_LIST.get( + sliderPosition)); + } + + @Test + public void getSliderPosition_matchesWithTimeoutValue() { + int timeout = InputSettings.getRepeatKeysTimeout(mContext); + assertThat(mKeyboardRepeatKeysTimeOutPreferenceController.getSliderPosition()).isEqualTo( + KeyboardRepeatKeysTimeOutPreferenceController.REPEAT_KEY_TIMEOUT_VALUE_LIST.indexOf( + timeout)); + } +}