Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 4a02662b authored by Vaibhav Devmurari's avatar Vaibhav Devmurari Committed by Automerger Merge Worker
Browse files

Merge "Log KeyboardConfigured atom" into udc-qpr-dev am: b85e32f7

parents eb854c69 b85e32f7
Loading
Loading
Loading
Loading
+22 −1
Original line number Diff line number Diff line
@@ -73,7 +73,7 @@ public final class KeyboardLayout implements Parcelable, Comparable<KeyboardLayo
    private final int mProductId;

    /** Currently supported Layout types in the KCM files */
    private enum LayoutType {
    public enum LayoutType {
        UNDEFINED(0, LAYOUT_TYPE_UNDEFINED),
        QWERTY(1, LAYOUT_TYPE_QWERTY),
        QWERTZ(2, LAYOUT_TYPE_QWERTZ),
@@ -88,9 +88,11 @@ public final class KeyboardLayout implements Parcelable, Comparable<KeyboardLayo
        private final int mValue;
        private final String mName;
        private static final Map<Integer, LayoutType> VALUE_TO_ENUM_MAP = new HashMap<>();
        private static final Map<String, LayoutType> NAME_TO_ENUM_MAP = new HashMap<>();
        static {
            for (LayoutType type : LayoutType.values()) {
                VALUE_TO_ENUM_MAP.put(type.mValue, type);
                NAME_TO_ENUM_MAP.put(type.mName, type);
            }
        }

@@ -110,6 +112,25 @@ public final class KeyboardLayout implements Parcelable, Comparable<KeyboardLayo
        private String getName() {
            return mName;
        }

        /**
         * Returns enum value for provided layout type
         * @param layoutName name of the layout type
         * @return int value corresponding to the LayoutType enum that matches the layout name.
         * (LayoutType.UNDEFINED if no match found)
         */
        public static int getLayoutTypeEnumValue(String layoutName) {
            return NAME_TO_ENUM_MAP.getOrDefault(layoutName, UNDEFINED).getValue();
        }

        /**
         * Returns name for provided layout type enum value
         * @param enumValue value representation for LayoutType enum
         * @return Layout name corresponding to the enum value (LAYOUT_TYPE_UNDEFINED if not found)
         */
        public static String getLayoutNameFromValue(int enumValue) {
            return VALUE_TO_ENUM_MAP.getOrDefault(enumValue, UNDEFINED).getName();
        }
    }

    @NonNull
+127 −54
Original line number Diff line number Diff line
@@ -16,6 +16,10 @@

package com.android.server.input;

import static com.android.server.input.KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE;
import static com.android.server.input.KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_USER;
import static com.android.server.input.KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD;

import android.annotation.AnyThread;
import android.annotation.MainThread;
import android.annotation.NonNull;
@@ -67,6 +71,8 @@ import com.android.internal.inputmethod.InputMethodSubtypeHandle;
import com.android.internal.messages.nano.SystemMessageProto;
import com.android.internal.notification.SystemNotificationChannels;
import com.android.internal.util.XmlUtils;
import com.android.server.input.KeyboardMetricsCollector.KeyboardConfigurationEvent;
import com.android.server.input.KeyboardMetricsCollector.LayoutSelectionCriteria;
import com.android.server.inputmethod.InputMethodManagerInternal;

import libcore.io.Streams;
@@ -118,7 +124,7 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
    // This cache stores "best-matched" layouts so that we don't need to run the matching
    // algorithm repeatedly.
    @GuardedBy("mKeyboardLayoutCache")
    private final Map<String, String> mKeyboardLayoutCache = new ArrayMap<>();
    private final Map<String, KeyboardLayoutInfo> mKeyboardLayoutCache = new ArrayMap<>();
    private final Object mImeInfoLock = new Object();
    @Nullable
    @GuardedBy("mImeInfoLock")
@@ -194,7 +200,8 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
                        setCurrentKeyboardLayoutForInputDevice(inputDevice.getIdentifier(), layout);
                    }
                }
                config.setCurrentLayout(layout);
                config.setCurrentLayout(
                        new KeyboardLayoutInfo(layout, LAYOUT_SELECTION_CRITERIA_USER));
                if (layout == null) {
                    // In old settings show notification always until user manually selects a
                    // layout in the settings.
@@ -205,18 +212,19 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
            final InputDeviceIdentifier identifier = inputDevice.getIdentifier();
            final String key = getLayoutDescriptor(identifier);
            Set<String> selectedLayouts = new HashSet<>();
            for (ImeInfo imeInfo : getImeInfoListForLayoutMapping()) {
            List<ImeInfo> imeInfoList = getImeInfoListForLayoutMapping();
            List<KeyboardLayoutInfo> layoutInfoList = new ArrayList<>();
            boolean hasMissingLayout = false;
            for (ImeInfo imeInfo : imeInfoList) {
                // Check if the layout has been previously configured
                String layout = getKeyboardLayoutForInputDeviceInternal(identifier,
                        new ImeInfo(imeInfo.mUserId, imeInfo.mImeSubtypeHandle,
                                imeInfo.mImeSubtype));
                if (layout == null) {
                    // If even one layout not configured properly, we need to ask user to configure
                    // the keyboard properly from the Settings.
                    selectedLayouts.clear();
                    break;
                KeyboardLayoutInfo layoutInfo = getKeyboardLayoutForInputDeviceInternal(identifier,
                        imeInfo);
                boolean noLayoutFound = layoutInfo == null || layoutInfo.mDescriptor == null;
                if (!noLayoutFound) {
                    selectedLayouts.add(layoutInfo.mDescriptor);
                }
                selectedLayouts.add(layout);
                layoutInfoList.add(layoutInfo);
                hasMissingLayout |= noLayoutFound;
            }

            if (DEBUG) {
@@ -225,24 +233,38 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
                                + selectedLayouts);
            }

            // If even one layout not configured properly, we need to ask user to configure
            // the keyboard properly from the Settings.
            if (hasMissingLayout) {
                selectedLayouts.clear();
            }

            config.setConfiguredLayouts(selectedLayouts);

            // Update current layout: If there is a change then need to reload.
            synchronized (mImeInfoLock) {
                String layout = getKeyboardLayoutForInputDeviceInternal(
                KeyboardLayoutInfo layoutInfo = getKeyboardLayoutForInputDeviceInternal(
                        inputDevice.getIdentifier(), mCurrentImeInfo);
                if (!Objects.equals(layout, config.getCurrentLayout())) {
                    config.setCurrentLayout(layout);
                if (!Objects.equals(layoutInfo, config.getCurrentLayout())) {
                    config.setCurrentLayout(layoutInfo);
                    mHandler.sendEmptyMessage(MSG_RELOAD_KEYBOARD_LAYOUTS);
                }
            }

            synchronized (mDataStore) {
                try {
                    boolean isFirstConfiguration = !mDataStore.hasInputDeviceEntry(key);
                    if (mDataStore.setSelectedKeyboardLayouts(key, selectedLayouts)) {
                        // Need to show the notification only if layout selection changed
                        // from the previous configuration
                        needToShowNotification = true;

                        // Logging keyboard configuration data to statsd only if the
                        // configuration changed from the previous configuration. Currently
                        // only logging for New Settings UI where we are using IME to decide
                        // the layout information.
                        logKeyboardConfigurationEvent(inputDevice, imeInfoList, layoutInfoList,
                                isFirstConfiguration);
                    }
                } finally {
                    mDataStore.saveIfNeeded();
@@ -252,8 +274,6 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
        if (needToShowNotification) {
            maybeUpdateNotification();
        }
        // TODO (b/280421650): Implement logging statements using KeyboardMetricsCollector
        //  for KeyboardConfigured atom
    }

    private String getDefaultKeyboardLayout(final InputDevice inputDevice) {
@@ -403,7 +423,7 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {

    @AnyThread
    @Nullable
    public KeyboardLayout getKeyboardLayout(String keyboardLayoutDescriptor) {
    public KeyboardLayout getKeyboardLayout(@NonNull String keyboardLayoutDescriptor) {
        Objects.requireNonNull(keyboardLayoutDescriptor,
                "keyboardLayoutDescriptor must not be null");

@@ -751,8 +771,9 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
        String keyboardLayoutDescriptor;
        if (useNewSettingsUi()) {
            synchronized (mImeInfoLock) {
                keyboardLayoutDescriptor = getKeyboardLayoutForInputDeviceInternal(identifier,
                KeyboardLayoutInfo layoutInfo = getKeyboardLayoutForInputDeviceInternal(identifier,
                        mCurrentImeInfo);
                keyboardLayoutDescriptor = layoutInfo == null ? null : layoutInfo.mDescriptor;
            }
        } else {
            keyboardLayoutDescriptor = getCurrentKeyboardLayoutForInputDevice(identifier);
@@ -789,13 +810,13 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
            return null;
        }
        InputMethodSubtypeHandle subtypeHandle = InputMethodSubtypeHandle.of(imeInfo, imeSubtype);
        String layout = getKeyboardLayoutForInputDeviceInternal(identifier,
        KeyboardLayoutInfo layoutInfo = getKeyboardLayoutForInputDeviceInternal(identifier,
                new ImeInfo(userId, subtypeHandle, imeSubtype));
        if (DEBUG) {
            Slog.d(TAG, "getKeyboardLayoutForInputDevice() " + identifier.toString() + ", userId : "
                    + userId + ", subtypeHandle = " + subtypeHandle + " -> " + layout);
                    + userId + ", subtypeHandle = " + subtypeHandle + " -> " + layoutInfo);
        }
        return layout;
        return layoutInfo != null ? layoutInfo.mDescriptor : null;
    }

    @AnyThread
@@ -926,11 +947,11 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
            for (int i = 0; i < mConfiguredKeyboards.size(); i++) {
                InputDevice inputDevice = Objects.requireNonNull(
                        getInputDevice(mConfiguredKeyboards.keyAt(i)));
                String layout = getKeyboardLayoutForInputDeviceInternal(inputDevice.getIdentifier(),
                        mCurrentImeInfo);
                KeyboardLayoutInfo layoutInfo = getKeyboardLayoutForInputDeviceInternal(
                        inputDevice.getIdentifier(), mCurrentImeInfo);
                KeyboardConfiguration config = mConfiguredKeyboards.valueAt(i);
                if (!Objects.equals(layout, config.getCurrentLayout())) {
                    config.setCurrentLayout(layout);
                if (!Objects.equals(layoutInfo, config.getCurrentLayout())) {
                    config.setCurrentLayout(layoutInfo);
                    mHandler.sendEmptyMessage(MSG_RELOAD_KEYBOARD_LAYOUTS);
                    return;
                }
@@ -939,39 +960,40 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
    }

    @Nullable
    private String getKeyboardLayoutForInputDeviceInternal(InputDeviceIdentifier identifier,
            @Nullable ImeInfo imeInfo) {
    private KeyboardLayoutInfo getKeyboardLayoutForInputDeviceInternal(
            InputDeviceIdentifier identifier, @Nullable ImeInfo imeInfo) {
        InputDevice inputDevice = getInputDevice(identifier);
        if (inputDevice == null || inputDevice.isVirtual() || !inputDevice.isFullKeyboard()) {
            return null;
        }
        String key = createLayoutKey(identifier, imeInfo);
        String layout;
        synchronized (mDataStore) {
            layout = mDataStore.getKeyboardLayout(getLayoutDescriptor(identifier), key);
            String layout = mDataStore.getKeyboardLayout(getLayoutDescriptor(identifier), key);
            if (layout != null) {
                return new KeyboardLayoutInfo(layout, LAYOUT_SELECTION_CRITERIA_USER);
            }
        if (layout == null) {
        }

        synchronized (mKeyboardLayoutCache) {
            // Check Auto-selected layout cache to see if layout had been previously selected
            if (mKeyboardLayoutCache.containsKey(key)) {
                    layout = mKeyboardLayoutCache.get(key);
                return mKeyboardLayoutCache.get(key);
            } else {
                // NOTE: This list is already filtered based on IME Script code
                KeyboardLayout[] layoutList = getKeyboardLayoutListForInputDeviceInternal(
                        identifier, imeInfo);
                // Call auto-matching algorithm to find the best matching layout
                    layout = getDefaultKeyboardLayoutBasedOnImeInfo(inputDevice, imeInfo,
                            layoutList);
                    mKeyboardLayoutCache.put(key, layout);
                KeyboardLayoutInfo layoutInfo =
                        getDefaultKeyboardLayoutBasedOnImeInfo(inputDevice, imeInfo, layoutList);
                mKeyboardLayoutCache.put(key, layoutInfo);
                return layoutInfo;
            }
        }
    }
        return layout;
    }

    @Nullable
    private static String getDefaultKeyboardLayoutBasedOnImeInfo(InputDevice inputDevice,
            @Nullable ImeInfo imeInfo, KeyboardLayout[] layoutList) {
    private static KeyboardLayoutInfo getDefaultKeyboardLayoutBasedOnImeInfo(
            InputDevice inputDevice, @Nullable ImeInfo imeInfo, KeyboardLayout[] layoutList) {
        Arrays.sort(layoutList);

        // Check <VendorID, ProductID> matching for explicitly declared custom KCM files.
@@ -984,7 +1006,8 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
                                    + "vendor and product Ids. " + inputDevice.getIdentifier()
                                    + " : " + layout.getDescriptor());
                }
                return layout.getDescriptor();
                return new KeyboardLayoutInfo(layout.getDescriptor(),
                        LAYOUT_SELECTION_CRITERIA_DEVICE);
            }
        }

@@ -1001,7 +1024,7 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
                                    + "HW information (Language tag and Layout type). "
                                    + inputDevice.getIdentifier() + " : " + layoutDesc);
                }
                return layoutDesc;
                return new KeyboardLayoutInfo(layoutDesc, LAYOUT_SELECTION_CRITERIA_DEVICE);
            }
        }

@@ -1023,7 +1046,10 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
                            + "IME locale matching. " + inputDevice.getIdentifier() + " : "
                            + layoutDesc);
        }
        return layoutDesc;
        if (layoutDesc != null) {
            return new KeyboardLayoutInfo(layoutDesc, LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD);
        }
        return null;
    }

    @Nullable
@@ -1229,6 +1255,26 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
        }
    }

    private void logKeyboardConfigurationEvent(@NonNull InputDevice inputDevice,
            @NonNull List<ImeInfo> imeInfoList, @NonNull List<KeyboardLayoutInfo> layoutInfoList,
            boolean isFirstConfiguration) {
        if (imeInfoList.isEmpty() || layoutInfoList.isEmpty()) {
            return;
        }
        KeyboardConfigurationEvent.Builder configurationEventBuilder =
                new KeyboardConfigurationEvent.Builder(inputDevice).setIsFirstTimeConfiguration(
                        isFirstConfiguration);
        for (int i = 0; i < imeInfoList.size(); i++) {
            KeyboardLayoutInfo layoutInfo = layoutInfoList.get(i);
            boolean noLayoutFound = layoutInfo == null || layoutInfo.mDescriptor == null;
            configurationEventBuilder.addLayoutSelection(imeInfoList.get(i).mImeSubtype,
                    noLayoutFound ? null : getKeyboardLayout(layoutInfo.mDescriptor),
                    noLayoutFound ? LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD
                            : layoutInfo.mSelectionCriteria);
        }
        KeyboardMetricsCollector.logKeyboardConfiguredAtom(configurationEventBuilder.build());
    }

    private boolean handleMessage(Message msg) {
        switch (msg.what) {
            case MSG_UPDATE_EXISTING_DEVICES:
@@ -1411,7 +1457,7 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {

        // If null, it means no layout is selected for the device.
        @Nullable
        private String mCurrentLayout;
        private KeyboardLayoutInfo mCurrentLayout;

        private boolean hasConfiguredLayouts() {
            return mConfiguredLayouts != null && !mConfiguredLayouts.isEmpty();
@@ -1427,15 +1473,42 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
        }

        @Nullable
        private String getCurrentLayout() {
        private KeyboardLayoutInfo getCurrentLayout() {
            return mCurrentLayout;
        }

        private void setCurrentLayout(String currentLayout) {
        private void setCurrentLayout(KeyboardLayoutInfo currentLayout) {
            mCurrentLayout = currentLayout;
        }
    }

    private static class KeyboardLayoutInfo {
        @Nullable
        private final String mDescriptor;
        @LayoutSelectionCriteria
        private final int mSelectionCriteria;

        private KeyboardLayoutInfo(@Nullable String descriptor,
                @LayoutSelectionCriteria int selectionCriteria) {
            mDescriptor = descriptor;
            mSelectionCriteria = selectionCriteria;
        }

        @Override
        public boolean equals(Object obj) {
            if (obj instanceof KeyboardLayoutInfo) {
                return Objects.equals(mDescriptor, ((KeyboardLayoutInfo) obj).mDescriptor)
                        && mSelectionCriteria == ((KeyboardLayoutInfo) obj).mSelectionCriteria;
            }
            return false;
        }

        @Override
        public int hashCode() {
            return 31 * mSelectionCriteria + mDescriptor.hashCode();
        }
    }

    private interface KeyboardLayoutVisitor {
        void visitKeyboardLayout(Resources resources,
                int keyboardLayoutResId, KeyboardLayout layout);
+212 −66

File changed.

Preview size limit exceeded, changes collapsed.

+4 −0
Original line number Diff line number Diff line
@@ -101,6 +101,10 @@ final class PersistentDataStore {
        }
    }

    public boolean hasInputDeviceEntry(String inputDeviceDescriptor) {
        return getInputDeviceState(inputDeviceDescriptor) != null;
    }

    public TouchCalibration getTouchCalibration(String inputDeviceDescriptor, int surfaceRotation) {
        InputDeviceState state = getInputDeviceState(inputDeviceDescriptor);
        if (state == null) {
+179 −0
Original line number Diff line number Diff line
/*
 * Copyright 2023 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.server.input

import android.hardware.input.KeyboardLayout
import android.icu.util.ULocale
import android.platform.test.annotations.Presubmit
import android.view.InputDevice
import android.view.inputmethod.InputMethodSubtype
import org.junit.Assert.assertEquals
import org.junit.Assert.assertThrows
import org.junit.Assert.assertTrue
import org.junit.Test

private fun createKeyboard(
    deviceId: Int,
    vendorId: Int,
    productId: Int,
    languageTag: String?,
    layoutType: String?
): InputDevice =
    InputDevice.Builder()
        .setId(deviceId)
        .setName("Device $deviceId")
        .setDescriptor("descriptor $deviceId")
        .setSources(InputDevice.SOURCE_KEYBOARD)
        .setKeyboardType(InputDevice.KEYBOARD_TYPE_ALPHABETIC)
        .setExternal(true)
        .setVendorId(vendorId)
        .setProductId(productId)
        .setKeyboardLanguageTag(languageTag)
        .setKeyboardLayoutType(layoutType)
        .build()

private fun createImeSubtype(
    imeSubtypeId: Int,
    languageTag: String,
    layoutType: String
): InputMethodSubtype =
    InputMethodSubtype.InputMethodSubtypeBuilder().setSubtypeId(imeSubtypeId)
        .setPhysicalKeyboardHint(ULocale.forLanguageTag(languageTag), layoutType).build()

/**
 * Tests for {@link KeyboardMetricsCollector}.
 *
 * Build/Install/Run:
 * atest FrameworksServicesTests:KeyboardMetricsCollectorTests
 */
@Presubmit
class KeyboardMetricsCollectorTests {

    companion object {
        const val DEVICE_ID = 1
        const val DEFAULT_VENDOR_ID = 123
        const val DEFAULT_PRODUCT_ID = 456
    }

    @Test
    fun testCreateKeyboardConfigurationEvent_throwsExceptionWithoutAnyLayoutConfiguration() {
        assertThrows(IllegalStateException::class.java) {
            KeyboardMetricsCollector.KeyboardConfigurationEvent.Builder(
                createKeyboard(
                    DEVICE_ID,
                    DEFAULT_VENDOR_ID,
                    DEFAULT_PRODUCT_ID,
                    null,
                    null
                )
            ).build()
        }
    }

    @Test
    fun testCreateKeyboardConfigurationEvent_throwsExceptionWithInvalidLayoutSelectionCriteria() {
        assertThrows(IllegalStateException::class.java) {
            KeyboardMetricsCollector.KeyboardConfigurationEvent.Builder(
                createKeyboard(
                    DEVICE_ID,
                    DEFAULT_VENDOR_ID,
                    DEFAULT_PRODUCT_ID,
                    null,
                    null
                )
            ).addLayoutSelection(createImeSubtype(1, "en-US", "qwerty"), null, 123).build()
        }
    }

    @Test
    fun testCreateKeyboardConfigurationEvent_withMultipleConfigurations() {
        val builder = KeyboardMetricsCollector.KeyboardConfigurationEvent.Builder(
            createKeyboard(
                DEVICE_ID,
                DEFAULT_VENDOR_ID,
                DEFAULT_PRODUCT_ID,
                "de-CH",
                "qwertz"
            )
        )
        val event = builder.addLayoutSelection(
            createImeSubtype(1, "en-US", "qwerty"),
            KeyboardLayout(null, "English(US)(Qwerty)", null, 0, null, 0, 0, 0),
            KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD
        ).addLayoutSelection(
            createImeSubtype(2, "en-US", "azerty"),
            null,
            KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_USER
        ).addLayoutSelection(
            createImeSubtype(3, "en-US", "qwerty"),
            KeyboardLayout(null, "German", null, 0, null, 0, 0, 0),
            KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE
        ).setIsFirstTimeConfiguration(true).build()

        assertEquals(
            "KeyboardConfigurationEvent should pick vendor ID from provided InputDevice",
            DEFAULT_VENDOR_ID,
            event.vendorId
        )
        assertEquals(
            "KeyboardConfigurationEvent should pick product ID from provided InputDevice",
            DEFAULT_PRODUCT_ID,
            event.productId
        )
        assertTrue(event.isFirstConfiguration)

        assertEquals(
            "KeyboardConfigurationEvent should contain 3 configurations provided",
            3,
            event.layoutConfigurations.size
        )
        assertExpectedLayoutConfiguration(
            event.layoutConfigurations[0],
            "en-US",
            KeyboardLayout.LayoutType.getLayoutTypeEnumValue("qwerty"),
            "English(US)(Qwerty)",
            KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD
        )
        assertExpectedLayoutConfiguration(
            event.layoutConfigurations[1],
            "en-US",
            KeyboardLayout.LayoutType.getLayoutTypeEnumValue("azerty"),
            KeyboardMetricsCollector.DEFAULT_LAYOUT,
            KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_USER
        )
        assertExpectedLayoutConfiguration(
            event.layoutConfigurations[2],
            "de-CH",
            KeyboardLayout.LayoutType.getLayoutTypeEnumValue("qwertz"),
            "German",
            KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE
        )
    }

    private fun assertExpectedLayoutConfiguration(
        configuration: KeyboardMetricsCollector.LayoutConfiguration,
        expectedLanguageTag: String,
        expectedLayoutType: Int,
        expectedSelectedLayout: String,
        expectedLayoutSelectionCriteria: Int
    ) {
        assertEquals(expectedLanguageTag, configuration.keyboardLanguageTag)
        assertEquals(expectedLayoutType, configuration.keyboardLayoutType)
        assertEquals(expectedSelectedLayout, configuration.keyboardLayoutName)
        assertEquals(expectedLayoutSelectionCriteria, configuration.layoutSelectionCriteria)
    }
}
 No newline at end of file