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

Commit 67aaa3d8 authored by Olivier St-Onge's avatar Olivier St-Onge
Browse files

Fetch tile categories from service metadata

Flag: android.service.quicksettings.quicksettings_tile_categories
Bug: 417203376
Test: manually - seeing tile categories reflected in edit mode
Test: IconAndNameCustomRepositoryParameterizedTest
Change-Id: Idc6a7ce69e2515700020f2764b3dde8d3f259134
parent 65b1c341
Loading
Loading
Loading
Loading
+178 −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.qs.panels.data.repository

import android.content.ComponentName
import android.content.packageManager
import android.content.pm.PackageManager
import android.content.pm.ServiceInfo
import android.graphics.drawable.TestStubDrawable
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.service.quicksettings.Flags
import android.service.quicksettings.TileService
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.shared.model.Text
import com.android.systemui.kosmos.mainCoroutineContext
import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.testScope
import com.android.systemui.qs.panels.shared.model.EditTileData
import com.android.systemui.qs.pipeline.data.repository.FakeInstalledTilesComponentRepository
import com.android.systemui.qs.pipeline.data.repository.fakeInstalledTilesRepository
import com.android.systemui.qs.pipeline.data.repository.installedTilesRepository
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.shared.model.TileCategory
import com.android.systemui.settings.FakeUserTracker
import com.android.systemui.settings.fakeUserTracker
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import platform.test.runner.parameterized.ParameterizedAndroidJunit4
import platform.test.runner.parameterized.Parameters

@RunWith(ParameterizedAndroidJunit4::class)
@SmallTest
@android.platform.test.annotations.EnabledOnRavenwood
internal class IconAndNameCustomRepositoryParameterizedTest(private val testCase: TestCase) :
    SysuiTestCase() {
    private val kosmos = testKosmos()

    private val packageManager: PackageManager = kosmos.packageManager
    private val userTracker: FakeUserTracker =
        kosmos.fakeUserTracker.apply {
            whenever(userContext.packageManager).thenReturn(packageManager)
        }

    private val underTest =
        with(kosmos) {
            IconAndNameCustomRepository(
                installedTilesRepository,
                userTracker,
                mainCoroutineContext,
                appIconRepositoryFactory,
            )
        }

    @Before
    fun setUp() {
        kosmos.fakeInstalledTilesRepository.setInstalledServicesForUser(
            userTracker.userId,
            listOf(testCase.toServiceInfo()),
        )
    }

    @Test
    @EnableFlags(Flags.FLAG_QUICKSETTINGS_TILE_CATEGORIES)
    fun tileService_categoriesEnabled_returnsValidCategory() =
        with(kosmos) {
            testScope.runTest {
                val editTileDataList = underTest.getCustomTileData()
                val expectedData1 =
                    EditTileData(
                        TileSpec.create(component1),
                        Icon.Loaded(drawable1, ContentDescription.Loaded(tileService1)),
                        Text.Loaded(tileService1),
                        Text.Loaded(appName1),
                        null,
                        testCase.expected,
                    )

                assertThat(editTileDataList).containsExactly(expectedData1)
            }
        }

    @Test
    @DisableFlags(Flags.FLAG_QUICKSETTINGS_TILE_CATEGORIES)
    fun tileService_categoriesDisabled_returnsValidCategory() =
        with(kosmos) {
            testScope.runTest {
                val editTileDataList = underTest.getCustomTileData()
                val expectedData1 =
                    EditTileData(
                        TileSpec.create(component1),
                        Icon.Loaded(drawable1, ContentDescription.Loaded(tileService1)),
                        Text.Loaded(tileService1),
                        Text.Loaded(appName1),
                        null,
                        testCase.expectedDefault,
                    )

                assertThat(editTileDataList).containsExactly(expectedData1)
            }
        }

    internal data class TestCase(
        val category: String,
        val isSystemApp: Boolean,
        val expected: TileCategory,
        val expectedDefault: TileCategory =
            if (isSystemApp) TileCategory.PROVIDED_BY_SYSTEM_APP else TileCategory.PROVIDED_BY_APP,
    ) {
        fun toServiceInfo(): ServiceInfo {
            return FakeInstalledTilesComponentRepository.ServiceInfo(
                component1,
                tileService1,
                drawable1,
                appName1,
                null,
                isSystemApp,
                category,
            )
        }

        override fun toString(): String =
            "category=$category," +
                "isSystemApp=$isSystemApp," +
                "expected=${expected.name}," +
                "expectedDefault=${expectedDefault.name}"
    }

    companion object {
        val drawable1 = TestStubDrawable("drawable1")
        val appName1 = "App1"
        val tileService1 = "Tile Service 1"
        val component1 = ComponentName("pkg1", "srv1")

        @Parameters(name = "{0}")
        @JvmStatic
        fun data() =
            listOf(
                TestCase(TileService.CATEGORY_CONNECTIVITY, true, TileCategory.CONNECTIVITY),
                TestCase(TileService.CATEGORY_DISPLAY, false, TileCategory.DISPLAY),
                TestCase(TileService.CATEGORY_UTILITIES, true, TileCategory.UTILITIES),
                TestCase(TileService.CATEGORY_PRIVACY, false, TileCategory.PRIVACY),
                TestCase(TileService.CATEGORY_ACCESSIBILITY, true, TileCategory.ACCESSIBILITY),
                TestCase(
                    "android.service.quicksettings.CATEGORY_NON_VALID",
                    true,
                    TileCategory.PROVIDED_BY_SYSTEM_APP,
                ),
                TestCase(
                    "android.service.quicksettings.CATEGORY_NON_VALID",
                    false,
                    TileCategory.PROVIDED_BY_APP,
                ),
            )
    }
}
+1 −0
Original line number Original line Diff line number Diff line
@@ -28,6 +28,7 @@ import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.shared.model.Text
import com.android.systemui.common.shared.model.Text
import com.android.systemui.kosmos.mainCoroutineContext
import com.android.systemui.kosmos.mainCoroutineContext
import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.testScope
import com.android.systemui.kosmos.testScope
import com.android.systemui.qs.panels.shared.model.EditTileData
import com.android.systemui.qs.panels.shared.model.EditTileData
import com.android.systemui.qs.pipeline.data.repository.FakeInstalledTilesComponentRepository
import com.android.systemui.qs.pipeline.data.repository.FakeInstalledTilesComponentRepository
+12 −10
Original line number Original line Diff line number Diff line
@@ -84,7 +84,7 @@ class InstalledTilesComponentRepositoryImplTest : SysuiTestCase() {
            InstalledTilesComponentRepositoryImpl(
            InstalledTilesComponentRepositoryImpl(
                context,
                context,
                testScope.backgroundScope,
                testScope.backgroundScope,
                kosmos.packageChangeRepository
                kosmos.packageChangeRepository,
            )
            )
    }
    }


@@ -98,7 +98,7 @@ class InstalledTilesComponentRepositoryImplTest : SysuiTestCase() {
                    packageManager.queryIntentServicesAsUser(
                    packageManager.queryIntentServicesAsUser(
                        matchIntent(),
                        matchIntent(),
                        matchFlags(),
                        matchFlags(),
                        eq(userId)
                        eq(userId),
                    )
                    )
                )
                )
                .thenReturn(listOf(resolveInfo))
                .thenReturn(listOf(resolveInfo))
@@ -127,7 +127,7 @@ class InstalledTilesComponentRepositoryImplTest : SysuiTestCase() {
                    packageManager.queryIntentServicesAsUser(
                    packageManager.queryIntentServicesAsUser(
                        matchIntent(),
                        matchIntent(),
                        matchFlags(),
                        matchFlags(),
                        eq(userId)
                        eq(userId),
                    )
                    )
                )
                )
                .thenReturn(listOf(resolveInfo))
                .thenReturn(listOf(resolveInfo))
@@ -149,7 +149,7 @@ class InstalledTilesComponentRepositoryImplTest : SysuiTestCase() {
                    packageManager.queryIntentServicesAsUser(
                    packageManager.queryIntentServicesAsUser(
                        matchIntent(),
                        matchIntent(),
                        matchFlags(),
                        matchFlags(),
                        eq(userId)
                        eq(userId),
                    )
                    )
                )
                )
                .thenReturn(listOf(resolveInfo))
                .thenReturn(listOf(resolveInfo))
@@ -172,7 +172,7 @@ class InstalledTilesComponentRepositoryImplTest : SysuiTestCase() {
                    packageManager.queryIntentServicesAsUser(
                    packageManager.queryIntentServicesAsUser(
                        matchIntent(),
                        matchIntent(),
                        matchFlags(),
                        matchFlags(),
                        eq(userId)
                        eq(userId),
                    )
                    )
                )
                )
                .thenReturn(listOf(resolveInfo))
                .thenReturn(listOf(resolveInfo))
@@ -213,7 +213,7 @@ class InstalledTilesComponentRepositoryImplTest : SysuiTestCase() {
                    secondaryUserPackageManager.queryIntentServicesAsUser(
                    secondaryUserPackageManager.queryIntentServicesAsUser(
                        matchIntent(),
                        matchIntent(),
                        matchFlags(),
                        matchFlags(),
                        eq(userId)
                        eq(userId),
                    )
                    )
                )
                )
                .thenReturn(listOf(resolveInfo))
                .thenReturn(listOf(resolveInfo))
@@ -221,7 +221,7 @@ class InstalledTilesComponentRepositoryImplTest : SysuiTestCase() {
                    packageManager.queryIntentServicesAsUser(
                    packageManager.queryIntentServicesAsUser(
                        matchIntent(),
                        matchIntent(),
                        matchFlags(),
                        matchFlags(),
                        eq(userId)
                        eq(userId),
                    )
                    )
                )
                )
                .thenReturn(listOf(resolveInfo))
                .thenReturn(listOf(resolveInfo))
@@ -259,7 +259,7 @@ class InstalledTilesComponentRepositoryImplTest : SysuiTestCase() {
                    packageManager.queryIntentServicesAsUser(
                    packageManager.queryIntentServicesAsUser(
                        matchIntent(),
                        matchIntent(),
                        matchFlags(),
                        matchFlags(),
                        eq(userId)
                        eq(userId),
                    )
                    )
                )
                )
                .thenReturn(listOf(resolveInfo))
                .thenReturn(listOf(resolveInfo))
@@ -277,7 +277,8 @@ class InstalledTilesComponentRepositoryImplTest : SysuiTestCase() {
            ResolveInfoFlags.of(
            ResolveInfoFlags.of(
                (PackageManager.MATCH_DIRECT_BOOT_AWARE or
                (PackageManager.MATCH_DIRECT_BOOT_AWARE or
                        PackageManager.MATCH_DIRECT_BOOT_UNAWARE or
                        PackageManager.MATCH_DIRECT_BOOT_UNAWARE or
                        PackageManager.GET_SERVICES)
                        PackageManager.GET_SERVICES or
                        PackageManager.GET_META_DATA)
                    .toLong()
                    .toLong()
            )
            )
        private val PERMISSION = BIND_QUICK_SETTINGS_TILE
        private val PERMISSION = BIND_QUICK_SETTINGS_TILE
@@ -286,12 +287,13 @@ class InstalledTilesComponentRepositoryImplTest : SysuiTestCase() {


        private fun matchFlags() =
        private fun matchFlags() =
            argThat<ResolveInfoFlags> { flags -> flags?.value == FLAGS.value }
            argThat<ResolveInfoFlags> { flags -> flags?.value == FLAGS.value }

        private fun matchIntent() = argThat<Intent> { intent -> intent.action == INTENT.action }
        private fun matchIntent() = argThat<Intent> { intent -> intent.action == INTENT.action }


        private fun ResolveInfo(
        private fun ResolveInfo(
            componentName: ComponentName,
            componentName: ComponentName,
            hasPermission: Boolean,
            hasPermission: Boolean,
            defaultEnabled: Boolean
            defaultEnabled: Boolean,
        ): ResolveInfo {
        ): ResolveInfo {
            val applicationInfo = ApplicationInfo().apply { enabled = true }
            val applicationInfo = ApplicationInfo().apply { enabled = true }
            val serviceInfo =
            val serviceInfo =
+22 −5
Original line number Original line Diff line number Diff line
@@ -16,6 +16,9 @@


package com.android.systemui.qs.panels.data.repository
package com.android.systemui.qs.panels.data.repository


import android.os.Bundle
import android.service.quicksettings.Flags
import android.service.quicksettings.TileService
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.shared.model.Text
import com.android.systemui.common.shared.model.Text
@@ -25,6 +28,7 @@ import com.android.systemui.qs.panels.shared.model.EditTileData
import com.android.systemui.qs.pipeline.data.repository.InstalledTilesComponentRepository
import com.android.systemui.qs.pipeline.data.repository.InstalledTilesComponentRepository
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.shared.model.TileCategory
import com.android.systemui.qs.shared.model.TileCategory
import com.android.systemui.qs.shared.model.tileCategoryFor
import com.android.systemui.settings.UserTracker
import com.android.systemui.settings.UserTracker
import javax.inject.Inject
import javax.inject.Inject
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.CoroutineContext
@@ -57,11 +61,8 @@ constructor(
                    val icon = it.loadIcon(packageManager)
                    val icon = it.loadIcon(packageManager)
                    val appName = it.applicationInfo.loadLabel(packageManager)
                    val appName = it.applicationInfo.loadLabel(packageManager)
                    val category =
                    val category =
                        if (it.applicationInfo.isSystemApp) {
                        it.metaData?.getTileCategory()
                            TileCategory.PROVIDED_BY_SYSTEM_APP
                            ?: defaultCategory(it.applicationInfo.isSystemApp)
                        } else {
                            TileCategory.PROVIDED_BY_APP
                        }
                    val appIcon =
                    val appIcon =
                        if (it.applicationInfo.isSystemApp) {
                        if (it.applicationInfo.isSystemApp) {
                            null
                            null
@@ -84,4 +85,20 @@ constructor(
                .filterNotNull()
                .filterNotNull()
        }
        }
    }
    }

    private fun Bundle.getTileCategory(): TileCategory? {
        return if (Flags.quicksettingsTileCategories()) {
            getString(TileService.META_DATA_TILE_CATEGORY)?.let { tileCategoryFor(it) }
        } else {
            null
        }
    }

    private fun defaultCategory(isSystemApp: Boolean): TileCategory {
        return if (isSystemApp) {
            TileCategory.PROVIDED_BY_SYSTEM_APP
        } else {
            TileCategory.PROVIDED_BY_APP
        }
    }
}
}
+4 −8
Original line number Original line Diff line number Diff line
@@ -56,7 +56,7 @@ class InstalledTilesComponentRepositoryImpl
constructor(
constructor(
    @ShadeDisplayAware private val context: Context,
    @ShadeDisplayAware private val context: Context,
    @Background private val backgroundScope: CoroutineScope,
    @Background private val backgroundScope: CoroutineScope,
    private val packageChangeRepository: PackageChangeRepository
    private val packageChangeRepository: PackageChangeRepository,
) : InstalledTilesComponentRepository {
) : InstalledTilesComponentRepository {


    @GuardedBy("userMap") private val userMap = mutableMapOf<Int, StateFlow<List<ServiceInfo>>>()
    @GuardedBy("userMap") private val userMap = mutableMapOf<Int, StateFlow<List<ServiceInfo>>>()
@@ -80,12 +80,7 @@ constructor(
                if (context.userId == userId) {
                if (context.userId == userId) {
                    context.packageManager
                    context.packageManager
                } else {
                } else {
                    context
                    context.createContextAsUser(UserHandle.of(userId), /* flags */ 0).packageManager
                        .createContextAsUser(
                            UserHandle.of(userId),
                            /* flags */ 0,
                        )
                        .packageManager
                }
                }
            packageChangeRepository
            packageChangeRepository
                .packageChanged(UserHandle.of(userId))
                .packageChanged(UserHandle.of(userId))
@@ -119,7 +114,8 @@ constructor(
            ResolveInfoFlags.of(
            ResolveInfoFlags.of(
                (PackageManager.GET_SERVICES or
                (PackageManager.GET_SERVICES or
                        PackageManager.MATCH_DIRECT_BOOT_AWARE or
                        PackageManager.MATCH_DIRECT_BOOT_AWARE or
                        PackageManager.MATCH_DIRECT_BOOT_UNAWARE)
                        PackageManager.MATCH_DIRECT_BOOT_UNAWARE or
                        PackageManager.GET_META_DATA)
                    .toLong()
                    .toLong()
            )
            )
    }
    }
Loading