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

Commit df7b46c3 authored by Josh's avatar Josh
Browse files

new implementation of IME shortcuts retrieval

Fixes: 341045650
Test: atest ShortcutHelperCategoriesInteractorTest
ShortcutHelperCategoriesRepositoryTest
Flag: com.android.systemui.keyboard_shortcut_helper_rewrite

Change-Id: I0869db110683738d9c5bda359b5b48af3c91e9bb
parent 7b214e9d
Loading
Loading
Loading
Loading
+82 −4
Original line number Original line Diff line number Diff line
@@ -16,11 +16,23 @@


package com.android.systemui.keyboard.shortcut.data.repository
package com.android.systemui.keyboard.shortcut.data.repository


import android.view.KeyEvent
import android.view.KeyboardShortcutGroup
import android.view.KeyboardShortcutInfo
import android.view.WindowManager
import android.view.WindowManager.KeyboardShortcutsReceiver
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyboard.shortcut.data.source.MultitaskingShortcutsSource
import com.android.systemui.keyboard.shortcut.data.source.MultitaskingShortcutsSource
import com.android.systemui.keyboard.shortcut.data.source.SystemShortcutsSource
import com.android.systemui.keyboard.shortcut.data.source.SystemShortcutsSource
import com.android.systemui.keyboard.shortcut.shared.model.Shortcut
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCommand
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutHelperState.Active
import com.android.systemui.keyboard.shortcut.shared.model.shortcutCategory
import javax.inject.Inject
import javax.inject.Inject
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.suspendCancellableCoroutine


@SysUISingleton
@SysUISingleton
class ShortcutHelperCategoriesRepository
class ShortcutHelperCategoriesRepository
@@ -28,11 +40,77 @@ class ShortcutHelperCategoriesRepository
constructor(
constructor(
    private val systemShortcutsSource: SystemShortcutsSource,
    private val systemShortcutsSource: SystemShortcutsSource,
    private val multitaskingShortcutsSource: MultitaskingShortcutsSource,
    private val multitaskingShortcutsSource: MultitaskingShortcutsSource,
    private val windowManager: WindowManager,
    shortcutHelperStateRepository: ShortcutHelperStateRepository
) {
) {


    fun systemShortcutsCategory(): ShortcutCategory =
    val systemShortcutsCategory =
        systemShortcutsSource.systemShortcutsCategory()
        shortcutHelperStateRepository.state.map {
            if (it is Active) systemShortcutsSource.systemShortcutsCategory() else null
        }

    val multitaskingShortcutsCategory =
        shortcutHelperStateRepository.state.map {
            if (it is Active) multitaskingShortcutsSource.multitaskingShortcutCategory() else null
        }


    fun multitaskingShortcutsCategory(): ShortcutCategory =
    val imeShortcutsCategory =
        multitaskingShortcutsSource.multitaskingShortcutCategory()
        shortcutHelperStateRepository.state.map {
            if (it is Active) retrieveImeShortcuts(it.deviceId) else null
        }

    private suspend fun retrieveImeShortcuts(deviceId: Int): ShortcutCategory {
        return suspendCancellableCoroutine { continuation ->
            val shortcutsReceiver = KeyboardShortcutsReceiver { shortcutGroups ->
                continuation.resumeWith(Result.success(toShortcutCategory(shortcutGroups)))
            }
            windowManager.requestImeKeyboardShortcuts(shortcutsReceiver, deviceId)
        }
    }

    private fun toShortcutCategory(shortcutGroups: List<KeyboardShortcutGroup>) =
        shortcutCategory(ShortcutCategoryType.IME) {
            shortcutGroups.map { shortcutGroup ->
                subCategory(shortcutGroup.label.toString(), toShortcuts(shortcutGroup.items))
            }
        }

    private fun toShortcuts(infoList: List<KeyboardShortcutInfo>) =
        infoList.mapNotNull { toShortcut(it) }

    private fun toShortcut(shortcutInfo: KeyboardShortcutInfo): Shortcut? {
        val shortcutCommand = toShortcutCommand(shortcutInfo)
        return if (shortcutCommand == null) null
        else Shortcut(label = shortcutInfo.label!!.toString(), commands = listOf(shortcutCommand))
    }

    private fun toShortcutCommand(info: KeyboardShortcutInfo): ShortcutCommand? {
        val keyCodes = mutableListOf<Int>()
        var remainingModifiers = info.modifiers
        SUPPORTED_MODIFIERS.forEach { supportedModifier ->
            if ((supportedModifier and remainingModifiers) != 0) {
                keyCodes += supportedModifier
                // "Remove" the modifier from the remaining modifiers
                remainingModifiers = remainingModifiers and supportedModifier.inv()
            }
        }
        if (remainingModifiers != 0) {
            // There is a remaining modifier we don't support
            return null
        }
        keyCodes += info.keycode
        return ShortcutCommand(keyCodes)
    }

    companion object {
        private val SUPPORTED_MODIFIERS =
            listOf(
                KeyEvent.META_META_ON,
                KeyEvent.META_CTRL_ON,
                KeyEvent.META_ALT_ON,
                KeyEvent.META_SHIFT_ON,
                KeyEvent.META_SYM_ON,
                KeyEvent.META_FUNCTION_ON
            )
    }
}
}
+38 −12
Original line number Original line Diff line number Diff line
@@ -18,30 +18,56 @@ package com.android.systemui.keyboard.shortcut.domain.interactor


import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyboard.shortcut.data.repository.ShortcutHelperCategoriesRepository
import com.android.systemui.keyboard.shortcut.data.repository.ShortcutHelperCategoriesRepository
import com.android.systemui.keyboard.shortcut.data.repository.ShortcutHelperStateRepository
import com.android.systemui.keyboard.shortcut.shared.model.Shortcut
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutHelperState
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutSubCategory
import javax.inject.Inject
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.map


@SysUISingleton
@SysUISingleton
class ShortcutHelperCategoriesInteractor
class ShortcutHelperCategoriesInteractor
@Inject
@Inject
constructor(
constructor(
    stateRepository: ShortcutHelperStateRepository,
    categoriesRepository: ShortcutHelperCategoriesRepository,
    categoriesRepository: ShortcutHelperCategoriesRepository,
) {
) {


    private val systemsShortcutCategory = categoriesRepository.systemShortcutsCategory
    private val multitaskingShortcutsCategory = categoriesRepository.multitaskingShortcutsCategory
    private val imeShortcutsCategory =
        categoriesRepository.imeShortcutsCategory.map { groupSubCategoriesInCategory(it) }

    val shortcutCategories: Flow<List<ShortcutCategory>> =
    val shortcutCategories: Flow<List<ShortcutCategory>> =
        stateRepository.state.map { state ->
        combine(systemsShortcutCategory, multitaskingShortcutsCategory, imeShortcutsCategory) {
            when (state) {
            shortcutCategories ->
                is ShortcutHelperState.Active ->
            shortcutCategories.filterNotNull()
                    listOf(
        }
                        categoriesRepository.systemShortcutsCategory(),

                        categoriesRepository.multitaskingShortcutsCategory()
    private fun groupSubCategoriesInCategory(
        shortcutCategory: ShortcutCategory?
    ): ShortcutCategory? {
        if (shortcutCategory == null) {
            return null
        }
        val subCategoriesWithGroupedShortcuts =
            shortcutCategory.subCategories.map {
                ShortcutSubCategory(
                    label = it.label,
                    shortcuts = groupShortcutsInSubcategory(it.shortcuts)
                )
                )
                is ShortcutHelperState.Inactive -> emptyList()
            }
            }
        return ShortcutCategory(
            type = shortcutCategory.type,
            subCategories = subCategoriesWithGroupedShortcuts
        )
    }

    private fun groupShortcutsInSubcategory(shortcuts: List<Shortcut>) =
        shortcuts
            .groupBy { it.label }
            .entries
            .map { (commonLabel, groupedShortcuts) ->
                Shortcut(label = commonLabel, commands = groupedShortcuts.flatMap { it.commands })
            }
            }
}
}
+2 −1
Original line number Original line Diff line number Diff line
@@ -26,6 +26,7 @@ import com.android.systemui.shared.system.QuickStepContract
import javax.inject.Inject
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.launch


@SysUISingleton
@SysUISingleton
@@ -38,7 +39,7 @@ constructor(
    private val repository: ShortcutHelperStateRepository
    private val repository: ShortcutHelperStateRepository
) {
) {


    val state: Flow<ShortcutHelperState> = repository.state
    val state: Flow<ShortcutHelperState> = repository.state.asStateFlow()


    fun onViewClosed() {
    fun onViewClosed() {
        repository.hide()
        repository.hide()
+1 −0
Original line number Original line Diff line number Diff line
@@ -19,6 +19,7 @@ package com.android.systemui.keyboard.shortcut.shared.model
enum class ShortcutCategoryType {
enum class ShortcutCategoryType {
    SYSTEM,
    SYSTEM,
    MULTI_TASKING,
    MULTI_TASKING,
    IME
}
}


data class ShortcutCategory(
data class ShortcutCategory(
+130 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2024 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.systemui.keyboard.shortcut.data.repository

import android.view.KeyEvent
import android.view.KeyboardShortcutGroup
import android.view.KeyboardShortcutInfo
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyboard.shortcut.shared.model.Shortcut
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCommand
import com.android.systemui.keyboard.shortcut.shared.model.shortcutCategory
import com.android.systemui.keyboard.shortcut.shortcutHelperCategoriesRepository
import com.android.systemui.keyboard.shortcut.shortcutHelperTestHelper
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith

@SmallTest
@RunWith(AndroidJUnit4::class)
class ShortcutHelperCategoriesRepositoryTest : SysuiTestCase() {
    @OptIn(ExperimentalCoroutinesApi::class)
    private val kosmos = testKosmos().also { it.testDispatcher = UnconfinedTestDispatcher() }
    private val repo = kosmos.shortcutHelperCategoriesRepository
    private val helper = kosmos.shortcutHelperTestHelper
    private val testScope = kosmos.testScope

    @Test
    fun stateActive_imeShortcuts_shortcutInfoCorrectlyConverted() =
        testScope.runTest {
            helper.setImeShortcuts(imeShortcutsGroupWithPreviousLanguageSwitchShortcut)
            val imeShortcutCategory by collectLastValue(repo.imeShortcutsCategory)

            helper.showFromActivity()

            assertThat(imeShortcutCategory)
                .isEqualTo(expectedImeShortcutCategoryWithPreviousLanguageSwitchShortcut)
        }

    @Test
    fun stateActive_imeShortcuts_discardUnsupportedShortcutInfoModifiers() =
        testScope.runTest {
            helper.setImeShortcuts(imeShortcutsGroupWithUnsupportedShortcutModifiers)
            val imeShortcutCategory by collectLastValue(repo.imeShortcutsCategory)

            helper.showFromActivity()

            assertThat(imeShortcutCategory)
                .isEqualTo(expectedImeShortcutCategoryWithDiscardedUnsupportedShortcuts)
        }

    private val switchToPreviousLanguageCommand =
        ShortcutCommand(
            listOf(KeyEvent.META_CTRL_ON, KeyEvent.META_SHIFT_ON, KeyEvent.KEYCODE_SPACE)
        )

    private val expectedImeShortcutCategoryWithDiscardedUnsupportedShortcuts =
        shortcutCategory(ShortcutCategoryType.IME) { subCategory("input", emptyList()) }

    private val switchToPreviousLanguageKeyboardShortcutInfo =
        KeyboardShortcutInfo(
            /* label = */ "switch to previous language",
            /* keycode = */ switchToPreviousLanguageCommand.keyCodes[2],
            /* modifiers = */ switchToPreviousLanguageCommand.keyCodes[0] or
                switchToPreviousLanguageCommand.keyCodes[1],
        )

    private val expectedImeShortcutCategoryWithPreviousLanguageSwitchShortcut =
        shortcutCategory(ShortcutCategoryType.IME) {
            subCategory(
                "input",
                listOf(
                    Shortcut(
                        switchToPreviousLanguageKeyboardShortcutInfo.label!!.toString(),
                        listOf(switchToPreviousLanguageCommand)
                    )
                )
            )
        }

    private val imeShortcutsGroupWithPreviousLanguageSwitchShortcut =
        listOf(
            KeyboardShortcutGroup(
                "input",
                listOf(
                    switchToPreviousLanguageKeyboardShortcutInfo,
                )
            )
        )

    private val shortcutInfoWithUnsupportedModifier =
        KeyboardShortcutInfo(
            /* label = */ "unsupported shortcut",
            /* keycode = */ KeyEvent.KEYCODE_SPACE,
            /* modifiers = */ 32
        )

    private val imeShortcutsGroupWithUnsupportedShortcutModifiers =
        listOf(
            KeyboardShortcutGroup(
                "input",
                listOf(
                    shortcutInfoWithUnsupportedModifier,
                )
            )
        )
}
Loading