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

Commit ba1cf687 authored by Joshua Mokut's avatar Joshua Mokut Committed by Android (Google) Code Review
Browse files

Merge "new implementation of IME shortcuts retrieval" into main

parents 9906ed4c df7b46c3
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