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