Gets keyboard layout info from user space when available.

The layout info consists of a BCP 47 conformant language tag and a
layout type such as "qwerty" or "azerty". They will be used to
initialize the input device.

This is currently used by uinput virtual keyboard to pass in layout
information.

Bug: 237537306
Test: atest inputflinger_tests
Change-Id: Icfc30f1afb0f88dd704d1d598d62a300a032b0f5
diff --git a/include/input/InputDevice.h b/include/input/InputDevice.h
index e911734..5fa9fda 100644
--- a/include/input/InputDevice.h
+++ b/include/input/InputDevice.h
@@ -205,6 +205,16 @@
     int32_t id;
 };
 
+struct KeyboardLayoutInfo {
+    explicit KeyboardLayoutInfo(std::string languageTag, std::string layoutType)
+          : languageTag(languageTag), layoutType(layoutType) {}
+
+    // A BCP 47 conformant language tag such as "en-US".
+    std::string languageTag;
+    // The layout type such as QWERTY or AZERTY.
+    std::string layoutType;
+};
+
 /*
  * Describes the characteristics and capabilities of an input device.
  */
@@ -256,6 +266,11 @@
     void setKeyboardType(int32_t keyboardType);
     inline int32_t getKeyboardType() const { return mKeyboardType; }
 
+    void setKeyboardLayoutInfo(KeyboardLayoutInfo keyboardLayoutInfo);
+    inline const std::optional<KeyboardLayoutInfo>& getKeyboardLayoutInfo() const {
+        return mKeyboardLayoutInfo;
+    }
+
     inline void setKeyCharacterMap(const std::shared_ptr<KeyCharacterMap> value) {
         mKeyCharacterMap = value;
     }
@@ -296,6 +311,7 @@
     bool mIsExternal;
     bool mHasMic;
     hardware::input::InputDeviceCountryCode mCountryCode;
+    std::optional<KeyboardLayoutInfo> mKeyboardLayoutInfo;
     uint32_t mSources;
     int32_t mKeyboardType;
     std::shared_ptr<KeyCharacterMap> mKeyCharacterMap;
diff --git a/libs/input/InputDevice.cpp b/libs/input/InputDevice.cpp
index 4751a7d..fb6c590 100644
--- a/libs/input/InputDevice.cpp
+++ b/libs/input/InputDevice.cpp
@@ -179,6 +179,7 @@
         mIsExternal(other.mIsExternal),
         mHasMic(other.mHasMic),
         mCountryCode(other.mCountryCode),
+        mKeyboardLayoutInfo(other.mKeyboardLayoutInfo),
         mSources(other.mSources),
         mKeyboardType(other.mKeyboardType),
         mKeyCharacterMap(other.mKeyCharacterMap),
@@ -270,6 +271,10 @@
     mKeyboardType = std::max(mKeyboardType, keyboardType);
 }
 
+void InputDeviceInfo::setKeyboardLayoutInfo(KeyboardLayoutInfo layoutInfo) {
+    mKeyboardLayoutInfo = std::move(layoutInfo);
+}
+
 std::vector<InputDeviceSensorInfo> InputDeviceInfo::getSensors() {
     std::vector<InputDeviceSensorInfo> infos;
     infos.reserve(mSensors.size());
diff --git a/services/inputflinger/include/InputReaderBase.h b/services/inputflinger/include/InputReaderBase.h
index 3e4db43..d2c940f 100644
--- a/services/inputflinger/include/InputReaderBase.h
+++ b/services/inputflinger/include/InputReaderBase.h
@@ -194,6 +194,9 @@
         // The device type has been updated.
         CHANGE_DEVICE_TYPE = 1 << 10,
 
+        // The keyboard layout association has changed.
+        CHANGE_KEYBOARD_LAYOUT_ASSOCIATION = 1 << 11,
+
         // All devices must be reopened.
         CHANGE_MUST_REOPEN = 1 << 31,
     };
@@ -211,7 +214,7 @@
     // Used to determine which DisplayViewport should be tied to which InputDevice.
     std::unordered_map<std::string, uint8_t> portAssociations;
 
-    // The associations between input device names and display unique ids.
+    // The associations between input device physical port locations and display unique ids.
     // Used to determine which DisplayViewport should be tied to which InputDevice.
     std::unordered_map<std::string, std::string> uniqueIdAssociations;
 
@@ -219,6 +222,10 @@
     // This is used to determine which device type and source should be tied to which InputDevice.
     std::unordered_map<std::string, std::string> deviceTypeAssociations;
 
+    // The map from the input device physical port location to the input device layout info.
+    // Can be used to determine the layout of the keyboard device.
+    std::unordered_map<std::string, KeyboardLayoutInfo> keyboardLayoutAssociations;
+
     // The suggested display ID to show the cursor.
     int32_t defaultPointerDisplayId;
 
diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp
index 6dfe5f5..13f40ee 100644
--- a/services/inputflinger/reader/InputDevice.cpp
+++ b/services/inputflinger/reader/InputDevice.cpp
@@ -56,16 +56,6 @@
 
 InputDevice::~InputDevice() {}
 
-template <typename K, typename V>
-std::optional<V> getValueByKey(const std::unordered_map<K, V>& map, K key) {
-    auto it = map.find(key);
-    std::optional<V> value = std::nullopt;
-    if (it != map.end()) {
-        value = it->second;
-    }
-    return value;
-}
-
 bool InputDevice::isEnabled() {
     if (!hasEventHubDevices()) {
         return false;
diff --git a/services/inputflinger/reader/Macros.h b/services/inputflinger/reader/Macros.h
index e107d88..d2a7ced 100644
--- a/services/inputflinger/reader/Macros.h
+++ b/services/inputflinger/reader/Macros.h
@@ -22,6 +22,8 @@
 #include <log/log.h>
 #include <log/log_event_list.h>
 
+#include <unordered_map>
+
 namespace android {
 /**
  * Log debug messages for each raw event received from the EventHub.
@@ -113,4 +115,14 @@
     return (sources & sourceMask & ~AINPUT_SOURCE_CLASS_MASK) != 0;
 }
 
+template <typename K, typename V>
+static inline std::optional<V> getValueByKey(const std::unordered_map<K, V>& map, K key) {
+    auto it = map.find(key);
+    std::optional<V> value = std::nullopt;
+    if (it != map.end()) {
+        value = it->second;
+    }
+    return value;
+}
+
 } // namespace android
diff --git a/services/inputflinger/reader/include/InputDevice.h b/services/inputflinger/reader/include/InputDevice.h
index af59fe2..6179f05 100644
--- a/services/inputflinger/reader/include/InputDevice.h
+++ b/services/inputflinger/reader/include/InputDevice.h
@@ -54,6 +54,7 @@
     inline std::optional<std::string> getBluetoothAddress() const {
         return mIdentifier.bluetoothAddress;
     }
+    inline const std::string getLocation() const { return mIdentifier.location; }
     inline ftl::Flags<InputDeviceClass> getClasses() const { return mClasses; }
     inline uint32_t getSources() const { return mSources; }
     inline bool hasEventHubDevices() const { return !mDevices.empty(); }
@@ -405,6 +406,7 @@
 
     inline const std::string getName() { return mDevice.getName(); }
     inline const std::string getDescriptor() { return mDevice.getDescriptor(); }
+    inline const std::string getLocation() { return mDevice.getLocation(); }
     inline bool isExternal() { return mDevice.isExternal(); }
     inline std::optional<uint8_t> getAssociatedDisplayPort() const {
         return mDevice.getAssociatedDisplayPort();
diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
index 44f0dfe..6f01449 100644
--- a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
@@ -120,6 +120,10 @@
 
     info->setKeyboardType(mKeyboardType);
     info->setKeyCharacterMap(getDeviceContext().getKeyCharacterMap());
+
+    if (mKeyboardLayoutInfo) {
+        info->setKeyboardLayoutInfo(*mKeyboardLayoutInfo);
+    }
 }
 
 void KeyboardInputMapper::dump(std::string& dump) {
@@ -129,6 +133,12 @@
     dump += StringPrintf(INDENT3 "Orientation: %d\n", getOrientation());
     dump += StringPrintf(INDENT3 "KeyDowns: %zu keys currently down\n", mKeyDowns.size());
     dump += StringPrintf(INDENT3 "MetaState: 0x%0x\n", mMetaState);
+    dump += INDENT3 "KeyboardLayoutInfo: ";
+    if (mKeyboardLayoutInfo) {
+        dump += mKeyboardLayoutInfo->languageTag + ", " + mKeyboardLayoutInfo->layoutType + "\n";
+    } else {
+        dump += "<not set>\n";
+    }
 }
 
 std::optional<DisplayViewport> KeyboardInputMapper::findViewport(
@@ -158,6 +168,12 @@
     if (!changes || (changes & InputReaderConfiguration::CHANGE_DISPLAY_INFO)) {
         mViewport = findViewport(config);
     }
+
+    if (!changes || (changes & InputReaderConfiguration::CHANGE_KEYBOARD_LAYOUT_ASSOCIATION)) {
+        mKeyboardLayoutInfo =
+                getValueByKey(config->keyboardLayoutAssociations, getDeviceContext().getLocation());
+    }
+
     return out;
 }
 
diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.h b/services/inputflinger/reader/mapper/KeyboardInputMapper.h
index 0526fd8..da5b8ee 100644
--- a/services/inputflinger/reader/mapper/KeyboardInputMapper.h
+++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.h
@@ -58,6 +58,7 @@
 
     uint32_t mSource{};
     int32_t mKeyboardType{};
+    std::optional<KeyboardLayoutInfo> mKeyboardLayoutInfo;
 
     std::vector<KeyDown> mKeyDowns{}; // keys that are down
     int32_t mMetaState{};
diff --git a/services/inputflinger/tests/FakeInputReaderPolicy.cpp b/services/inputflinger/tests/FakeInputReaderPolicy.cpp
index dc7e581..f755356 100644
--- a/services/inputflinger/tests/FakeInputReaderPolicy.cpp
+++ b/services/inputflinger/tests/FakeInputReaderPolicy.cpp
@@ -136,6 +136,11 @@
     mConfig.uniqueIdAssociations.insert({inputUniqueId, displayUniqueId});
 }
 
+void FakeInputReaderPolicy::addKeyboardLayoutAssociation(const std::string& inputUniqueId,
+                                                         const KeyboardLayoutInfo& layoutInfo) {
+    mConfig.keyboardLayoutAssociations.insert({inputUniqueId, layoutInfo});
+}
+
 void FakeInputReaderPolicy::addDisabledDevice(int32_t deviceId) {
     mConfig.disabledDevices.insert(deviceId);
 }
diff --git a/services/inputflinger/tests/FakeInputReaderPolicy.h b/services/inputflinger/tests/FakeInputReaderPolicy.h
index faa9c01..862ff0b 100644
--- a/services/inputflinger/tests/FakeInputReaderPolicy.h
+++ b/services/inputflinger/tests/FakeInputReaderPolicy.h
@@ -58,6 +58,8 @@
     void addDeviceTypeAssociation(const std::string& inputPort, const std::string& type);
     void addInputUniqueIdAssociation(const std::string& inputUniqueId,
                                      const std::string& displayUniqueId);
+    void addKeyboardLayoutAssociation(const std::string& inputUniqueId,
+                                      const KeyboardLayoutInfo& layoutInfo);
     void addDisabledDevice(int32_t deviceId);
     void removeDisabledDevice(int32_t deviceId);
     void setPointerController(std::shared_ptr<FakePointerController> controller);
diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp
index 3516092..5b0185f 100644
--- a/services/inputflinger/tests/InputReader_test.cpp
+++ b/services/inputflinger/tests/InputReader_test.cpp
@@ -2763,7 +2763,7 @@
 class KeyboardInputMapperTest : public InputMapperTest {
 protected:
     const std::string UNIQUE_ID = "local:0";
-
+    const KeyboardLayoutInfo DEVICE_KEYBOARD_LAYOUT_INFO = KeyboardLayoutInfo("en-US", "qwerty");
     void prepareDisplay(ui::Rotation orientation);
 
     void testDPadKeyRotation(KeyboardInputMapper& mapper, int32_t originalScanCode,
@@ -3582,6 +3582,24 @@
     ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_CANCELED, args.flags);
 }
 
+TEST_F(KeyboardInputMapperTest, Configure_AssignKeyboardLayoutInfo) {
+    mDevice->addMapper<KeyboardInputMapper>(EVENTHUB_ID, AINPUT_SOURCE_KEYBOARD,
+                                            AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+    std::list<NotifyArgs> unused =
+            mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), 0);
+
+    mFakePolicy->addKeyboardLayoutAssociation(DEVICE_LOCATION, DEVICE_KEYBOARD_LAYOUT_INFO);
+
+    unused += mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
+                                 InputReaderConfiguration::CHANGE_KEYBOARD_LAYOUT_ASSOCIATION);
+
+    InputDeviceInfo deviceInfo = mDevice->getDeviceInfo();
+    ASSERT_EQ(DEVICE_KEYBOARD_LAYOUT_INFO.languageTag,
+              deviceInfo.getKeyboardLayoutInfo()->languageTag);
+    ASSERT_EQ(DEVICE_KEYBOARD_LAYOUT_INFO.layoutType,
+              deviceInfo.getKeyboardLayoutInfo()->layoutType);
+}
+
 // --- KeyboardInputMapperTest_ExternalDevice ---
 
 class KeyboardInputMapperTest_ExternalDevice : public InputMapperTest {