| /* | |
| * 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.Button; | |
| 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.Locale; | |
| 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 static final String EXTRA_SEEKBAR_CONTENT_DESCRIPTION = | |
| "extra_seekbar_content_description_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); | |
| int seekbarContentDescriptionRes = getArguments().getInt(EXTRA_SEEKBAR_CONTENT_DESCRIPTION); | |
| 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); | |
| Button doneButton = dialoglayout.findViewById(R.id.done_button); | |
| doneButton.setOnClickListener(v -> { | |
| 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); | |
| dismiss(); | |
| }); | |
| Button cancelButton = dialoglayout.findViewById(R.id.cancel_button); | |
| cancelButton.setOnClickListener(v -> { | |
| 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); | |
| View seekbarView = accessibilityKeyDialog.findViewById( | |
| R.id.input_setting_keys_custom_seekbar_layout); | |
| 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); | |
| if (seekbarContentDescriptionRes != 0) { | |
| customProgressBar.setContentDescription( | |
| getContext().getString(seekbarContentDescriptionRes)); | |
| } | |
| 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())); | |
| seekbarView.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) { | |
| String threshold = progressToThresholdInSecond(progress); | |
| customValueTextView.setText(threshold); | |
| customProgressBar.setContentDescription(threshold); | |
| } | |
| @Override | |
| public void onStartTrackingTouch(SeekBar seekBar) { | |
| } | |
| @Override | |
| public void onStopTrackingTouch(SeekBar seekBar) { | |
| } | |
| }); | |
| if (cannedValueRadioGroup.getCheckedRadioButtonId() == -1 | |
| && !customRadioButton.isChecked()) { | |
| //if canned radio group and custom are not select, initial check state from input | |
| // setting | |
| initStateBasedOnThreshold(cannedValueRadioGroup, customRadioButton, | |
| customValueTextView, | |
| customProgressBar, seekbarView); | |
| } else if (customRadioButton.isChecked()) { | |
| cannedValueRadioGroup.clearCheck(); | |
| customRadioButton.setChecked(true); | |
| customValueTextView.setVisibility(View.VISIBLE); | |
| customValueTextView.setText( | |
| progressToThresholdInSecond(customProgressBar.getProgress())); | |
| seekbarView.setVisibility(View.VISIBLE); | |
| } | |
| }); | |
| final Window window = accessibilityKeyDialog.getWindow(); | |
| window.setType(TYPE_SYSTEM_DIALOG); | |
| return accessibilityKeyDialog; | |
| } | |
| private String progressToThresholdInSecond(int progress) { | |
| return (double) progress * CUSTOM_PROGRESS_INTERVAL | |
| / MILLISECOND_IN_SECONDS + " " + TimeUnit.SECONDS.name().toLowerCase( | |
| Locale.getDefault()); | |
| } | |
| private void initStateBasedOnThreshold(RadioGroup cannedValueRadioGroup, | |
| RadioButton customRadioButton, TextView customValueTextView, | |
| SeekBar customProgressBar, View seekbarView) { | |
| 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); | |
| } | |
| } | |
| seekbarView.setVisibility(customRadioButton.isChecked() ? View.VISIBLE : View.GONE); | |
| } | |
| } |