/* * Copyright (C) 2022 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.app.settings.SettingsEnums; import android.content.Context; import android.graphics.drawable.Drawable; import android.hardware.input.InputManager; import android.os.Bundle; import android.text.Spannable; import android.text.SpannableString; import android.text.style.ForegroundColorSpan; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.Window; import android.widget.AdapterView; import android.widget.BaseAdapter; import android.widget.Button; import android.widget.ImageView; import android.widget.ListView; import android.widget.TextView; import androidx.core.graphics.drawable.DrawableCompat; import androidx.fragment.app.DialogFragment; import androidx.preference.Preference; import com.android.settings.R; import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.Utils; import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; public class ModifierKeysPickerDialogFragment extends DialogFragment { static final String DEFAULT_KEY = "default_key"; static final String SELECTION_KEY = "delection_key"; private Preference mPreference; private String mKeyDefaultName; private String mKeyFocus; private Activity mActivity; private KeyboardSettingsFeatureProvider mFeatureProvider; private Drawable mActionKeyDrawable; private TextView mLeftBracket; private TextView mRightBracket; private ImageView mActionKeyIcon; private MetricsFeatureProvider mMetricsFeatureProvider; private List mRemappableKeyList = new ArrayList<>(Arrays.asList( new int[]{KeyEvent.KEYCODE_CAPS_LOCK}, new int[]{KeyEvent.KEYCODE_CTRL_LEFT, KeyEvent.KEYCODE_CTRL_RIGHT}, new int[]{KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_META_RIGHT}, new int[]{KeyEvent.KEYCODE_ALT_LEFT, KeyEvent.KEYCODE_ALT_RIGHT})); private Map mRemappableKeyMap = new HashMap<>(); public ModifierKeysPickerDialogFragment() {} @Override public void onSaveInstanceState(Bundle savedInstanceState) { savedInstanceState.putString(SELECTION_KEY, mKeyFocus); super.onSaveInstanceState(savedInstanceState); } @Override public Dialog onCreateDialog(Bundle savedInstanceState) { super.onCreateDialog(savedInstanceState); mActivity = getActivity(); FeatureFactory featureFactory = FeatureFactory.getFeatureFactory(); mMetricsFeatureProvider = featureFactory.getMetricsFeatureProvider(); mFeatureProvider = featureFactory.getKeyboardSettingsFeatureProvider(); InputManager inputManager = mActivity.getSystemService(InputManager.class); mKeyDefaultName = getArguments().getString(DEFAULT_KEY); mKeyFocus = getArguments().getString(SELECTION_KEY); if (savedInstanceState != null) { mKeyFocus = savedInstanceState.getString(SELECTION_KEY); } List modifierKeys = new ArrayList(Arrays.asList( mActivity.getString(R.string.modifier_keys_caps_lock), mActivity.getString(R.string.modifier_keys_ctrl), mActivity.getString(R.string.modifier_keys_meta), mActivity.getString(R.string.modifier_keys_alt))); for (int i = 0; i < modifierKeys.size(); i++) { mRemappableKeyMap.put(modifierKeys.get(i), mRemappableKeyList.get(i)); } Drawable drawable = mFeatureProvider.getActionKeyIcon(mActivity); if (drawable != null) { mActionKeyDrawable = DrawableCompat.wrap(drawable); } View dialoglayout = LayoutInflater.from(mActivity).inflate(R.layout.modifier_key_picker_dialog, null); AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(mActivity); dialogBuilder.setView(dialoglayout); TextView summary = dialoglayout.findViewById(R.id.modifier_key_picker_summary); CharSequence summaryText = mActivity.getString( R.string.modifier_keys_picker_summary, mKeyDefaultName); summary.setText(summaryText); ModifierKeyAdapter adapter = new ModifierKeyAdapter(modifierKeys); ListView listView = dialoglayout.findViewById(R.id.modifier_key_picker); listView.setAdapter(adapter); setInitialFocusItem(modifierKeys, adapter); listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView adapterView, View view, int i, long l) { adapter.setCurrentItem(i); adapter.notifyDataSetChanged(); } }); AlertDialog modifierKeyDialog = dialogBuilder.create(); Button doneButton = dialoglayout.findViewById(R.id.modifier_key_done_button); doneButton.setOnClickListener(v -> { String selectedItem = modifierKeys.get(adapter.getCurrentItem()); Spannable itemSummary; logMetricsForRemapping(selectedItem); if (selectedItem.equals(mKeyDefaultName)) { itemSummary = new SpannableString( mActivity.getString(R.string.modifier_keys_default_summary)); itemSummary.setSpan( new ForegroundColorSpan(getColorOfTextColorSecondary()), 0, itemSummary.length(), 0); // Set keys to default. int[] keys = mRemappableKeyMap.get(mKeyDefaultName); for (int i = 0; i < keys.length; i++) { inputManager.remapModifierKey(keys[i], keys[i]); } } else { itemSummary = new SpannableString(selectedItem); itemSummary.setSpan( new ForegroundColorSpan(getColorOfMaterialColorPrimary()), 0, itemSummary.length(), 0); int[] fromKeys = mRemappableKeyMap.get(mKeyDefaultName); int[] toKeys = mRemappableKeyMap.get(selectedItem); // CAPS_LOCK only one key, so always choose the left key for remapping. if (isKeyCapsLock(mActivity, mKeyDefaultName)) { inputManager.remapModifierKey(fromKeys[0], toKeys[0]); } // Remap KEY_LEFT and KEY_RIGHT to CAPS_LOCK. if (!isKeyCapsLock(mActivity, mKeyDefaultName) && isKeyCapsLock(mActivity, selectedItem)) { inputManager.remapModifierKey(fromKeys[0], toKeys[0]); inputManager.remapModifierKey(fromKeys[1], toKeys[0]); } // Auto handle left and right keys remapping. if (!isKeyCapsLock(mActivity, mKeyDefaultName) && !isKeyCapsLock(mActivity, selectedItem)) { inputManager.remapModifierKey(fromKeys[0], toKeys[0]); inputManager.remapModifierKey(fromKeys[1], toKeys[1]); } } dismiss(); mActivity.recreate(); }); Button cancelButton = dialoglayout.findViewById(R.id.modifier_key_cancel_button); cancelButton.setOnClickListener(v -> { dismiss(); }); final Window window = modifierKeyDialog.getWindow(); window.setType(TYPE_SYSTEM_DIALOG); return modifierKeyDialog; } private void logMetricsForRemapping(String selectedItem) { if (mKeyDefaultName.equals("Caps lock")) { mMetricsFeatureProvider.action( mActivity, SettingsEnums.ACTION_FROM_CAPS_LOCK_TO, selectedItem); } if (mKeyDefaultName.equals("Ctrl")) { mMetricsFeatureProvider.action( mActivity, SettingsEnums.ACTION_FROM_CTRL_TO, selectedItem); } if (mKeyDefaultName.equals("Action key")) { mMetricsFeatureProvider.action( mActivity, SettingsEnums.ACTION_FROM_ACTION_KEY_TO, selectedItem); } if (mKeyDefaultName.equals("Alt")) { mMetricsFeatureProvider.action( mActivity, SettingsEnums.ACTION_FROM_ALT_TO, selectedItem); } } private void setInitialFocusItem( List modifierKeys, ModifierKeyAdapter adapter) { if (modifierKeys.indexOf(mKeyFocus) == -1) { adapter.setCurrentItem(modifierKeys.indexOf(mKeyDefaultName)); } else { adapter.setCurrentItem(modifierKeys.indexOf(mKeyFocus)); } adapter.notifyDataSetChanged(); } private static boolean isKeyCapsLock(Context context, String key) { return key.equals(context.getString(R.string.modifier_keys_caps_lock)); } class ModifierKeyAdapter extends BaseAdapter { private int mCurrentItem = 0; private List mList; ModifierKeyAdapter(List list) { this.mList = list; } @Override public int getCount() { return mList.size(); } @Override public Object getItem(int i) { return mList.get(i); } @Override public long getItemId(int i) { return i; } @Override public View getView(int i, View view, ViewGroup viewGroup) { if (view == null) { view = LayoutInflater.from(mActivity).inflate(R.layout.modifier_key_item, null); } TextView textView = view.findViewById(R.id.modifier_key_text); ImageView checkIcon = view.findViewById(R.id.modifier_key_check_icon); textView.setText(mList.get(i)); if (mCurrentItem == i) { mKeyFocus = mList.get(i); textView.setTextColor(getColorOfMaterialColorPrimary()); checkIcon.setImageAlpha(255); view.setBackground( mActivity.getDrawable(R.drawable.modifier_key_lisetview_background)); if (mActionKeyDrawable != null && i == 2) { setActionKeyIcon(view); setActionKeyColor(getColorOfMaterialColorPrimary()); } } else { textView.setTextColor(getColorOfTextColorPrimary()); checkIcon.setImageAlpha(0); view.setBackground(null); if (mActionKeyDrawable != null && i == 2) { setActionKeyIcon(view); setActionKeyColor(getColorOfTextColorPrimary()); } } return view; } public void setCurrentItem(int currentItem) { this.mCurrentItem = currentItem; } public int getCurrentItem() { return this.mCurrentItem; } } private void setActionKeyIcon(View view) { mLeftBracket = view.findViewById(R.id.modifier_key_left_bracket); mRightBracket = view.findViewById(R.id.modifier_key_right_bracket); mActionKeyIcon = view.findViewById(R.id.modifier_key_action_key_icon); mLeftBracket.setText("("); mRightBracket.setText(")"); mActionKeyIcon.setImageDrawable(mActionKeyDrawable); } private void setActionKeyColor(int color) { mLeftBracket.setTextColor(color); mRightBracket.setTextColor(color); DrawableCompat.setTint(mActionKeyDrawable, color); } private int getColorOfTextColorPrimary() { return Utils.getColorAttrDefaultColor(mActivity, android.R.attr.textColorPrimary); } private int getColorOfTextColorSecondary() { return Utils.getColorAttrDefaultColor(mActivity, android.R.attr.textColorSecondary); } private int getColorOfMaterialColorPrimary() { return Utils.getColorAttrDefaultColor( mActivity, com.android.internal.R.attr.materialColorPrimary); } }