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

Commit 77b2e9ab authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Shortcut Helper - Only start Activity after shortcuts have been fetched" into main

parents 4921308e d4bdf695
Loading
Loading
Loading
Loading
+73 −77
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import android.content.Context
import android.graphics.drawable.Icon
import android.hardware.input.InputManager
import android.util.Log
import android.view.InputDevice
import android.view.KeyCharacterMap
import android.view.KeyEvent
import android.view.KeyboardShortcutGroup
@@ -47,8 +48,11 @@ import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutSubCategory
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.withContext

@SysUISingleton
@@ -56,6 +60,7 @@ class ShortcutHelperCategoriesRepository
@Inject
constructor(
    private val context: Context,
    @Background private val backgroundScope: CoroutineScope,
    @Background private val backgroundDispatcher: CoroutineDispatcher,
    @SystemShortcuts private val systemShortcutsSource: KeyboardShortcutGroupsSource,
    @MultitaskingShortcuts private val multitaskingShortcutsSource: KeyboardShortcutGroupsSource,
@@ -75,79 +80,80 @@ constructor(
            }
        }

    val systemShortcutsCategory =
        activeInputDevice.map {
            if (it != null) {
    val categories: Flow<List<ShortcutCategory>> =
        activeInputDevice
            .map {
                if (it == null) {
                    return@map emptyList()
                }
                return@map listOfNotNull(
                    fetchSystemShortcuts(it),
                    fetchMultiTaskingShortcuts(it),
                    fetchAppCategoriesShortcuts(it),
                    fetchImeShortcuts(it),
                    fetchCurrentAppShortcuts(it),
                )
            }
            .stateIn(
                scope = backgroundScope,
                started = SharingStarted.Lazily,
                initialValue = emptyList(),
            )

    private suspend fun fetchSystemShortcuts(inputDevice: InputDevice) =
        toShortcutCategory(
                    it.keyCharacterMap,
            inputDevice.keyCharacterMap,
            System,
                    systemShortcutsSource.shortcutGroups(it.id),
            systemShortcutsSource.shortcutGroups(inputDevice.id),
            keepIcons = true,
        )
            } else {
                null
            }
        }

    val multitaskingShortcutsCategory =
        activeInputDevice.map {
            if (it != null) {
    private suspend fun fetchMultiTaskingShortcuts(inputDevice: InputDevice) =
        toShortcutCategory(
                    it.keyCharacterMap,
            inputDevice.keyCharacterMap,
            MultiTasking,
                    multitaskingShortcutsSource.shortcutGroups(it.id),
            multitaskingShortcutsSource.shortcutGroups(inputDevice.id),
            keepIcons = true,
        )
            } else {
                null
            }
        }

    val appCategoriesShortcutsCategory =
        activeInputDevice.map {
            if (it != null) {
    private suspend fun fetchAppCategoriesShortcuts(inputDevice: InputDevice) =
        toShortcutCategory(
                    it.keyCharacterMap,
            inputDevice.keyCharacterMap,
            AppCategories,
                    appCategoriesShortcutsSource.shortcutGroups(it.id),
            appCategoriesShortcutsSource.shortcutGroups(inputDevice.id),
            keepIcons = true,
        )
            } else {
                null
            }
        }

    val imeShortcutsCategory =
        activeInputDevice.map {
            if (it != null) {
    private suspend fun fetchImeShortcuts(inputDevice: InputDevice) =
        toShortcutCategory(
                    it.keyCharacterMap,
            inputDevice.keyCharacterMap,
            InputMethodEditor,
                    inputShortcutsSource.shortcutGroups(it.id),
            inputShortcutsSource.shortcutGroups(inputDevice.id),
            keepIcons = false,
        )
            } else {
                null
            }
        }

    val currentAppShortcutsCategory: Flow<ShortcutCategory?> =
        activeInputDevice.map {
            if (it != null) {
                val shortcutGroups = currentAppShortcutsSource.shortcutGroups(it.id)
    private suspend fun fetchCurrentAppShortcuts(inputDevice: InputDevice): ShortcutCategory? {
        val shortcutGroups = currentAppShortcutsSource.shortcutGroups(inputDevice.id)
        val categoryType = getCurrentAppShortcutCategoryType(shortcutGroups)
                if (categoryType == null) {
        return if (categoryType == null) {
            null
        } else {
            toShortcutCategory(
                        it.keyCharacterMap,
                inputDevice.keyCharacterMap,
                categoryType,
                shortcutGroups,
                keepIcons = false
            )
        }
            } else {
    }

    private fun getCurrentAppShortcutCategoryType(
        shortcutGroups: List<KeyboardShortcutGroup>
    ): ShortcutCategoryType? {
        return if (shortcutGroups.isEmpty()) {
            null
        } else {
            CurrentApp(packageName = shortcutGroups[0].packageName.toString())
        }
    }

@@ -174,16 +180,6 @@ constructor(
        }
    }

    private fun getCurrentAppShortcutCategoryType(
        shortcutGroups: List<KeyboardShortcutGroup>
    ): ShortcutCategoryType? {
        return if (shortcutGroups.isEmpty()) {
            null
        } else {
            CurrentApp(packageName = shortcutGroups[0].packageName.toString())
        }
    }

    private fun toShortcuts(
        keyCharacterMap: KeyCharacterMap,
        infoList: List<KeyboardShortcutInfo>,
+3 −9
Original line number Diff line number Diff line
@@ -23,7 +23,7 @@ import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutSubCategory
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map

@SysUISingleton
class ShortcutHelperCategoriesInteractor
@@ -33,14 +33,8 @@ constructor(
) {

    val shortcutCategories: Flow<List<ShortcutCategory>> =
        combine(
            categoriesRepository.systemShortcutsCategory,
            categoriesRepository.multitaskingShortcutsCategory,
            categoriesRepository.imeShortcutsCategory,
            categoriesRepository.appCategoriesShortcutsCategory,
            categoriesRepository.currentAppShortcutsCategory
        ) { shortcutCategories ->
            shortcutCategories.filterNotNull().map { groupSubCategoriesInCategory(it) }
        categoriesRepository.categories.map { categories ->
            categories.map { category -> groupSubCategoriesInCategory(category) }
        }

    private fun groupSubCategoriesInCategory(shortcutCategory: ShortcutCategory): ShortcutCategory {
+2 −3
Original line number Diff line number Diff line
@@ -19,7 +19,6 @@ package com.android.systemui.keyboard.shortcut.ui.viewmodel
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.keyboard.shortcut.domain.interactor.ShortcutHelperCategoriesInteractor
import com.android.systemui.keyboard.shortcut.domain.interactor.ShortcutHelperStateInteractor
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutHelperState
import com.android.systemui.keyboard.shortcut.ui.model.ShortcutsUiState
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
@@ -40,8 +39,8 @@ constructor(
) {

    val shouldShow =
        stateInteractor.state
            .map { it is ShortcutHelperState.Active }
        categoriesInteractor.shortcutCategories
            .map { it.isNotEmpty() }
            .distinctUntilChanged()
            .flowOn(backgroundDispatcher)

+90 −0
Original line number 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 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.data.source.FakeKeyboardShortcutGroupsSource
import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts
import com.android.systemui.keyboard.shortcut.shortcutHelperAppCategoriesShortcutsSource
import com.android.systemui.keyboard.shortcut.shortcutHelperCategoriesRepository
import com.android.systemui.keyboard.shortcut.shortcutHelperCurrentAppShortcutsSource
import com.android.systemui.keyboard.shortcut.shortcutHelperInputShortcutsSource
import com.android.systemui.keyboard.shortcut.shortcutHelperMultiTaskingShortcutsSource
import com.android.systemui.keyboard.shortcut.shortcutHelperSystemShortcutsSource
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.Before
import org.junit.Test
import org.junit.runner.RunWith

@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class ShortcutHelperCategoriesRepositoryTest : SysuiTestCase() {

    private val fakeSystemSource = FakeKeyboardShortcutGroupsSource()
    private val fakeMultiTaskingSource = FakeKeyboardShortcutGroupsSource()

    private val kosmos =
        testKosmos().also {
            it.testDispatcher = UnconfinedTestDispatcher()
            it.shortcutHelperSystemShortcutsSource = fakeSystemSource
            it.shortcutHelperMultiTaskingShortcutsSource = fakeMultiTaskingSource
            it.shortcutHelperAppCategoriesShortcutsSource = FakeKeyboardShortcutGroupsSource()
            it.shortcutHelperInputShortcutsSource = FakeKeyboardShortcutGroupsSource()
            it.shortcutHelperCurrentAppShortcutsSource = FakeKeyboardShortcutGroupsSource()
        }

    private val repo = kosmos.shortcutHelperCategoriesRepository
    private val helper = kosmos.shortcutHelperTestHelper
    private val testScope = kosmos.testScope

    @Before
    fun setUp() {
        fakeSystemSource.setGroups(TestShortcuts.systemGroups)
        fakeMultiTaskingSource.setGroups(TestShortcuts.multitaskingGroups)
    }

    @Test
    fun categories_multipleSubscribers_replaysExistingValueToNewSubscribers() =
        testScope.runTest {
            fakeSystemSource.setGroups(TestShortcuts.systemGroups)
            fakeMultiTaskingSource.setGroups(TestShortcuts.multitaskingGroups)
            helper.showFromActivity()
            val firstCategories by collectLastValue(repo.categories)

            // Intentionally change shortcuts now. This simulates "current app" shortcuts changing
            // when our helper is shown.
            // We still want to return the shortcuts that were returned before our helper was
            // showing.
            fakeSystemSource.setGroups(emptyList())

            val secondCategories by collectLastValue(repo.categories)
            // Make sure the second subscriber receives the same value as the first subscriber, even
            // though fetching shortcuts again would have returned a new result.
            assertThat(secondCategories).isEqualTo(firstCategories)
        }
}
+3 −2
Original line number Diff line number Diff line
@@ -28,6 +28,7 @@ import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.System
import com.android.systemui.keyboard.shortcut.shortcutHelperAppCategoriesShortcutsSource
import com.android.systemui.keyboard.shortcut.shortcutHelperCategoriesInteractor
import com.android.systemui.keyboard.shortcut.shortcutHelperCurrentAppShortcutsSource
import com.android.systemui.keyboard.shortcut.shortcutHelperMultiTaskingShortcutsSource
import com.android.systemui.keyboard.shortcut.shortcutHelperSystemShortcutsSource
import com.android.systemui.keyboard.shortcut.shortcutHelperTestHelper
@@ -48,14 +49,14 @@ class ShortcutHelperCategoriesInteractorTest : SysuiTestCase() {

    private val systemShortcutsSource = FakeKeyboardShortcutGroupsSource()
    private val multitaskingShortcutsSource = FakeKeyboardShortcutGroupsSource()
    private val defaultAppsShortcutsSource = FakeKeyboardShortcutGroupsSource()
    @OptIn(ExperimentalCoroutinesApi::class)
    private val kosmos =
        testKosmos().also {
            it.testDispatcher = UnconfinedTestDispatcher()
            it.shortcutHelperSystemShortcutsSource = systemShortcutsSource
            it.shortcutHelperMultiTaskingShortcutsSource = multitaskingShortcutsSource
            it.shortcutHelperAppCategoriesShortcutsSource = defaultAppsShortcutsSource
            it.shortcutHelperAppCategoriesShortcutsSource = FakeKeyboardShortcutGroupsSource()
            it.shortcutHelperCurrentAppShortcutsSource = FakeKeyboardShortcutGroupsSource()
        }

    private val testScope = kosmos.testScope
Loading