1 /* 2 * Copyright (C) 2022 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.app.settings.SettingsEnums; 25 import android.content.Context; 26 import android.graphics.drawable.Drawable; 27 import android.hardware.input.InputManager; 28 import android.os.Bundle; 29 import android.text.Spannable; 30 import android.text.SpannableString; 31 import android.text.style.ForegroundColorSpan; 32 import android.view.KeyEvent; 33 import android.view.LayoutInflater; 34 import android.view.View; 35 import android.view.ViewGroup; 36 import android.view.Window; 37 import android.widget.AdapterView; 38 import android.widget.BaseAdapter; 39 import android.widget.Button; 40 import android.widget.ImageView; 41 import android.widget.ListView; 42 import android.widget.TextView; 43 44 import androidx.core.graphics.drawable.DrawableCompat; 45 import androidx.fragment.app.DialogFragment; 46 import androidx.preference.Preference; 47 48 import com.android.settings.R; 49 import com.android.settings.overlay.FeatureFactory; 50 import com.android.settingslib.Utils; 51 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; 52 53 import java.util.ArrayList; 54 import java.util.Arrays; 55 import java.util.HashMap; 56 import java.util.List; 57 import java.util.Map; 58 59 public class ModifierKeysPickerDialogFragment extends DialogFragment { 60 61 static final String DEFAULT_KEY = "default_key"; 62 static final String SELECTION_KEY = "delection_key"; 63 64 private Preference mPreference; 65 private String mKeyDefaultName; 66 private String mKeyFocus; 67 private Activity mActivity; 68 private KeyboardSettingsFeatureProvider mFeatureProvider; 69 private Drawable mActionKeyDrawable; 70 private TextView mLeftBracket; 71 private TextView mRightBracket; 72 private ImageView mActionKeyIcon; 73 private MetricsFeatureProvider mMetricsFeatureProvider; 74 75 private List<int[]> mRemappableKeyList = 76 new ArrayList<>(Arrays.asList( 77 new int[]{KeyEvent.KEYCODE_CAPS_LOCK}, 78 new int[]{KeyEvent.KEYCODE_CTRL_LEFT, KeyEvent.KEYCODE_CTRL_RIGHT}, 79 new int[]{KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_META_RIGHT}, 80 new int[]{KeyEvent.KEYCODE_ALT_LEFT, KeyEvent.KEYCODE_ALT_RIGHT})); 81 82 private Map<String, int[]> mRemappableKeyMap = new HashMap<>(); 83 ModifierKeysPickerDialogFragment()84 public ModifierKeysPickerDialogFragment() {} 85 86 @Override onSaveInstanceState(Bundle savedInstanceState)87 public void onSaveInstanceState(Bundle savedInstanceState) { 88 savedInstanceState.putString(SELECTION_KEY, mKeyFocus); 89 super.onSaveInstanceState(savedInstanceState); 90 } 91 92 @Override onCreateDialog(Bundle savedInstanceState)93 public Dialog onCreateDialog(Bundle savedInstanceState) { 94 super.onCreateDialog(savedInstanceState); 95 96 mActivity = getActivity(); 97 FeatureFactory featureFactory = FeatureFactory.getFeatureFactory(); 98 mMetricsFeatureProvider = featureFactory.getMetricsFeatureProvider(); 99 mFeatureProvider = featureFactory.getKeyboardSettingsFeatureProvider(); 100 InputManager inputManager = mActivity.getSystemService(InputManager.class); 101 mKeyDefaultName = getArguments().getString(DEFAULT_KEY); 102 mKeyFocus = getArguments().getString(SELECTION_KEY); 103 if (savedInstanceState != null) { 104 mKeyFocus = savedInstanceState.getString(SELECTION_KEY); 105 } 106 List<String> modifierKeys = new ArrayList<String>(Arrays.asList( 107 mActivity.getString(R.string.modifier_keys_caps_lock), 108 mActivity.getString(R.string.modifier_keys_ctrl), 109 mActivity.getString(R.string.modifier_keys_meta), 110 mActivity.getString(R.string.modifier_keys_alt))); 111 for (int i = 0; i < modifierKeys.size(); i++) { 112 mRemappableKeyMap.put(modifierKeys.get(i), mRemappableKeyList.get(i)); 113 } 114 Drawable drawable = mFeatureProvider.getActionKeyIcon(mActivity); 115 if (drawable != null) { 116 mActionKeyDrawable = DrawableCompat.wrap(drawable); 117 } 118 119 View dialoglayout = 120 LayoutInflater.from(mActivity).inflate(R.layout.modifier_key_picker_dialog, null); 121 AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(mActivity); 122 dialogBuilder.setView(dialoglayout); 123 124 TextView summary = dialoglayout.findViewById(R.id.modifier_key_picker_summary); 125 CharSequence summaryText = mActivity.getString( 126 R.string.modifier_keys_picker_summary, mKeyDefaultName); 127 summary.setText(summaryText); 128 129 ModifierKeyAdapter adapter = new ModifierKeyAdapter(modifierKeys); 130 ListView listView = dialoglayout.findViewById(R.id.modifier_key_picker); 131 listView.setAdapter(adapter); 132 setInitialFocusItem(modifierKeys, adapter); 133 listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { 134 @Override 135 public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) { 136 adapter.setCurrentItem(i); 137 adapter.notifyDataSetChanged(); 138 } 139 }); 140 141 AlertDialog modifierKeyDialog = dialogBuilder.create(); 142 Button doneButton = dialoglayout.findViewById(R.id.modifier_key_done_button); 143 doneButton.setOnClickListener(v -> { 144 String selectedItem = modifierKeys.get(adapter.getCurrentItem()); 145 Spannable itemSummary; 146 logMetricsForRemapping(selectedItem); 147 if (selectedItem.equals(mKeyDefaultName)) { 148 itemSummary = new SpannableString( 149 mActivity.getString(R.string.modifier_keys_default_summary)); 150 itemSummary.setSpan( 151 new ForegroundColorSpan(getColorOfTextColorSecondary()), 152 0, itemSummary.length(), 0); 153 // Set keys to default. 154 int[] keys = mRemappableKeyMap.get(mKeyDefaultName); 155 for (int i = 0; i < keys.length; i++) { 156 inputManager.remapModifierKey(keys[i], keys[i]); 157 } 158 } else { 159 itemSummary = new SpannableString(selectedItem); 160 itemSummary.setSpan( 161 new ForegroundColorSpan(getColorOfMaterialColorPrimary()), 162 0, itemSummary.length(), 0); 163 int[] fromKeys = mRemappableKeyMap.get(mKeyDefaultName); 164 int[] toKeys = mRemappableKeyMap.get(selectedItem); 165 // CAPS_LOCK only one key, so always choose the left key for remapping. 166 if (isKeyCapsLock(mActivity, mKeyDefaultName)) { 167 inputManager.remapModifierKey(fromKeys[0], toKeys[0]); 168 } 169 // Remap KEY_LEFT and KEY_RIGHT to CAPS_LOCK. 170 if (!isKeyCapsLock(mActivity, mKeyDefaultName) 171 && isKeyCapsLock(mActivity, selectedItem)) { 172 inputManager.remapModifierKey(fromKeys[0], toKeys[0]); 173 inputManager.remapModifierKey(fromKeys[1], toKeys[0]); 174 } 175 // Auto handle left and right keys remapping. 176 if (!isKeyCapsLock(mActivity, mKeyDefaultName) 177 && !isKeyCapsLock(mActivity, selectedItem)) { 178 inputManager.remapModifierKey(fromKeys[0], toKeys[0]); 179 inputManager.remapModifierKey(fromKeys[1], toKeys[1]); 180 } 181 } 182 dismiss(); 183 mActivity.recreate(); 184 }); 185 186 Button cancelButton = dialoglayout.findViewById(R.id.modifier_key_cancel_button); 187 cancelButton.setOnClickListener(v -> { 188 dismiss(); 189 }); 190 191 final Window window = modifierKeyDialog.getWindow(); 192 window.setType(TYPE_SYSTEM_DIALOG); 193 194 return modifierKeyDialog; 195 } 196 logMetricsForRemapping(String selectedItem)197 private void logMetricsForRemapping(String selectedItem) { 198 if (mKeyDefaultName.equals("Caps lock")) { 199 mMetricsFeatureProvider.action( 200 mActivity, SettingsEnums.ACTION_FROM_CAPS_LOCK_TO, selectedItem); 201 } 202 203 if (mKeyDefaultName.equals("Ctrl")) { 204 mMetricsFeatureProvider.action( 205 mActivity, SettingsEnums.ACTION_FROM_CTRL_TO, selectedItem); 206 } 207 208 if (mKeyDefaultName.equals("Action key")) { 209 mMetricsFeatureProvider.action( 210 mActivity, SettingsEnums.ACTION_FROM_ACTION_KEY_TO, selectedItem); 211 } 212 213 if (mKeyDefaultName.equals("Alt")) { 214 mMetricsFeatureProvider.action( 215 mActivity, SettingsEnums.ACTION_FROM_ALT_TO, selectedItem); 216 } 217 } 218 setInitialFocusItem( List<String> modifierKeys, ModifierKeyAdapter adapter)219 private void setInitialFocusItem( 220 List<String> modifierKeys, ModifierKeyAdapter adapter) { 221 if (modifierKeys.indexOf(mKeyFocus) == -1) { 222 adapter.setCurrentItem(modifierKeys.indexOf(mKeyDefaultName)); 223 } else { 224 adapter.setCurrentItem(modifierKeys.indexOf(mKeyFocus)); 225 } 226 adapter.notifyDataSetChanged(); 227 } 228 isKeyCapsLock(Context context, String key)229 private static boolean isKeyCapsLock(Context context, String key) { 230 return key.equals(context.getString(R.string.modifier_keys_caps_lock)); 231 } 232 233 class ModifierKeyAdapter extends BaseAdapter { 234 private int mCurrentItem = 0; 235 private List<String> mList; 236 ModifierKeyAdapter(List<String> list)237 ModifierKeyAdapter(List<String> list) { 238 this.mList = list; 239 } 240 241 @Override getCount()242 public int getCount() { 243 return mList.size(); 244 } 245 246 @Override getItem(int i)247 public Object getItem(int i) { 248 return mList.get(i); 249 } 250 251 @Override getItemId(int i)252 public long getItemId(int i) { 253 return i; 254 } 255 256 @Override getView(int i, View view, ViewGroup viewGroup)257 public View getView(int i, View view, ViewGroup viewGroup) { 258 if (view == null) { 259 view = LayoutInflater.from(mActivity).inflate(R.layout.modifier_key_item, null); 260 } 261 TextView textView = view.findViewById(R.id.modifier_key_text); 262 ImageView checkIcon = view.findViewById(R.id.modifier_key_check_icon); 263 textView.setText(mList.get(i)); 264 if (mCurrentItem == i) { 265 mKeyFocus = mList.get(i); 266 textView.setTextColor(getColorOfMaterialColorPrimary()); 267 checkIcon.setImageAlpha(255); 268 view.setBackground( 269 mActivity.getDrawable(R.drawable.modifier_key_lisetview_background)); 270 if (mActionKeyDrawable != null && i == 2) { 271 setActionKeyIcon(view); 272 setActionKeyColor(getColorOfMaterialColorPrimary()); 273 } 274 } else { 275 textView.setTextColor(getColorOfTextColorPrimary()); 276 checkIcon.setImageAlpha(0); 277 view.setBackground(null); 278 if (mActionKeyDrawable != null && i == 2) { 279 setActionKeyIcon(view); 280 setActionKeyColor(getColorOfTextColorPrimary()); 281 } 282 } 283 return view; 284 } 285 setCurrentItem(int currentItem)286 public void setCurrentItem(int currentItem) { 287 this.mCurrentItem = currentItem; 288 } 289 getCurrentItem()290 public int getCurrentItem() { 291 return this.mCurrentItem; 292 } 293 } 294 setActionKeyIcon(View view)295 private void setActionKeyIcon(View view) { 296 mLeftBracket = view.findViewById(R.id.modifier_key_left_bracket); 297 mRightBracket = view.findViewById(R.id.modifier_key_right_bracket); 298 mActionKeyIcon = view.findViewById(R.id.modifier_key_action_key_icon); 299 mLeftBracket.setText("("); 300 mRightBracket.setText(")"); 301 mActionKeyIcon.setImageDrawable(mActionKeyDrawable); 302 } 303 setActionKeyColor(int color)304 private void setActionKeyColor(int color) { 305 mLeftBracket.setTextColor(color); 306 mRightBracket.setTextColor(color); 307 DrawableCompat.setTint(mActionKeyDrawable, color); 308 } 309 getColorOfTextColorPrimary()310 private int getColorOfTextColorPrimary() { 311 return Utils.getColorAttrDefaultColor(mActivity, android.R.attr.textColorPrimary); 312 } 313 getColorOfTextColorSecondary()314 private int getColorOfTextColorSecondary() { 315 return Utils.getColorAttrDefaultColor(mActivity, android.R.attr.textColorSecondary); 316 } 317 getColorOfMaterialColorPrimary()318 private int getColorOfMaterialColorPrimary() { 319 return Utils.getColorAttrDefaultColor( 320 mActivity, com.android.internal.R.attr.materialColorPrimary); 321 } 322 } 323