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.server.input; 18 19 import android.content.Context; 20 import android.hardware.input.InputManager; 21 import android.os.Handler; 22 import android.os.Looper; 23 import android.os.Message; 24 import android.util.ArrayMap; 25 import android.util.FeatureFlagUtils; 26 import android.view.InputDevice; 27 28 import com.android.internal.annotations.GuardedBy; 29 30 import java.util.Map; 31 import java.util.Objects; 32 33 /** 34 * A component of {@link InputManagerService} responsible for managing key remappings. 35 * 36 * @hide 37 */ 38 final class KeyRemapper implements InputManager.InputDeviceListener { 39 40 private static final int MSG_UPDATE_EXISTING_DEVICES = 1; 41 private static final int MSG_REMAP_KEY = 2; 42 private static final int MSG_CLEAR_ALL_REMAPPING = 3; 43 44 private final Context mContext; 45 private final NativeInputManagerService mNative; 46 // The PersistentDataStore should be locked before use. 47 @GuardedBy("mDataStore") 48 private final PersistentDataStore mDataStore; 49 private final Handler mHandler; 50 KeyRemapper(Context context, NativeInputManagerService nativeService, PersistentDataStore dataStore, Looper looper)51 KeyRemapper(Context context, NativeInputManagerService nativeService, 52 PersistentDataStore dataStore, Looper looper) { 53 mContext = context; 54 mNative = nativeService; 55 mDataStore = dataStore; 56 mHandler = new Handler(looper, this::handleMessage); 57 } 58 systemRunning()59 public void systemRunning() { 60 InputManager inputManager = Objects.requireNonNull( 61 mContext.getSystemService(InputManager.class)); 62 inputManager.registerInputDeviceListener(this, mHandler); 63 64 Message msg = Message.obtain(mHandler, MSG_UPDATE_EXISTING_DEVICES, 65 inputManager.getInputDeviceIds()); 66 mHandler.sendMessage(msg); 67 } 68 remapKey(int fromKey, int toKey)69 public void remapKey(int fromKey, int toKey) { 70 if (!supportRemapping()) { 71 return; 72 } 73 Message msg = Message.obtain(mHandler, MSG_REMAP_KEY, fromKey, toKey); 74 mHandler.sendMessage(msg); 75 } 76 clearAllKeyRemappings()77 public void clearAllKeyRemappings() { 78 if (!supportRemapping()) { 79 return; 80 } 81 Message msg = Message.obtain(mHandler, MSG_CLEAR_ALL_REMAPPING); 82 mHandler.sendMessage(msg); 83 } 84 getKeyRemapping()85 public Map<Integer, Integer> getKeyRemapping() { 86 if (!supportRemapping()) { 87 return new ArrayMap<>(); 88 } 89 synchronized (mDataStore) { 90 return mDataStore.getKeyRemapping(); 91 } 92 } 93 addKeyRemapping(int fromKey, int toKey)94 private void addKeyRemapping(int fromKey, int toKey) { 95 InputManager inputManager = Objects.requireNonNull( 96 mContext.getSystemService(InputManager.class)); 97 for (int deviceId : inputManager.getInputDeviceIds()) { 98 InputDevice inputDevice = inputManager.getInputDevice(deviceId); 99 if (inputDevice != null && !inputDevice.isVirtual() && inputDevice.isFullKeyboard()) { 100 mNative.addKeyRemapping(deviceId, fromKey, toKey); 101 } 102 } 103 } 104 remapKeyInternal(int fromKey, int toKey)105 private void remapKeyInternal(int fromKey, int toKey) { 106 addKeyRemapping(fromKey, toKey); 107 synchronized (mDataStore) { 108 try { 109 if (fromKey == toKey) { 110 mDataStore.clearMappedKey(fromKey); 111 } else { 112 mDataStore.remapKey(fromKey, toKey); 113 } 114 } finally { 115 mDataStore.saveIfNeeded(); 116 } 117 } 118 } 119 clearAllRemappingsInternal()120 private void clearAllRemappingsInternal() { 121 synchronized (mDataStore) { 122 try { 123 Map<Integer, Integer> keyRemapping = mDataStore.getKeyRemapping(); 124 for (int fromKey : keyRemapping.keySet()) { 125 mDataStore.clearMappedKey(fromKey); 126 127 // Remapping to itself will clear the remapping on native side 128 addKeyRemapping(fromKey, fromKey); 129 } 130 } finally { 131 mDataStore.saveIfNeeded(); 132 } 133 } 134 } 135 136 @Override onInputDeviceAdded(int deviceId)137 public void onInputDeviceAdded(int deviceId) { 138 if (!supportRemapping()) { 139 return; 140 } 141 InputManager inputManager = Objects.requireNonNull( 142 mContext.getSystemService(InputManager.class)); 143 InputDevice inputDevice = inputManager.getInputDevice(deviceId); 144 if (inputDevice != null && !inputDevice.isVirtual() && inputDevice.isFullKeyboard()) { 145 Map<Integer, Integer> remapping = getKeyRemapping(); 146 remapping.forEach( 147 (fromKey, toKey) -> mNative.addKeyRemapping(deviceId, fromKey, toKey)); 148 } 149 } 150 151 @Override onInputDeviceRemoved(int deviceId)152 public void onInputDeviceRemoved(int deviceId) { 153 } 154 155 @Override onInputDeviceChanged(int deviceId)156 public void onInputDeviceChanged(int deviceId) { 157 } 158 handleMessage(Message msg)159 private boolean handleMessage(Message msg) { 160 switch (msg.what) { 161 case MSG_UPDATE_EXISTING_DEVICES: 162 for (int deviceId : (int[]) msg.obj) { 163 onInputDeviceAdded(deviceId); 164 } 165 return true; 166 case MSG_REMAP_KEY: 167 remapKeyInternal(msg.arg1, msg.arg2); 168 return true; 169 case MSG_CLEAR_ALL_REMAPPING: 170 clearAllRemappingsInternal(); 171 return true; 172 } 173 return false; 174 } 175 supportRemapping()176 private boolean supportRemapping() { 177 return FeatureFlagUtils.isEnabled(mContext, 178 FeatureFlagUtils.SETTINGS_NEW_KEYBOARD_MODIFIER_KEY); 179 } 180 } 181