From 120b2d46f71476119c33f5ed4855d991700afe6e Mon Sep 17 00:00:00 2001 From: shaoweishen Date: Mon, 20 Jan 2025 08:27:54 +0000 Subject: [PATCH] [Physical Keyboard] Move Dialog to DialogFragment Move Bounce key and Slow key Dialog to DialogFragment, dialog will not be dismissed when screen rotate. Bug: 390243451 Bug: 374037603 Flag: com.android.settings.keyboard.keyboard_and_touchpad_a11y_new_page_enabled Test: atest packages/apps/Settings/tests/robotests/src/com/android/settings/inputmethod/ Change-Id: Ide87dbf8214f411941114281e7a5e8c81f75bdd4 --- .../InputSettingPreferenceController.java | 135 +------------ ...oardAccessibilityBounceKeysController.java | 23 +-- ...AccessibilityBounceKeysDialogFragment.java | 54 +++++ ...yboardAccessibilityKeysDialogFragment.java | 187 ++++++++++++++++++ ...yboardAccessibilitySlowKeysController.java | 22 +-- ...rdAccessibilitySlowKeysDialogFragment.java | 54 +++++ .../PhysicalKeyboardA11yFragment.java | 2 + ...AccessibilityBounceKeysControllerTest.java | 58 ++++-- ...ssibilityBounceKeysDialogFragmentTest.java | 81 ++++++++ ...rdAccessibilitySlowKeysControllerTest.java | 58 ++++-- ...cessibilitySlowKeysDialogFragmentTest.java | 81 ++++++++ 11 files changed, 555 insertions(+), 200 deletions(-) create mode 100644 src/com/android/settings/inputmethod/KeyboardAccessibilityBounceKeysDialogFragment.java create mode 100644 src/com/android/settings/inputmethod/KeyboardAccessibilityKeysDialogFragment.java create mode 100644 src/com/android/settings/inputmethod/KeyboardAccessibilitySlowKeysDialogFragment.java create mode 100644 tests/robotests/src/com/android/settings/inputmethod/KeyboardAccessibilityBounceKeysDialogFragmentTest.java create mode 100644 tests/robotests/src/com/android/settings/inputmethod/KeyboardAccessibilitySlowKeysDialogFragmentTest.java diff --git a/src/com/android/settings/inputmethod/InputSettingPreferenceController.java b/src/com/android/settings/inputmethod/InputSettingPreferenceController.java index f18c9f4ed49..17b70b3da3a 100644 --- a/src/com/android/settings/inputmethod/InputSettingPreferenceController.java +++ b/src/com/android/settings/inputmethod/InputSettingPreferenceController.java @@ -25,36 +25,29 @@ import android.database.ContentObserver; import android.net.Uri; import android.os.Handler; import android.os.UserHandle; -import android.view.View; -import android.widget.RadioButton; -import android.widget.RadioGroup; -import android.widget.SeekBar; -import android.widget.TextView; import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; import androidx.lifecycle.LifecycleObserver; import androidx.lifecycle.OnLifecycleEvent; import androidx.preference.Preference; -import com.android.settings.R; import com.android.settings.core.TogglePreferenceController; import com.android.settings.keyboard.Flags; import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; -import java.text.NumberFormat; -import java.util.concurrent.TimeUnit; - /** * Abstract class for toggle controllers of Keyboard input setting related function. */ public abstract class InputSettingPreferenceController extends TogglePreferenceController implements LifecycleObserver { - private static final int CUSTOM_PROGRESS_INTERVAL = 100; - private static final long MILLISECOND_IN_SECONDS = TimeUnit.SECONDS.toMillis(1); private final ContentResolver mContentResolver; protected final MetricsFeatureProvider mMetricsFeatureProvider; + protected FragmentManager mFragmentManager; + private final ContentObserver mContentObserver = new ContentObserver(new Handler(true)) { @Override public void onChange(boolean selfChange, Uri uri) { @@ -72,13 +65,6 @@ public abstract class InputSettingPreferenceController extends TogglePreferenceC protected void updateInputSettingKeysValue(int thresholdTimeMillis) { } - protected int getInputSettingKeysValue() { - return 0; - } - - protected void onCustomValueUpdated(int thresholdTimeMillis) { - } - public InputSettingPreferenceController(@NonNull Context context, @NonNull String preferenceKey) { super(context, preferenceKey); @@ -86,6 +72,10 @@ public abstract class InputSettingPreferenceController extends TogglePreferenceC mMetricsFeatureProvider = FeatureFactory.getFeatureFactory().getMetricsFeatureProvider(); } + public void setFragment(Fragment fragment) { + mFragmentManager = fragment.getParentFragmentManager(); + } + @Override public void updateState(@NonNull Preference preference) { super.updateState(preference); @@ -127,113 +117,4 @@ public abstract class InputSettingPreferenceController extends TogglePreferenceC private void unregisterSettingsObserver() { mContentResolver.unregisterContentObserver(mContentObserver); } - - protected void constructDialog(Context context, int titleRes, int subtitleRes) { - mAlertDialog = new AlertDialog.Builder(context) - .setView(R.layout.dialog_keyboard_a11y_input_setting_keys) - .setPositiveButton(android.R.string.ok, - (dialog, which) -> { - RadioGroup radioGroup = - mAlertDialog.findViewById( - R.id.input_setting_keys_value_group); - SeekBar seekbar = mAlertDialog.findViewById( - R.id.input_setting_keys_value_custom_slider); - RadioButton customRadioButton = mAlertDialog.findViewById( - R.id.input_setting_keys_value_custom); - int threshold; - if (customRadioButton.isChecked()) { - threshold = seekbar.getProgress() * CUSTOM_PROGRESS_INTERVAL; - } else { - int checkedRadioButtonId = radioGroup.getCheckedRadioButtonId(); - if (checkedRadioButtonId == R.id.input_setting_keys_value_600) { - threshold = 600; - } else if (checkedRadioButtonId - == R.id.input_setting_keys_value_400) { - threshold = 400; - } else if (checkedRadioButtonId - == R.id.input_setting_keys_value_200) { - threshold = 200; - } else { - threshold = 0; - } - } - updateInputSettingKeysValue(threshold); - onCustomValueUpdated(threshold); - }) - .setNegativeButton(android.R.string.cancel, (dialog, which) -> dialog.dismiss()) - .create(); - mAlertDialog.setOnShowListener(dialog -> { - RadioGroup cannedValueRadioGroup = mAlertDialog.findViewById( - R.id.input_setting_keys_value_group); - RadioButton customRadioButton = mAlertDialog.findViewById( - R.id.input_setting_keys_value_custom); - TextView customValueTextView = mAlertDialog.findViewById( - R.id.input_setting_keys_value_custom_value); - SeekBar customProgressBar = mAlertDialog.findViewById( - R.id.input_setting_keys_value_custom_slider); - TextView titleTextView = mAlertDialog.findViewById( - R.id.input_setting_keys_dialog_title); - TextView subTitleTextView = mAlertDialog.findViewById( - R.id.input_setting_keys_dialog_subtitle); - titleTextView.setText(titleRes); - subTitleTextView.setText(subtitleRes); - - customProgressBar.incrementProgressBy(CUSTOM_PROGRESS_INTERVAL); - customProgressBar.setProgress(1); - View customValueView = mAlertDialog.findViewById( - R.id.input_setting_keys_custom_value_option); - customValueView.setOnClickListener(l -> customRadioButton.performClick()); - customRadioButton.setOnCheckedChangeListener((buttonView, isChecked) -> { - if (isChecked) { - cannedValueRadioGroup.clearCheck(); - } - customValueTextView.setVisibility(isChecked ? View.VISIBLE : View.GONE); - customValueTextView.setText( - progressToThresholdInSecond(customProgressBar.getProgress())); - customProgressBar.setVisibility(isChecked ? View.VISIBLE : View.GONE); - buttonView.setChecked(isChecked); - }); - cannedValueRadioGroup.setOnCheckedChangeListener( - (group, checkedId) -> customRadioButton.setChecked(false)); - customProgressBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { - @Override - public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { - customValueTextView.setText(progressToThresholdInSecond(progress)); - } - - @Override - public void onStartTrackingTouch(SeekBar seekBar) { - } - - @Override - public void onStopTrackingTouch(SeekBar seekBar) { - } - }); - initStateBasedOnThreshold(cannedValueRadioGroup, customRadioButton, customValueTextView, - customProgressBar); - }); - } - - private static String progressToThresholdInSecond(int progress) { - return NumberFormat.getInstance().format((float) progress * CUSTOM_PROGRESS_INTERVAL - / MILLISECOND_IN_SECONDS); - } - - private void initStateBasedOnThreshold(RadioGroup cannedValueRadioGroup, - RadioButton customRadioButton, TextView customValueTextView, - SeekBar customProgressBar) { - int inputSettingKeysThreshold = getInputSettingKeysValue(); - switch (inputSettingKeysThreshold) { - case 600 -> cannedValueRadioGroup.check(R.id.input_setting_keys_value_600); - case 400 -> cannedValueRadioGroup.check(R.id.input_setting_keys_value_400); - case 0, 200 -> cannedValueRadioGroup.check(R.id.input_setting_keys_value_200); - default -> { - customValueTextView.setText( - String.valueOf( - (double) inputSettingKeysThreshold / MILLISECOND_IN_SECONDS)); - customProgressBar.setProgress(inputSettingKeysThreshold / CUSTOM_PROGRESS_INTERVAL); - customRadioButton.setChecked(true); - } - } - } } diff --git a/src/com/android/settings/inputmethod/KeyboardAccessibilityBounceKeysController.java b/src/com/android/settings/inputmethod/KeyboardAccessibilityBounceKeysController.java index 9a0f1c0d6e3..69fbe6b7e3a 100644 --- a/src/com/android/settings/inputmethod/KeyboardAccessibilityBounceKeysController.java +++ b/src/com/android/settings/inputmethod/KeyboardAccessibilityBounceKeysController.java @@ -16,7 +16,6 @@ package com.android.settings.inputmethod; -import static android.app.settings.SettingsEnums.ACTION_BOUNCE_KEYS_CUSTOM_VALUE_CHANGE; import static android.app.settings.SettingsEnums.ACTION_BOUNCE_KEYS_DISABLED; import static android.app.settings.SettingsEnums.ACTION_BOUNCE_KEYS_ENABLED; @@ -32,13 +31,13 @@ import androidx.lifecycle.LifecycleObserver; import androidx.preference.Preference; import androidx.preference.PreferenceScreen; -import com.android.settings.R; import com.android.settingslib.PrimarySwitchPreference; public class KeyboardAccessibilityBounceKeysController extends InputSettingPreferenceController implements LifecycleObserver { public static final int BOUNCE_KEYS_THRESHOLD = 500; + private static final String KEY_TAG = "bounce_keys_dialog_tag"; @Nullable private PrimarySwitchPreference mPrimaryPreference; @@ -46,8 +45,6 @@ public class KeyboardAccessibilityBounceKeysController extends public KeyboardAccessibilityBounceKeysController(@NonNull Context context, @NonNull String key) { super(context, key); - constructDialog(context, R.string.bounce_keys_dialog_title, - R.string.bounce_keys_dialog_subtitle); } @Override @@ -65,12 +62,11 @@ public class KeyboardAccessibilityBounceKeysController extends @Override public boolean handlePreferenceTreeClick(@NonNull Preference preference) { - if (!TextUtils.equals(preference.getKey(), getPreferenceKey())) { + if (!TextUtils.equals(preference.getKey(), getPreferenceKey()) + || mFragmentManager == null) { return false; } - if (mAlertDialog != null) { - mAlertDialog.show(); - } + KeyboardAccessibilityBounceKeysDialogFragment.getInstance().show(mFragmentManager, KEY_TAG); return true; } @@ -87,12 +83,6 @@ public class KeyboardAccessibilityBounceKeysController extends return true; } - @Override - protected void onCustomValueUpdated(int thresholdTimeMillis) { - mMetricsFeatureProvider.action(mContext, ACTION_BOUNCE_KEYS_CUSTOM_VALUE_CHANGE, - thresholdTimeMillis); - } - @Override protected void onInputSettingUpdated() { if (mPrimaryPreference != null) { @@ -111,9 +101,4 @@ public class KeyboardAccessibilityBounceKeysController extends protected void updateInputSettingKeysValue(int thresholdTimeMillis) { InputSettings.setAccessibilityBounceKeysThreshold(mContext, thresholdTimeMillis); } - - @Override - protected int getInputSettingKeysValue() { - return InputSettings.getAccessibilityBounceKeysThreshold(mContext); - } } diff --git a/src/com/android/settings/inputmethod/KeyboardAccessibilityBounceKeysDialogFragment.java b/src/com/android/settings/inputmethod/KeyboardAccessibilityBounceKeysDialogFragment.java new file mode 100644 index 00000000000..2dc90de84b1 --- /dev/null +++ b/src/com/android/settings/inputmethod/KeyboardAccessibilityBounceKeysDialogFragment.java @@ -0,0 +1,54 @@ +/* + * 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.app.settings.SettingsEnums.ACTION_BOUNCE_KEYS_CUSTOM_VALUE_CHANGE; + +import android.hardware.input.InputSettings; +import android.os.Bundle; + +import com.android.settings.R; + +public class KeyboardAccessibilityBounceKeysDialogFragment extends + KeyboardAccessibilityKeysDialogFragment { + + static KeyboardAccessibilityBounceKeysDialogFragment getInstance() { + final KeyboardAccessibilityBounceKeysDialogFragment result = + new KeyboardAccessibilityBounceKeysDialogFragment(); + Bundle bundle = new Bundle(); + bundle.putInt(EXTRA_TITLE_RES, R.string.bounce_keys_dialog_title); + bundle.putInt(EXTRA_SUBTITLE_RES, R.string.bounce_keys_dialog_subtitle); + result.setArguments(bundle); + return result; + } + + @Override + protected void updateInputSettingKeysValue(int thresholdTimeMillis) { + InputSettings.setAccessibilityBounceKeysThreshold(getContext(), thresholdTimeMillis); + } + + @Override + protected void onCustomValueUpdated(int thresholdTimeMillis) { + mMetricsFeatureProvider.action(getContext(), ACTION_BOUNCE_KEYS_CUSTOM_VALUE_CHANGE, + thresholdTimeMillis); + } + + @Override + protected int getInputSettingKeysValue() { + return InputSettings.getAccessibilityBounceKeysThreshold(getContext()); + } +} diff --git a/src/com/android/settings/inputmethod/KeyboardAccessibilityKeysDialogFragment.java b/src/com/android/settings/inputmethod/KeyboardAccessibilityKeysDialogFragment.java new file mode 100644 index 00000000000..bba47a72fd7 --- /dev/null +++ b/src/com/android/settings/inputmethod/KeyboardAccessibilityKeysDialogFragment.java @@ -0,0 +1,187 @@ +/* + * 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.WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Dialog; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.Window; +import android.widget.RadioButton; +import android.widget.RadioGroup; +import android.widget.SeekBar; +import android.widget.TextView; + +import androidx.fragment.app.DialogFragment; + +import com.android.settings.R; +import com.android.settings.overlay.FeatureFactory; +import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; + +import org.jspecify.annotations.Nullable; + +import java.util.concurrent.TimeUnit; + +public abstract class KeyboardAccessibilityKeysDialogFragment extends DialogFragment { + private static final int CUSTOM_PROGRESS_INTERVAL = 100; + private static final long MILLISECOND_IN_SECONDS = TimeUnit.SECONDS.toMillis(1); + protected static final String EXTRA_TITLE_RES = "extra_title_res"; + protected static final String EXTRA_SUBTITLE_RES = "extra_subtitle_res"; + + protected final MetricsFeatureProvider mMetricsFeatureProvider; + + public KeyboardAccessibilityKeysDialogFragment() { + mMetricsFeatureProvider = FeatureFactory.getFeatureFactory().getMetricsFeatureProvider(); + } + + protected void updateInputSettingKeysValue(int thresholdTimeMillis) { + } + + protected void onCustomValueUpdated(int thresholdTimeMillis) { + } + + protected int getInputSettingKeysValue() { + return 0; + } + + @Override + public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { + super.onCreateDialog(savedInstanceState); + int titleRes = getArguments().getInt(EXTRA_TITLE_RES); + int subtitleRes = getArguments().getInt(EXTRA_SUBTITLE_RES); + + Activity activity = getActivity(); + View dialoglayout = + LayoutInflater.from(activity).inflate( + R.layout.dialog_keyboard_a11y_input_setting_keys, null); + AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(activity); + dialogBuilder.setView(dialoglayout); + dialogBuilder.setPositiveButton(android.R.string.ok, + (dialog, which) -> { + RadioGroup radioGroup = + dialoglayout.findViewById( + R.id.input_setting_keys_value_group); + SeekBar seekbar = dialoglayout.findViewById( + R.id.input_setting_keys_value_custom_slider); + RadioButton customRadioButton = dialoglayout.findViewById( + R.id.input_setting_keys_value_custom); + int threshold; + if (customRadioButton.isChecked()) { + threshold = seekbar.getProgress() * CUSTOM_PROGRESS_INTERVAL; + } else { + int checkedRadioButtonId = radioGroup.getCheckedRadioButtonId(); + if (checkedRadioButtonId == R.id.input_setting_keys_value_600) { + threshold = 600; + } else if (checkedRadioButtonId + == R.id.input_setting_keys_value_400) { + threshold = 400; + } else if (checkedRadioButtonId + == R.id.input_setting_keys_value_200) { + threshold = 200; + } else { + threshold = 0; + } + } + updateInputSettingKeysValue(threshold); + onCustomValueUpdated(threshold); + }) + .setNegativeButton(android.R.string.cancel, (dialog, which) -> dialog.dismiss()); + AlertDialog accessibilityKeyDialog = dialogBuilder.create(); + accessibilityKeyDialog.setOnShowListener(dialog -> { + RadioGroup cannedValueRadioGroup = accessibilityKeyDialog.findViewById( + R.id.input_setting_keys_value_group); + RadioButton customRadioButton = accessibilityKeyDialog.findViewById( + R.id.input_setting_keys_value_custom); + TextView customValueTextView = accessibilityKeyDialog.findViewById( + R.id.input_setting_keys_value_custom_value); + SeekBar customProgressBar = accessibilityKeyDialog.findViewById( + R.id.input_setting_keys_value_custom_slider); + TextView titleTextView = accessibilityKeyDialog.findViewById( + R.id.input_setting_keys_dialog_title); + TextView subTitleTextView = accessibilityKeyDialog.findViewById( + R.id.input_setting_keys_dialog_subtitle); + titleTextView.setText(titleRes); + subTitleTextView.setText(subtitleRes); + + customProgressBar.incrementProgressBy(CUSTOM_PROGRESS_INTERVAL); + customProgressBar.setProgress(1); + View customValueView = accessibilityKeyDialog.findViewById( + R.id.input_setting_keys_custom_value_option); + customValueView.setOnClickListener(l -> customRadioButton.performClick()); + customRadioButton.setOnCheckedChangeListener((buttonView, isChecked) -> { + if (isChecked) { + cannedValueRadioGroup.clearCheck(); + } + customValueTextView.setVisibility(isChecked ? View.VISIBLE : View.GONE); + customValueTextView.setText( + progressToThresholdInSecond(customProgressBar.getProgress())); + customProgressBar.setVisibility(isChecked ? View.VISIBLE : View.GONE); + buttonView.setChecked(isChecked); + }); + cannedValueRadioGroup.setOnCheckedChangeListener( + (group, checkedId) -> customRadioButton.setChecked(false)); + customProgressBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + customValueTextView.setText(progressToThresholdInSecond(progress)); + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + } + }); + initStateBasedOnThreshold(cannedValueRadioGroup, customRadioButton, customValueTextView, + customProgressBar); + }); + + final Window window = accessibilityKeyDialog.getWindow(); + window.setType(TYPE_SYSTEM_DIALOG); + + return accessibilityKeyDialog; + } + + private static String progressToThresholdInSecond(int progress) { + return String.valueOf((double) progress * CUSTOM_PROGRESS_INTERVAL + / MILLISECOND_IN_SECONDS); + } + + private void initStateBasedOnThreshold(RadioGroup cannedValueRadioGroup, + RadioButton customRadioButton, TextView customValueTextView, + SeekBar customProgressBar) { + int inputSettingKeysThreshold = getInputSettingKeysValue(); + switch (inputSettingKeysThreshold) { + case 600 -> cannedValueRadioGroup.check(R.id.input_setting_keys_value_600); + case 400 -> cannedValueRadioGroup.check(R.id.input_setting_keys_value_400); + case 0, 200 -> cannedValueRadioGroup.check(R.id.input_setting_keys_value_200); + default -> { + customValueTextView.setText( + String.valueOf( + (double) inputSettingKeysThreshold / MILLISECOND_IN_SECONDS)); + customProgressBar.setProgress(inputSettingKeysThreshold / CUSTOM_PROGRESS_INTERVAL); + customRadioButton.setChecked(true); + } + } + } +} diff --git a/src/com/android/settings/inputmethod/KeyboardAccessibilitySlowKeysController.java b/src/com/android/settings/inputmethod/KeyboardAccessibilitySlowKeysController.java index 451742ff2d4..f3d74075846 100644 --- a/src/com/android/settings/inputmethod/KeyboardAccessibilitySlowKeysController.java +++ b/src/com/android/settings/inputmethod/KeyboardAccessibilitySlowKeysController.java @@ -16,7 +16,6 @@ package com.android.settings.inputmethod; -import static android.app.settings.SettingsEnums.ACTION_SLOW_KEYS_CUSTOM_VALUE_CHANGE; import static android.app.settings.SettingsEnums.ACTION_SLOW_KEYS_DISABLED; import static android.app.settings.SettingsEnums.ACTION_SLOW_KEYS_ENABLED; @@ -32,20 +31,19 @@ import androidx.lifecycle.LifecycleObserver; import androidx.preference.Preference; import androidx.preference.PreferenceScreen; -import com.android.settings.R; import com.android.settingslib.PrimarySwitchPreference; public class KeyboardAccessibilitySlowKeysController extends InputSettingPreferenceController implements LifecycleObserver { public static final int SLOW_KEYS_THRESHOLD = 500; + private static final String KEY_TAG = "slow_keys_dialog_tag"; @Nullable private PrimarySwitchPreference mPrimarySwitchPreference; public KeyboardAccessibilitySlowKeysController(@NonNull Context context, @NonNull String key) { super(context, key); - constructDialog(context, R.string.slow_keys, R.string.slow_keys_summary); } @Override @@ -90,12 +88,11 @@ public class KeyboardAccessibilitySlowKeysController extends @Override public boolean handlePreferenceTreeClick(@NonNull Preference preference) { - if (!TextUtils.equals(preference.getKey(), getPreferenceKey())) { + if (!TextUtils.equals(preference.getKey(), getPreferenceKey()) + || mFragmentManager == null) { return false; } - if (mAlertDialog != null) { - mAlertDialog.show(); - } + KeyboardAccessibilitySlowKeysDialogFragment.getInstance().show(mFragmentManager, KEY_TAG); return true; } @@ -103,15 +100,4 @@ public class KeyboardAccessibilitySlowKeysController extends protected void updateInputSettingKeysValue(int thresholdTimeMillis) { InputSettings.setAccessibilitySlowKeysThreshold(mContext, thresholdTimeMillis); } - - @Override - protected void onCustomValueUpdated(int thresholdTimeMillis) { - mMetricsFeatureProvider.action(mContext, - ACTION_SLOW_KEYS_CUSTOM_VALUE_CHANGE, thresholdTimeMillis); - } - - @Override - protected int getInputSettingKeysValue() { - return InputSettings.getAccessibilitySlowKeysThreshold(mContext); - } } diff --git a/src/com/android/settings/inputmethod/KeyboardAccessibilitySlowKeysDialogFragment.java b/src/com/android/settings/inputmethod/KeyboardAccessibilitySlowKeysDialogFragment.java new file mode 100644 index 00000000000..fa3b6858cd5 --- /dev/null +++ b/src/com/android/settings/inputmethod/KeyboardAccessibilitySlowKeysDialogFragment.java @@ -0,0 +1,54 @@ +/* + * 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.app.settings.SettingsEnums.ACTION_SLOW_KEYS_CUSTOM_VALUE_CHANGE; + +import android.hardware.input.InputSettings; +import android.os.Bundle; + +import com.android.settings.R; + +public class KeyboardAccessibilitySlowKeysDialogFragment extends + KeyboardAccessibilityKeysDialogFragment { + + static KeyboardAccessibilitySlowKeysDialogFragment getInstance() { + final KeyboardAccessibilitySlowKeysDialogFragment result = + new KeyboardAccessibilitySlowKeysDialogFragment(); + Bundle bundle = new Bundle(); + bundle.putInt(EXTRA_TITLE_RES, R.string.slow_keys); + bundle.putInt(EXTRA_SUBTITLE_RES, R.string.slow_keys_summary); + result.setArguments(bundle); + return result; + } + + @Override + protected void updateInputSettingKeysValue(int thresholdTimeMillis) { + InputSettings.setAccessibilitySlowKeysThreshold(getContext(), thresholdTimeMillis); + } + + @Override + protected void onCustomValueUpdated(int thresholdTimeMillis) { + mMetricsFeatureProvider.action(getContext(), + ACTION_SLOW_KEYS_CUSTOM_VALUE_CHANGE, thresholdTimeMillis); + } + + @Override + protected int getInputSettingKeysValue() { + return InputSettings.getAccessibilitySlowKeysThreshold(getContext()); + } +} diff --git a/src/com/android/settings/inputmethod/PhysicalKeyboardA11yFragment.java b/src/com/android/settings/inputmethod/PhysicalKeyboardA11yFragment.java index 117aadbe4c8..67a808f48fa 100644 --- a/src/com/android/settings/inputmethod/PhysicalKeyboardA11yFragment.java +++ b/src/com/android/settings/inputmethod/PhysicalKeyboardA11yFragment.java @@ -51,6 +51,8 @@ public class PhysicalKeyboardA11yFragment extends DashboardFragment super.onAttach(context); mInputManager = Preconditions.checkNotNull(getActivity() .getSystemService(InputManager.class)); + use(KeyboardAccessibilitySlowKeysController.class).setFragment(this /*parent*/); + use(KeyboardAccessibilityBounceKeysController.class).setFragment(this /*parent*/); } @Override diff --git a/tests/robotests/src/com/android/settings/inputmethod/KeyboardAccessibilityBounceKeysControllerTest.java b/tests/robotests/src/com/android/settings/inputmethod/KeyboardAccessibilityBounceKeysControllerTest.java index b385b2f32a5..d9aa79c19b7 100644 --- a/tests/robotests/src/com/android/settings/inputmethod/KeyboardAccessibilityBounceKeysControllerTest.java +++ b/tests/robotests/src/com/android/settings/inputmethod/KeyboardAccessibilityBounceKeysControllerTest.java @@ -18,6 +18,9 @@ package com.android.settings.inputmethod; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.Context; @@ -25,12 +28,12 @@ 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 android.widget.RadioGroup; -import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; +import androidx.fragment.app.FragmentTransaction; import androidx.preference.Preference; -import com.android.settings.R; import com.android.settings.core.BasePreferenceController; import com.android.settings.keyboard.Flags; import com.android.settings.testutils.shadow.ShadowAlertDialogCompat; @@ -45,10 +48,13 @@ import org.mockito.junit.MockitoRule; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; -import org.robolectric.shadows.ShadowLooper; +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; @RunWith(RobolectricTestRunner.class) @Config(shadows = { + KeyboardAccessibilityBounceKeysControllerTest + .ShadowKeyboardAccessibilityBounceKeysDialogFragment.class, com.android.settings.testutils.shadow.ShadowFragment.class, ShadowAlertDialogCompat.class, }) @@ -60,6 +66,15 @@ public class KeyboardAccessibilityBounceKeysControllerTest { private static final String PREFERENCE_KEY = "keyboard_a11y_page_bounce_keys"; @Mock private Preference mPreference; + @Mock + private Fragment mFragment; + @Mock + private FragmentManager mFragmentManager; + @Mock + private FragmentTransaction mFragmentTransaction; + @Mock + private KeyboardAccessibilityBounceKeysDialogFragment + mKeyboardAccessibilityBounceKeysDialogFragment; private Context mContext; private KeyboardAccessibilityBounceKeysController mKeyboardAccessibilityBounceKeysController; @@ -71,6 +86,11 @@ public class KeyboardAccessibilityBounceKeysControllerTest { mContext, PREFERENCE_KEY); when(mPreference.getKey()).thenReturn(PREFERENCE_KEY); + when(mFragment.getParentFragmentManager()).thenReturn(mFragmentManager); + when(mFragmentManager.beginTransaction()).thenReturn(mFragmentTransaction); + mKeyboardAccessibilityBounceKeysController.setFragment(mFragment); + ShadowKeyboardAccessibilityBounceKeysDialogFragment.setInstance( + mKeyboardAccessibilityBounceKeysDialogFragment); } @Test @@ -107,23 +127,25 @@ public class KeyboardAccessibilityBounceKeysControllerTest { public void handlePreferenceTreeClick_dialogShows() { mKeyboardAccessibilityBounceKeysController.handlePreferenceTreeClick(mPreference); - AlertDialog alertDialog = ShadowAlertDialogCompat.getLatestAlertDialog(); - - assertThat(alertDialog.isShowing()).isTrue(); + verify(mKeyboardAccessibilityBounceKeysDialogFragment).show(any(FragmentManager.class), + anyString()); } - @Test - public void handlePreferenceTreeClick_performClickOn200_updatesBounceKeysThreshold() { - mKeyboardAccessibilityBounceKeysController.handlePreferenceTreeClick(mPreference); - AlertDialog alertDialog = ShadowAlertDialogCompat.getLatestAlertDialog(); - RadioGroup radioGroup = alertDialog.findViewById(R.id.input_setting_keys_value_group); - radioGroup.check(R.id.input_setting_keys_value_200); + /** + * Note: Actually, shadow of KeyboardAccessibilitySlowKeysDialogFragment will not be used. + * Instance that returned with {@link #getInstance} should be set with {@link #setInstance} + */ + @Implements(KeyboardAccessibilityBounceKeysDialogFragment.class) + public static class ShadowKeyboardAccessibilityBounceKeysDialogFragment { + static KeyboardAccessibilityBounceKeysDialogFragment sInstance = null; - alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).performClick(); - ShadowLooper.idleMainLooper(); + @Implementation + protected static KeyboardAccessibilityBounceKeysDialogFragment getInstance() { + return sInstance; + } - assertThat(alertDialog.isShowing()).isFalse(); - int threshold = InputSettings.getAccessibilityBounceKeysThreshold(mContext); - assertThat(threshold).isEqualTo(200); + public static void setInstance(KeyboardAccessibilityBounceKeysDialogFragment instance) { + sInstance = instance; + } } } diff --git a/tests/robotests/src/com/android/settings/inputmethod/KeyboardAccessibilityBounceKeysDialogFragmentTest.java b/tests/robotests/src/com/android/settings/inputmethod/KeyboardAccessibilityBounceKeysDialogFragmentTest.java new file mode 100644 index 00000000000..8e5f84e2191 --- /dev/null +++ b/tests/robotests/src/com/android/settings/inputmethod/KeyboardAccessibilityBounceKeysDialogFragmentTest.java @@ -0,0 +1,81 @@ +/* + * 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 com.android.settings.inputmethod.KeyboardAccessibilityKeysDialogFragment.EXTRA_SUBTITLE_RES; +import static com.android.settings.inputmethod.KeyboardAccessibilityKeysDialogFragment.EXTRA_TITLE_RES; + +import static com.google.common.truth.Truth.assertThat; + +import android.app.AlertDialog; +import android.hardware.input.InputSettings; +import android.os.Bundle; +import android.widget.RadioGroup; + +import androidx.fragment.app.testing.FragmentScenario; +import androidx.lifecycle.Lifecycle; +import androidx.test.core.app.ApplicationProvider; + +import com.android.settings.R; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.shadows.ShadowLooper; + +@RunWith(RobolectricTestRunner.class) +public class KeyboardAccessibilityBounceKeysDialogFragmentTest { + private AlertDialog mAlertDialog; + + @Before + public void setUp() { + Bundle bundle = new Bundle(); + bundle.putInt(EXTRA_TITLE_RES, R.string.bounce_keys_dialog_title); + bundle.putInt(EXTRA_SUBTITLE_RES, R.string.bounce_keys_dialog_subtitle); + + FragmentScenario mFragmentScenario = + FragmentScenario.launch( + KeyboardAccessibilityBounceKeysDialogFragment.class, + bundle, + R.style.Theme_AlertDialog_SettingsLib, + Lifecycle.State.INITIALIZED); + mFragmentScenario.moveToState(Lifecycle.State.RESUMED); + + mFragmentScenario.onFragment(fragment -> { + assertThat(fragment.getDialog()).isNotNull(); + assertThat(fragment.requireDialog().isShowing()).isTrue(); + assertThat(fragment.requireDialog()).isInstanceOf(AlertDialog.class); + mAlertDialog = (AlertDialog) fragment.requireDialog(); + }); + } + + @Test + public void handlePreferenceTreeClick_performClickOn200_updatesBounceKeysThreshold() { + assertThat(mAlertDialog.isShowing()).isTrue(); + RadioGroup radioGroup = mAlertDialog.findViewById(R.id.input_setting_keys_value_group); + radioGroup.check(R.id.input_setting_keys_value_200); + + mAlertDialog.getButton(AlertDialog.BUTTON_POSITIVE).performClick(); + ShadowLooper.idleMainLooper(); + + assertThat(mAlertDialog.isShowing()).isFalse(); + int threshold = InputSettings.getAccessibilityBounceKeysThreshold( + ApplicationProvider.getApplicationContext()); + assertThat(threshold).isEqualTo(200); + } +} diff --git a/tests/robotests/src/com/android/settings/inputmethod/KeyboardAccessibilitySlowKeysControllerTest.java b/tests/robotests/src/com/android/settings/inputmethod/KeyboardAccessibilitySlowKeysControllerTest.java index 9f82b759cdc..137f15ba9d8 100644 --- a/tests/robotests/src/com/android/settings/inputmethod/KeyboardAccessibilitySlowKeysControllerTest.java +++ b/tests/robotests/src/com/android/settings/inputmethod/KeyboardAccessibilitySlowKeysControllerTest.java @@ -18,6 +18,9 @@ package com.android.settings.inputmethod; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.Context; @@ -25,12 +28,12 @@ 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 android.widget.RadioGroup; -import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; +import androidx.fragment.app.FragmentTransaction; import androidx.preference.Preference; -import com.android.settings.R; import com.android.settings.core.BasePreferenceController; import com.android.settings.keyboard.Flags; import com.android.settings.testutils.shadow.ShadowAlertDialogCompat; @@ -45,10 +48,13 @@ import org.mockito.junit.MockitoRule; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; -import org.robolectric.shadows.ShadowLooper; +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; @RunWith(RobolectricTestRunner.class) @Config(shadows = { + KeyboardAccessibilitySlowKeysControllerTest + .ShadowKeyboardAccessibilitySlowKeysDialogFragment.class, com.android.settings.testutils.shadow.ShadowFragment.class, ShadowAlertDialogCompat.class, }) @@ -60,6 +66,15 @@ public class KeyboardAccessibilitySlowKeysControllerTest { private static final String PREFERENCE_KEY = "keyboard_a11y_page_slow_keys"; @Mock private Preference mPreference; + @Mock + private Fragment mFragment; + @Mock + private FragmentManager mFragmentManager; + @Mock + private FragmentTransaction mFragmentTransaction; + @Mock + private KeyboardAccessibilitySlowKeysDialogFragment + mKeyboardAccessibilitySlowKeysDialogFragment; private Context mContext; private KeyboardAccessibilitySlowKeysController mKeyboardAccessibilitySlowKeysController; @@ -71,6 +86,11 @@ public class KeyboardAccessibilitySlowKeysControllerTest { mContext, PREFERENCE_KEY); when(mPreference.getKey()).thenReturn(PREFERENCE_KEY); + when(mFragment.getParentFragmentManager()).thenReturn(mFragmentManager); + when(mFragmentManager.beginTransaction()).thenReturn(mFragmentTransaction); + mKeyboardAccessibilitySlowKeysController.setFragment(mFragment); + ShadowKeyboardAccessibilitySlowKeysDialogFragment.setInstance( + mKeyboardAccessibilitySlowKeysDialogFragment); } @Test @@ -107,23 +127,25 @@ public class KeyboardAccessibilitySlowKeysControllerTest { public void handlePreferenceTreeClick_dialogShows() { mKeyboardAccessibilitySlowKeysController.handlePreferenceTreeClick(mPreference); - AlertDialog alertDialog = ShadowAlertDialogCompat.getLatestAlertDialog(); - - assertThat(alertDialog.isShowing()).isTrue(); + verify(mKeyboardAccessibilitySlowKeysDialogFragment).show(any(FragmentManager.class), + anyString()); } - @Test - public void handlePreferenceTreeClick_performClickOn200_updatesSlowKeysThreshold() { - mKeyboardAccessibilitySlowKeysController.handlePreferenceTreeClick(mPreference); - AlertDialog alertDialog = ShadowAlertDialogCompat.getLatestAlertDialog(); - RadioGroup radioGroup = alertDialog.findViewById(R.id.input_setting_keys_value_group); - radioGroup.check(R.id.input_setting_keys_value_200); + /** + * Note: Actually, shadow of KeyboardAccessibilitySlowKeysDialogFragment will not be used. + * Instance that returned with {@link #getInstance} should be set with {@link #setInstance} + */ + @Implements(KeyboardAccessibilitySlowKeysDialogFragment.class) + public static class ShadowKeyboardAccessibilitySlowKeysDialogFragment { + static KeyboardAccessibilitySlowKeysDialogFragment sInstance = null; - alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).performClick(); - ShadowLooper.idleMainLooper(); + @Implementation + protected static KeyboardAccessibilitySlowKeysDialogFragment getInstance() { + return sInstance; + } - assertThat(alertDialog.isShowing()).isFalse(); - int threshold = InputSettings.getAccessibilitySlowKeysThreshold(mContext); - assertThat(threshold).isEqualTo(200); + public static void setInstance(KeyboardAccessibilitySlowKeysDialogFragment instance) { + sInstance = instance; + } } } diff --git a/tests/robotests/src/com/android/settings/inputmethod/KeyboardAccessibilitySlowKeysDialogFragmentTest.java b/tests/robotests/src/com/android/settings/inputmethod/KeyboardAccessibilitySlowKeysDialogFragmentTest.java new file mode 100644 index 00000000000..3a3010e7293 --- /dev/null +++ b/tests/robotests/src/com/android/settings/inputmethod/KeyboardAccessibilitySlowKeysDialogFragmentTest.java @@ -0,0 +1,81 @@ +/* + * 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 com.android.settings.inputmethod.KeyboardAccessibilityKeysDialogFragment.EXTRA_SUBTITLE_RES; +import static com.android.settings.inputmethod.KeyboardAccessibilityKeysDialogFragment.EXTRA_TITLE_RES; + +import static com.google.common.truth.Truth.assertThat; + +import android.app.AlertDialog; +import android.hardware.input.InputSettings; +import android.os.Bundle; +import android.widget.RadioGroup; + +import androidx.fragment.app.testing.FragmentScenario; +import androidx.lifecycle.Lifecycle; +import androidx.test.core.app.ApplicationProvider; + +import com.android.settings.R; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.shadows.ShadowLooper; + +@RunWith(RobolectricTestRunner.class) +public class KeyboardAccessibilitySlowKeysDialogFragmentTest { + private AlertDialog mAlertDialog; + + @Before + public void setUp() { + Bundle bundle = new Bundle(); + bundle.putInt(EXTRA_TITLE_RES, R.string.slow_keys); + bundle.putInt(EXTRA_SUBTITLE_RES, R.string.slow_keys_summary); + + FragmentScenario mFragmentScenario = + FragmentScenario.launch( + KeyboardAccessibilitySlowKeysDialogFragment.class, + bundle, + R.style.Theme_AlertDialog_SettingsLib, + Lifecycle.State.INITIALIZED); + mFragmentScenario.moveToState(Lifecycle.State.RESUMED); + + mFragmentScenario.onFragment(fragment -> { + assertThat(fragment.getDialog()).isNotNull(); + assertThat(fragment.requireDialog().isShowing()).isTrue(); + assertThat(fragment.requireDialog()).isInstanceOf(AlertDialog.class); + mAlertDialog = (AlertDialog) fragment.requireDialog(); + }); + } + + @Test + public void handlePreferenceTreeClick_performClickOn200_updatesSlowKeysThreshold() { + assertThat(mAlertDialog.isShowing()).isTrue(); + RadioGroup radioGroup = mAlertDialog.findViewById(R.id.input_setting_keys_value_group); + radioGroup.check(R.id.input_setting_keys_value_200); + + mAlertDialog.getButton(AlertDialog.BUTTON_POSITIVE).performClick(); + ShadowLooper.idleMainLooper(); + + assertThat(mAlertDialog.isShowing()).isFalse(); + int threshold = InputSettings.getAccessibilitySlowKeysThreshold( + ApplicationProvider.getApplicationContext()); + assertThat(threshold).isEqualTo(200); + } +}