• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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