1 /* 2 * Copyright 2025 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.settings.inputmethod; 18 19 import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG; 20 21 import android.app.Activity; 22 import android.app.AlertDialog; 23 import android.app.Dialog; 24 import android.os.Bundle; 25 import android.view.LayoutInflater; 26 import android.view.View; 27 import android.view.Window; 28 import android.widget.Button; 29 import android.widget.RadioButton; 30 import android.widget.RadioGroup; 31 import android.widget.SeekBar; 32 import android.widget.TextView; 33 34 import androidx.fragment.app.DialogFragment; 35 36 import com.android.settings.R; 37 import com.android.settings.overlay.FeatureFactory; 38 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; 39 40 import org.jspecify.annotations.Nullable; 41 42 import java.util.Locale; 43 import java.util.concurrent.TimeUnit; 44 45 public abstract class KeyboardAccessibilityKeysDialogFragment extends DialogFragment { 46 private static final int CUSTOM_PROGRESS_INTERVAL = 100; 47 private static final long MILLISECOND_IN_SECONDS = TimeUnit.SECONDS.toMillis(1); 48 protected static final String EXTRA_TITLE_RES = "extra_title_res"; 49 protected static final String EXTRA_SUBTITLE_RES = "extra_subtitle_res"; 50 protected static final String EXTRA_SEEKBAR_CONTENT_DESCRIPTION = 51 "extra_seekbar_content_description_res"; 52 53 protected final MetricsFeatureProvider mMetricsFeatureProvider; 54 KeyboardAccessibilityKeysDialogFragment()55 public KeyboardAccessibilityKeysDialogFragment() { 56 mMetricsFeatureProvider = FeatureFactory.getFeatureFactory().getMetricsFeatureProvider(); 57 } 58 updateInputSettingKeysValue(int thresholdTimeMillis)59 protected void updateInputSettingKeysValue(int thresholdTimeMillis) { 60 } 61 onCustomValueUpdated(int thresholdTimeMillis)62 protected void onCustomValueUpdated(int thresholdTimeMillis) { 63 } 64 getInputSettingKeysValue()65 protected int getInputSettingKeysValue() { 66 return 0; 67 } 68 69 @Override onCreateDialog(@ullable Bundle savedInstanceState)70 public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { 71 super.onCreateDialog(savedInstanceState); 72 int titleRes = getArguments().getInt(EXTRA_TITLE_RES); 73 int subtitleRes = getArguments().getInt(EXTRA_SUBTITLE_RES); 74 int seekbarContentDescriptionRes = getArguments().getInt(EXTRA_SEEKBAR_CONTENT_DESCRIPTION); 75 76 Activity activity = getActivity(); 77 View dialoglayout = 78 LayoutInflater.from(activity).inflate( 79 R.layout.dialog_keyboard_a11y_input_setting_keys, null); 80 AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(activity); 81 dialogBuilder.setView(dialoglayout); 82 Button doneButton = dialoglayout.findViewById(R.id.done_button); 83 doneButton.setOnClickListener(v -> { 84 RadioGroup radioGroup = 85 dialoglayout.findViewById( 86 R.id.input_setting_keys_value_group); 87 SeekBar seekbar = dialoglayout.findViewById( 88 R.id.input_setting_keys_value_custom_slider); 89 RadioButton customRadioButton = dialoglayout.findViewById( 90 R.id.input_setting_keys_value_custom); 91 int threshold; 92 if (customRadioButton.isChecked()) { 93 threshold = seekbar.getProgress() * CUSTOM_PROGRESS_INTERVAL; 94 } else { 95 int checkedRadioButtonId = radioGroup.getCheckedRadioButtonId(); 96 if (checkedRadioButtonId == R.id.input_setting_keys_value_600) { 97 threshold = 600; 98 } else if (checkedRadioButtonId 99 == R.id.input_setting_keys_value_400) { 100 threshold = 400; 101 } else if (checkedRadioButtonId 102 == R.id.input_setting_keys_value_200) { 103 threshold = 200; 104 } else { 105 threshold = 0; 106 } 107 } 108 updateInputSettingKeysValue(threshold); 109 onCustomValueUpdated(threshold); 110 dismiss(); 111 }); 112 113 Button cancelButton = dialoglayout.findViewById(R.id.cancel_button); 114 cancelButton.setOnClickListener(v -> { 115 dismiss(); 116 }); 117 AlertDialog accessibilityKeyDialog = dialogBuilder.create(); 118 accessibilityKeyDialog.setOnShowListener(dialog -> { 119 RadioGroup cannedValueRadioGroup = accessibilityKeyDialog.findViewById( 120 R.id.input_setting_keys_value_group); 121 RadioButton customRadioButton = accessibilityKeyDialog.findViewById( 122 R.id.input_setting_keys_value_custom); 123 TextView customValueTextView = accessibilityKeyDialog.findViewById( 124 R.id.input_setting_keys_value_custom_value); 125 View seekbarView = accessibilityKeyDialog.findViewById( 126 R.id.input_setting_keys_custom_seekbar_layout); 127 SeekBar customProgressBar = accessibilityKeyDialog.findViewById( 128 R.id.input_setting_keys_value_custom_slider); 129 TextView titleTextView = accessibilityKeyDialog.findViewById( 130 R.id.input_setting_keys_dialog_title); 131 TextView subTitleTextView = accessibilityKeyDialog.findViewById( 132 R.id.input_setting_keys_dialog_subtitle); 133 titleTextView.setText(titleRes); 134 subTitleTextView.setText(subtitleRes); 135 136 if (seekbarContentDescriptionRes != 0) { 137 customProgressBar.setContentDescription( 138 getContext().getString(seekbarContentDescriptionRes)); 139 } 140 customProgressBar.incrementProgressBy(CUSTOM_PROGRESS_INTERVAL); 141 customProgressBar.setProgress(1); 142 View customValueView = accessibilityKeyDialog.findViewById( 143 R.id.input_setting_keys_custom_value_option); 144 customValueView.setOnClickListener(l -> customRadioButton.performClick()); 145 customRadioButton.setOnCheckedChangeListener((buttonView, isChecked) -> { 146 if (isChecked) { 147 cannedValueRadioGroup.clearCheck(); 148 } 149 customValueTextView.setVisibility(isChecked ? View.VISIBLE : View.GONE); 150 customValueTextView.setText( 151 progressToThresholdInSecond(customProgressBar.getProgress())); 152 seekbarView.setVisibility(isChecked ? View.VISIBLE : View.GONE); 153 buttonView.setChecked(isChecked); 154 }); 155 cannedValueRadioGroup.setOnCheckedChangeListener( 156 (group, checkedId) -> customRadioButton.setChecked(false)); 157 customProgressBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { 158 @Override 159 public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { 160 String threshold = progressToThresholdInSecond(progress); 161 customValueTextView.setText(threshold); 162 customProgressBar.setContentDescription(threshold); 163 } 164 165 @Override 166 public void onStartTrackingTouch(SeekBar seekBar) { 167 } 168 169 @Override 170 public void onStopTrackingTouch(SeekBar seekBar) { 171 } 172 }); 173 if (cannedValueRadioGroup.getCheckedRadioButtonId() == -1 174 && !customRadioButton.isChecked()) { 175 //if canned radio group and custom are not select, initial check state from input 176 // setting 177 initStateBasedOnThreshold(cannedValueRadioGroup, customRadioButton, 178 customValueTextView, 179 customProgressBar, seekbarView); 180 } else if (customRadioButton.isChecked()) { 181 cannedValueRadioGroup.clearCheck(); 182 customRadioButton.setChecked(true); 183 customValueTextView.setVisibility(View.VISIBLE); 184 customValueTextView.setText( 185 progressToThresholdInSecond(customProgressBar.getProgress())); 186 seekbarView.setVisibility(View.VISIBLE); 187 } 188 }); 189 190 final Window window = accessibilityKeyDialog.getWindow(); 191 window.setType(TYPE_SYSTEM_DIALOG); 192 193 return accessibilityKeyDialog; 194 } 195 progressToThresholdInSecond(int progress)196 private String progressToThresholdInSecond(int progress) { 197 return (double) progress * CUSTOM_PROGRESS_INTERVAL 198 / MILLISECOND_IN_SECONDS + " " + TimeUnit.SECONDS.name().toLowerCase( 199 Locale.getDefault()); 200 } 201 initStateBasedOnThreshold(RadioGroup cannedValueRadioGroup, RadioButton customRadioButton, TextView customValueTextView, SeekBar customProgressBar, View seekbarView)202 private void initStateBasedOnThreshold(RadioGroup cannedValueRadioGroup, 203 RadioButton customRadioButton, TextView customValueTextView, 204 SeekBar customProgressBar, View seekbarView) { 205 int inputSettingKeysThreshold = getInputSettingKeysValue(); 206 switch (inputSettingKeysThreshold) { 207 case 600 -> cannedValueRadioGroup.check(R.id.input_setting_keys_value_600); 208 case 400 -> cannedValueRadioGroup.check(R.id.input_setting_keys_value_400); 209 case 0, 200 -> cannedValueRadioGroup.check(R.id.input_setting_keys_value_200); 210 default -> { 211 customValueTextView.setText( 212 String.valueOf( 213 (double) inputSettingKeysThreshold / MILLISECOND_IN_SECONDS)); 214 customProgressBar.setProgress(inputSettingKeysThreshold / CUSTOM_PROGRESS_INTERVAL); 215 customRadioButton.setChecked(true); 216 } 217 } 218 seekbarView.setVisibility(customRadioButton.isChecked() ? View.VISIBLE : View.GONE); 219 } 220 } 221