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

Commit fdc76dc9 authored by Vaibhav Devmurari's avatar Vaibhav Devmurari
Browse files

Add tests for logging keyboard configuration

Test: atest KeyboardLayoutManagerTests
Bug: 280421650
Change-Id: I1cab688ecc1b9de83daf4b2a15becec03e316714
parent ffb202ea
Loading
Loading
Loading
Loading
+6 −3
Original line number Diff line number Diff line
@@ -71,6 +71,7 @@ import android.widget.Toast;

import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.inputmethod.InputMethodSubtypeHandle;
import com.android.internal.messages.nano.SystemMessageProto;
import com.android.internal.notification.SystemNotificationChannels;
@@ -99,7 +100,7 @@ import java.util.stream.Stream;
 *
 * @hide
 */
final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
class KeyboardLayoutManager implements InputManager.InputDeviceListener {

    private static final String TAG = "KeyboardLayoutManager";

@@ -1295,7 +1296,8 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
    }

    @SuppressLint("MissingPermission")
    private List<ImeInfo> getImeInfoListForLayoutMapping() {
    @VisibleForTesting
    public List<ImeInfo> getImeInfoListForLayoutMapping() {
        List<ImeInfo> imeInfoList = new ArrayList<>();
        UserManager userManager = Objects.requireNonNull(
                mContext.getSystemService(UserManager.class));
@@ -1402,7 +1404,8 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
        }
    }

    private static class ImeInfo {
    @VisibleForTesting
    public static class ImeInfo {
        @UserIdInt int mUserId;
        @NonNull InputMethodSubtypeHandle mImeSubtypeHandle;
        @Nullable InputMethodSubtype mImeSubtype;
+4 −0
Original line number Diff line number Diff line
@@ -9,6 +9,10 @@ package {

android_test {
    name: "InputTests",
    defaults: [
        // For ExtendedMockito dependencies.
        "modules-utils-testable-device-config-defaults",
    ],
    srcs: [
        "src/**/*.java",
        "src/**/*.kt",
+160 −12
Original line number Diff line number Diff line
@@ -32,11 +32,16 @@ import android.os.Bundle
import android.os.test.TestLooper
import android.platform.test.annotations.Presubmit
import android.provider.Settings
import android.util.proto.ProtoOutputStream
import android.view.InputDevice
import android.view.inputmethod.InputMethodInfo
import android.view.inputmethod.InputMethodSubtype
import androidx.test.core.R
import androidx.test.core.app.ApplicationProvider
import com.android.dx.mockito.inline.extended.ExtendedMockito
import com.android.internal.os.KeyboardConfiguredProto
import com.android.internal.util.FrameworkStatsLog
import com.android.modules.utils.testing.ExtendedMockitoRule
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotEquals
@@ -46,9 +51,9 @@ import org.junit.Assert.assertThrows
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.mockito.ArgumentMatchers
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.junit.MockitoJUnit
import java.io.FileNotFoundException
import java.io.FileOutputStream
import java.io.IOException
@@ -96,6 +101,9 @@ class KeyboardLayoutManagerTests {
        private const val ENGLISH_US_LAYOUT_NAME = "keyboard_layout_english_us"
        private const val ENGLISH_UK_LAYOUT_NAME = "keyboard_layout_english_uk"
        private const val VENDOR_SPECIFIC_LAYOUT_NAME = "keyboard_layout_vendorId:1,productId:1"
        const val LAYOUT_TYPE_QWERTZ = 2
        const val LAYOUT_TYPE_QWERTY = 1
        const val LAYOUT_TYPE_DEFAULT = 0
    }

    private val ENGLISH_US_LAYOUT_DESCRIPTOR = createLayoutDescriptor(ENGLISH_US_LAYOUT_NAME)
@@ -103,8 +111,10 @@ class KeyboardLayoutManagerTests {
    private val VENDOR_SPECIFIC_LAYOUT_DESCRIPTOR =
        createLayoutDescriptor(VENDOR_SPECIFIC_LAYOUT_NAME)

    @get:Rule
    val rule = MockitoJUnit.rule()!!
    @JvmField
    @Rule
    val extendedMockitoRule = ExtendedMockitoRule.Builder(this)
            .mockStatic(FrameworkStatsLog::class.java).build()!!

    @Mock
    private lateinit var iInputManager: IInputManager
@@ -145,7 +155,9 @@ class KeyboardLayoutManagerTests {
            override fun finishWrite(fos: FileOutputStream?, success: Boolean) {}
        })
        testLooper = TestLooper()
        keyboardLayoutManager = KeyboardLayoutManager(context, native, dataStore, testLooper.looper)
        keyboardLayoutManager = Mockito.spy(
            KeyboardLayoutManager(context, native, dataStore, testLooper.looper)
        )
        setupInputDevices()
        setupBroadcastReceiver()
        setupIme()
@@ -827,6 +839,100 @@ class KeyboardLayoutManagerTests {
        }
    }

    @Test
    fun testConfigurationLogged_onInputDeviceAdded_VirtualKeyboardBasedSelection() {
        val imeInfos = listOf(
                KeyboardLayoutManager.ImeInfo(0, imeInfo,
                        createImeSubtypeForLanguageTagAndLayoutType("de-Latn", "qwertz")))
        Mockito.doReturn(imeInfos).`when`(keyboardLayoutManager).imeInfoListForLayoutMapping
        NewSettingsApiFlag(true).use {
            keyboardLayoutManager.onInputDeviceAdded(keyboardDevice.id)
            ExtendedMockito.verify {
                FrameworkStatsLog.write(
                        ArgumentMatchers.eq(FrameworkStatsLog.KEYBOARD_CONFIGURED),
                        ArgumentMatchers.anyBoolean(),
                        ArgumentMatchers.eq(keyboardDevice.vendorId),
                        ArgumentMatchers.eq(keyboardDevice.productId),
                        ArgumentMatchers.eq(createByteArray(
                                KeyboardMetricsCollector.DEFAULT_LANGUAGE_TAG,
                                LAYOUT_TYPE_DEFAULT,
                                "German",
                                KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD,
                                "de-Latn",
                                LAYOUT_TYPE_QWERTZ))
                )
            }
        }
    }

    @Test
    fun testConfigurationLogged_onInputDeviceAdded_DeviceBasedSelection() {
        val imeInfos = listOf(
                KeyboardLayoutManager.ImeInfo(0, imeInfo,
                        createImeSubtypeForLanguageTagAndLayoutType("de-Latn", "qwertz")))
        Mockito.doReturn(imeInfos).`when`(keyboardLayoutManager).imeInfoListForLayoutMapping
        NewSettingsApiFlag(true).use {
            keyboardLayoutManager.onInputDeviceAdded(englishQwertyKeyboardDevice.id)
            ExtendedMockito.verify {
                FrameworkStatsLog.write(
                        ArgumentMatchers.eq(FrameworkStatsLog.KEYBOARD_CONFIGURED),
                        ArgumentMatchers.anyBoolean(),
                        ArgumentMatchers.eq(englishQwertyKeyboardDevice.vendorId),
                        ArgumentMatchers.eq(englishQwertyKeyboardDevice.productId),
                        ArgumentMatchers.eq(createByteArray(
                                "en",
                                LAYOUT_TYPE_QWERTY,
                                "English (US)",
                                KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE,
                                "de-Latn",
                                LAYOUT_TYPE_QWERTZ))
                )
            }
        }
    }

    @Test
    fun testConfigurationLogged_onInputDeviceAdded_DefaultSelection() {
        val imeInfos = listOf(KeyboardLayoutManager.ImeInfo(0, imeInfo, createImeSubtype()))
        Mockito.doReturn(imeInfos).`when`(keyboardLayoutManager).imeInfoListForLayoutMapping
        NewSettingsApiFlag(true).use {
            keyboardLayoutManager.onInputDeviceAdded(keyboardDevice.id)
            ExtendedMockito.verify {
                FrameworkStatsLog.write(
                        ArgumentMatchers.eq(FrameworkStatsLog.KEYBOARD_CONFIGURED),
                        ArgumentMatchers.anyBoolean(),
                        ArgumentMatchers.eq(keyboardDevice.vendorId),
                        ArgumentMatchers.eq(keyboardDevice.productId),
                        ArgumentMatchers.eq(createByteArray(
                                KeyboardMetricsCollector.DEFAULT_LANGUAGE_TAG,
                                LAYOUT_TYPE_DEFAULT,
                                "Default",
                                KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEFAULT,
                                KeyboardMetricsCollector.DEFAULT_LANGUAGE_TAG,
                                LAYOUT_TYPE_DEFAULT))
                )
            }
        }
    }

    @Test
    fun testConfigurationNotLogged_onInputDeviceChanged() {
        val imeInfos = listOf(KeyboardLayoutManager.ImeInfo(0, imeInfo, createImeSubtype()))
        Mockito.doReturn(imeInfos).`when`(keyboardLayoutManager).imeInfoListForLayoutMapping
        NewSettingsApiFlag(true).use {
            keyboardLayoutManager.onInputDeviceChanged(keyboardDevice.id)
            ExtendedMockito.verify({
                FrameworkStatsLog.write(
                        ArgumentMatchers.eq(FrameworkStatsLog.KEYBOARD_CONFIGURED),
                        ArgumentMatchers.anyBoolean(),
                        ArgumentMatchers.anyInt(),
                        ArgumentMatchers.anyInt(),
                        ArgumentMatchers.any(ByteArray::class.java)
                )
            }, Mockito.times(0))
        }
    }

    private fun assertCorrectLayout(
        device: InputDevice,
        imeSubtype: InputMethodSubtype,
@@ -842,18 +948,60 @@ class KeyboardLayoutManagerTests {
    }

    private fun createImeSubtype(): InputMethodSubtype =
        InputMethodSubtype.InputMethodSubtypeBuilder().setSubtypeId(nextImeSubtypeId++).build()
            createImeSubtypeForLanguageTagAndLayoutType(null, null)

    private fun createImeSubtypeForLanguageTag(languageTag: String): InputMethodSubtype =
        InputMethodSubtype.InputMethodSubtypeBuilder().setSubtypeId(nextImeSubtypeId++)
            .setLanguageTag(languageTag).build()
            createImeSubtypeForLanguageTagAndLayoutType(languageTag, null)

    private fun createImeSubtypeForLanguageTagAndLayoutType(
        languageTag: String,
        layoutType: String
    ): InputMethodSubtype =
        InputMethodSubtype.InputMethodSubtypeBuilder().setSubtypeId(nextImeSubtypeId++)
            .setPhysicalKeyboardHint(ULocale.forLanguageTag(languageTag), layoutType).build()
            languageTag: String?,
            layoutType: String?
    ): InputMethodSubtype {
        val builder = InputMethodSubtype.InputMethodSubtypeBuilder()
                .setSubtypeId(nextImeSubtypeId++)
                .setIsAuxiliary(false)
                .setSubtypeMode("keyboard")
        if (languageTag != null && layoutType != null) {
            builder.setPhysicalKeyboardHint(ULocale.forLanguageTag(languageTag), layoutType)
        } else if (languageTag != null) {
            builder.setLanguageTag(languageTag)
        }
        return builder.build()
    }

    private fun createByteArray(
            expectedLanguageTag: String, expectedLayoutType: Int, expectedLayoutName: String,
            expectedCriteria: Int, expectedImeLanguageTag: String, expectedImeLayoutType: Int): ByteArray {
        val proto = ProtoOutputStream()
        val keyboardLayoutConfigToken = proto.start(
                KeyboardConfiguredProto.RepeatedKeyboardLayoutConfig.KEYBOARD_LAYOUT_CONFIG)
        proto.write(
                KeyboardConfiguredProto.KeyboardLayoutConfig.KEYBOARD_LANGUAGE_TAG,
                expectedLanguageTag
        )
        proto.write(
                KeyboardConfiguredProto.KeyboardLayoutConfig.KEYBOARD_LAYOUT_TYPE,
                expectedLayoutType
        )
        proto.write(
                KeyboardConfiguredProto.KeyboardLayoutConfig.KEYBOARD_LAYOUT_NAME,
                expectedLayoutName
        )
        proto.write(
                KeyboardConfiguredProto.KeyboardLayoutConfig.LAYOUT_SELECTION_CRITERIA,
                expectedCriteria
        )
        proto.write(
                KeyboardConfiguredProto.KeyboardLayoutConfig.IME_LANGUAGE_TAG,
                expectedImeLanguageTag
        )
        proto.write(
                KeyboardConfiguredProto.KeyboardLayoutConfig.IME_LAYOUT_TYPE,
                expectedImeLayoutType
        )
        proto.end(keyboardLayoutConfigToken);
        return proto.bytes
    }

    private fun hasLayout(layoutList: Array<KeyboardLayout>, layoutDesc: String): Boolean {
        for (kl in layoutList) {