Integrate modifier keys settings UI with APIs

1. getModifierKeyRemapping
2. remapModifierKey
3. clearAllModifierKeyRemappings

Demo: go/modifier_keys_settings_demo

Bug: 244535460
Test: local test
Change-Id: I47bcd0b58637feb68c579112a991371490af0157
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index a794d8e..48b8eb2 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -127,6 +127,7 @@
     <uses-permission android:name="android.permission.START_VIEW_APP_FEATURES" />
     <uses-permission android:name="android.permission.LIST_ENABLED_CREDENTIAL_PROVIDERS" />
     <uses-permission android:name="android.permission.CUSTOMIZE_SYSTEM_UI" />
+    <uses-permission android:name="android.permission.REMAP_MODIFIER_KEYS" />
 
     <application
             android:name=".SettingsApplication"
diff --git a/src/com/android/settings/inputmethod/ModifierKeysPickerDialogFragment.java b/src/com/android/settings/inputmethod/ModifierKeysPickerDialogFragment.java
index 40c15ff..b016f22 100644
--- a/src/com/android/settings/inputmethod/ModifierKeysPickerDialogFragment.java
+++ b/src/com/android/settings/inputmethod/ModifierKeysPickerDialogFragment.java
@@ -21,10 +21,12 @@
 import android.app.AlertDialog;
 import android.app.Dialog;
 import android.content.Context;
+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;
@@ -43,31 +45,48 @@
 import com.android.settingslib.Utils;
 
 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 {
 
     private Preference mPreference;
     private String mKeyDefaultName;
     private Context mContext;
+    private InputManager mIm;
+
+    private List<int[]> 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<String, int[]> mRemappableKeyMap = new HashMap<>();
 
     public ModifierKeysPickerDialogFragment() {
     }
 
-    public ModifierKeysPickerDialogFragment(Preference preference) {
+    public ModifierKeysPickerDialogFragment(Preference preference, InputManager inputManager) {
         mPreference = preference;
         mKeyDefaultName = preference.getTitle().toString();
+        mIm = inputManager;
     }
 
     @Override
     public Dialog onCreateDialog(Bundle savedInstanceState) {
         super.onCreateDialog(savedInstanceState);
         mContext = getActivity();
-        String[] modifierKeys = new String[] {
+        List<String> modifierKeys = new ArrayList<String>(Arrays.asList(
                 mContext.getString(R.string.modifier_keys_caps_lock),
                 mContext.getString(R.string.modifier_keys_ctrl),
                 mContext.getString(R.string.modifier_keys_meta),
-                mContext.getString(R.string.modifier_keys_alt)};
+                mContext.getString(R.string.modifier_keys_alt)));
+        for (int i = 0; i < modifierKeys.size(); i++) {
+            mRemappableKeyMap.put(modifierKeys.get(i), mRemappableKeyList.get(i));
+        }
 
         View dialoglayout  =
                 LayoutInflater.from(mContext).inflate(R.layout.modifier_key_picker_dialog, null);
@@ -79,11 +98,7 @@
                 R.string.modifier_keys_picker_summary, mKeyDefaultName);
         summary.setText(summaryText);
 
-        List<String> list = new ArrayList<>();
-        for (int i = 0; i < modifierKeys.length; i++) {
-            list.add(modifierKeys[i]);
-        }
-        ModifierKeyAdapter adapter = new ModifierKeyAdapter(list);
+        ModifierKeyAdapter adapter = new ModifierKeyAdapter(modifierKeys);
         ListView listView = dialoglayout.findViewById(R.id.modifier_key_picker);
         listView.setAdapter(adapter);
         listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@@ -98,7 +113,7 @@
         AlertDialog modifierKeyDialog = dialogBuilder.create();
         Button doneButton = dialoglayout.findViewById(R.id.modifier_key_done_button);
         doneButton.setOnClickListener(v -> {
-            String selectedItem = list.get(adapter.getCurrentItem());
+            String selectedItem = modifierKeys.get(adapter.getCurrentItem());
             Spannable itemSummary;
             if (selectedItem.equals(mKeyDefaultName)) {
                 itemSummary = new SpannableString(
@@ -106,12 +121,34 @@
                 itemSummary.setSpan(
                         new ForegroundColorSpan(getColorOfTextColorSecondary()),
                         0, itemSummary.length(), 0);
-                // TODO(b/252812993): remapModifierKey
+                // Set keys to default.
+                int[] keys = mRemappableKeyMap.get(mKeyDefaultName);
+                for (int i = 0; i < keys.length; i++) {
+                    mIm.remapModifierKey(keys[i], keys[i]);
+                }
             } else {
                 itemSummary = new SpannableString(selectedItem);
                 itemSummary.setSpan(
                         new ForegroundColorSpan(getColorOfColorAccentPrimaryVariant()),
                         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(mContext, mKeyDefaultName)) {
+                    mIm.remapModifierKey(fromKeys[0], toKeys[0]);
+                }
+                // Remap KEY_LEFT and KEY_RIGHT to CAPS_LOCK.
+                if (!isKeyCapsLock(mContext, mKeyDefaultName)
+                        && isKeyCapsLock(mContext, selectedItem)) {
+                    mIm.remapModifierKey(fromKeys[0], toKeys[0]);
+                    mIm.remapModifierKey(fromKeys[1], toKeys[0]);
+                }
+                // Auto handle left and right keys remapping.
+                if (!isKeyCapsLock(mContext, mKeyDefaultName)
+                        && !isKeyCapsLock(mContext, selectedItem)) {
+                    mIm.remapModifierKey(fromKeys[0], toKeys[0]);
+                    mIm.remapModifierKey(fromKeys[1], toKeys[1]);
+                }
             }
             mPreference.setSummary(itemSummary);
             modifierKeyDialog.dismiss();
@@ -128,6 +165,10 @@
         return modifierKeyDialog;
     }
 
+    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 boolean mIsClick = false;
diff --git a/src/com/android/settings/inputmethod/ModifierKeysPreferenceController.java b/src/com/android/settings/inputmethod/ModifierKeysPreferenceController.java
index a7f8a37..69f84a2 100644
--- a/src/com/android/settings/inputmethod/ModifierKeysPreferenceController.java
+++ b/src/com/android/settings/inputmethod/ModifierKeysPreferenceController.java
@@ -17,6 +17,11 @@
 package com.android.settings.inputmethod;
 
 import android.content.Context;
+import android.hardware.input.InputManager;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.style.ForegroundColorSpan;
+import android.view.KeyEvent;
 
 import androidx.fragment.app.DialogFragment;
 import androidx.fragment.app.Fragment;
@@ -24,18 +29,50 @@
 import androidx.preference.Preference;
 import androidx.preference.PreferenceScreen;
 
+import com.android.settings.R;
 import com.android.settings.core.BasePreferenceController;
+import com.android.settingslib.Utils;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
 
 public class ModifierKeysPreferenceController extends BasePreferenceController {
 
     private static String KEY_TAG = "modifier_keys_dialog_tag";
     private static String KEY_RESTORE_PREFERENCE = "modifier_keys_restore";
 
+    private static final String KEY_PREFERENCE_CAPS_LOCK = "modifier_keys_caps_lock";
+    private static final String KEY_PREFERENCE_CTRL = "modifier_keys_ctrl";
+    private static final String KEY_PREFERENCE_META = "modifier_keys_meta";
+    private static final String KEY_PREFERENCE_ALT = "modifier_keys_alt";
+
     private Fragment mParent;
     private FragmentManager mFragmentManager;
+    private final InputManager mIm;
+
+    private final List<Integer> mRemappableKeys = new ArrayList<>(
+            Arrays.asList(
+                    KeyEvent.KEYCODE_CTRL_LEFT, KeyEvent.KEYCODE_CTRL_RIGHT,
+                    KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_META_RIGHT,
+                    KeyEvent.KEYCODE_ALT_LEFT, KeyEvent.KEYCODE_ALT_RIGHT,
+                    KeyEvent.KEYCODE_CAPS_LOCK));
+
+    private String[] mKeyNames = new String[] {
+            mContext.getString(R.string.modifier_keys_ctrl),
+            mContext.getString(R.string.modifier_keys_ctrl),
+            mContext.getString(R.string.modifier_keys_meta),
+            mContext.getString(R.string.modifier_keys_meta),
+            mContext.getString(R.string.modifier_keys_alt),
+            mContext.getString(R.string.modifier_keys_alt),
+            mContext.getString(R.string.modifier_keys_caps_lock)};
 
     public ModifierKeysPreferenceController(Context context, String key) {
         super(context, key);
+        mIm = context.getSystemService(InputManager.class);
+        Objects.requireNonNull(mIm, "InputManager service cannot be null");
     }
 
     public void setFragment(Fragment parent) {
@@ -45,12 +82,37 @@
     @Override
     public void displayPreference(PreferenceScreen screen) {
         super.displayPreference(screen);
-        // TODO: getModifierKeyRemapping()
-        // setTitle
-        // setSummary
+
         if (mParent == null) {
             return;
         }
+
+        for (Map.Entry<Integer, Integer> entry : mIm.getModifierKeyRemapping().entrySet()) {
+            int fromKey = entry.getKey();
+            int toKey = entry.getValue();
+            int index = mRemappableKeys.indexOf(toKey);
+
+            if (isCtrl(fromKey) && mRemappableKeys.contains(toKey)) {
+                Preference preference = screen.findPreference(KEY_PREFERENCE_CTRL);
+                preference.setSummary(changeSummaryColor(mKeyNames[index]));
+            }
+
+            if (isMeta(fromKey) && mRemappableKeys.contains(toKey)) {
+                Preference preference = screen.findPreference(KEY_PREFERENCE_META);
+                preference.setSummary(changeSummaryColor(mKeyNames[index]));
+            }
+
+            if (isAlt(fromKey) && mRemappableKeys.contains(toKey)) {
+                Preference preference = screen.findPreference(KEY_PREFERENCE_ALT);
+                preference.setSummary(changeSummaryColor(mKeyNames[index]));
+            }
+
+            if (isCapLock(fromKey) && mRemappableKeys.contains(toKey)) {
+                Preference preference = screen.findPreference(KEY_PREFERENCE_CAPS_LOCK);
+                preference.setSummary(changeSummaryColor(mKeyNames[index]));
+            }
+        }
+
         // The dialog screen depends on the previous selected key's fragment.
         // In the rotation scenario, we should remove the previous dialog screen first.
         clearPreviousDialog();
@@ -72,7 +134,7 @@
 
     private void showModifierKeysDialog(Preference preference) {
         ModifierKeysPickerDialogFragment fragment =
-                new ModifierKeysPickerDialogFragment(preference);
+                new ModifierKeysPickerDialogFragment(preference, mIm);
         fragment.setTargetFragment(mParent, 0);
         fragment.show(mFragmentManager, KEY_TAG);
     }
@@ -85,4 +147,33 @@
             preKeysDialogFragment.dismiss();
         }
     }
+
+    private Spannable changeSummaryColor(String summary) {
+        Spannable spannableSummary = new SpannableString(summary);
+        spannableSummary.setSpan(
+                new ForegroundColorSpan(getColorOfColorAccentPrimaryVariant()),
+                0, spannableSummary.length(), 0);
+        return spannableSummary;
+    }
+
+    private int getColorOfColorAccentPrimaryVariant() {
+        return Utils.getColorAttrDefaultColor(
+                mContext, com.android.internal.R.attr.colorAccentPrimaryVariant);
+    }
+
+    private static boolean isCtrl(int keyCode) {
+        return keyCode == KeyEvent.KEYCODE_CTRL_LEFT || keyCode == KeyEvent.KEYCODE_CTRL_RIGHT;
+    }
+
+    private static boolean isMeta(int keyCode) {
+        return keyCode == KeyEvent.KEYCODE_META_LEFT || keyCode == KeyEvent.KEYCODE_META_RIGHT;
+    }
+
+    private static boolean isAlt(int keyCode) {
+        return keyCode == KeyEvent.KEYCODE_ALT_LEFT || keyCode == KeyEvent.KEYCODE_ALT_RIGHT;
+    }
+
+    private static boolean isCapLock(int keyCode) {
+        return keyCode == KeyEvent.KEYCODE_CAPS_LOCK;
+    }
 }
diff --git a/src/com/android/settings/inputmethod/ModifierKeysResetDialogFragment.java b/src/com/android/settings/inputmethod/ModifierKeysResetDialogFragment.java
index 0b605a0..4ca5ddd 100644
--- a/src/com/android/settings/inputmethod/ModifierKeysResetDialogFragment.java
+++ b/src/com/android/settings/inputmethod/ModifierKeysResetDialogFragment.java
@@ -21,6 +21,7 @@
 import android.app.AlertDialog;
 import android.app.Dialog;
 import android.content.Context;
+import android.hardware.input.InputManager;
 import android.os.Bundle;
 import android.text.Spannable;
 import android.text.SpannableString;
@@ -44,6 +45,7 @@
     private static final String MODIFIER_KEYS_ALT = "modifier_keys_alt";
 
     private PreferenceScreen mScreen;
+    private InputManager mIm;
     private String[] mKeys = {
             MODIFIER_KEYS_CAPS_LOCK,
             MODIFIER_KEYS_CTRL,
@@ -53,8 +55,9 @@
     public ModifierKeysResetDialogFragment() {
     }
 
-    public ModifierKeysResetDialogFragment(PreferenceScreen screen) {
+    public ModifierKeysResetDialogFragment(PreferenceScreen screen, InputManager inputManager) {
         mScreen = screen;
+        mIm = inputManager;
     }
 
     @Override
@@ -95,7 +98,10 @@
                     0, title.length(), 0);
             preference.setSummary(title);
         }
-        // TODO(b/252812993): clearAllModifierKeyRemappings()
+
+        if (mIm != null) {
+            mIm.clearAllModifierKeyRemappings();
+        }
     }
 
     private int getColorOfTextColorSecondary() {
diff --git a/src/com/android/settings/inputmethod/ModifierKeysRestorePreferenceController.java b/src/com/android/settings/inputmethod/ModifierKeysRestorePreferenceController.java
index 159cc11..189f4c3 100644
--- a/src/com/android/settings/inputmethod/ModifierKeysRestorePreferenceController.java
+++ b/src/com/android/settings/inputmethod/ModifierKeysRestorePreferenceController.java
@@ -17,6 +17,7 @@
 package com.android.settings.inputmethod;
 
 import android.content.Context;
+import android.hardware.input.InputManager;
 import android.text.Spannable;
 import android.text.SpannableString;
 import android.text.style.ForegroundColorSpan;
@@ -38,9 +39,11 @@
     private Fragment mParent;
     private FragmentManager mFragmentManager;
     private PreferenceScreen mScreen;
+    private final InputManager mIm;
 
     public ModifierKeysRestorePreferenceController(Context context, String key) {
         super(context, key);
+        mIm = context.getSystemService(InputManager.class);
     }
 
     public void setFragment(Fragment parent) {
@@ -76,7 +79,7 @@
 
     private void showResetDialog() {
         ModifierKeysResetDialogFragment fragment =
-                new ModifierKeysResetDialogFragment(mScreen);
+                new ModifierKeysResetDialogFragment(mScreen, mIm);
         fragment.setTargetFragment(mParent, 0);
         fragment.show(mFragmentManager, KEY_TAG);
     }