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

Commit f8bc5c4c authored by Lucas Silva's avatar Lucas Silva
Browse files

Update tiles component repository to remove broadcast

PackageChangeRepository was recently introduced as a centralized place
to listen to package broadcasts. This change migrates the
InstalledTilesComponentRepository to use this new logic instead of
listening to their own broadcast.

Also introduces a convenience state in PackageChangeModel to support a
sensible default value for package changes. This enables the following
pattern: `repository.packageChange(user).onStart { emit(Empty) }` to emit a starting value in the flow

Test: atest InstalledTilesComponentRepositoryImplTest
Flag: NA
Bug: 317059572
Change-Id: Ib0dddc6ea478919cbda5657f546e5d3394235e1d
parent 922a3212
Loading
Loading
Loading
Loading
+12 −86
Original line number Diff line number Diff line
@@ -17,11 +17,9 @@
package com.android.systemui.qs.pipeline.data.repository

import android.Manifest.permission.BIND_QUICK_SETTINGS_TILE
import android.content.BroadcastReceiver
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.content.pm.PackageManager.ResolveInfoFlags
@@ -33,44 +31,36 @@ import android.testing.TestableLooper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.data.repository.fakePackageChangeRepository
import com.android.systemui.common.data.repository.packageChangeRepository
import com.android.systemui.common.data.shared.model.PackageChangeModel
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argThat
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.nullable
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations

@SmallTest
@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper
@OptIn(ExperimentalCoroutinesApi::class)
class InstalledTilesComponentRepositoryImplTest : SysuiTestCase() {
    private val testDispatcher = StandardTestDispatcher()
    private val testScope = TestScope(testDispatcher)
    private val kosmos = testKosmos()
    private val testScope = kosmos.testScope

    @Mock private lateinit var context: Context
    @Mock private lateinit var packageManager: PackageManager
    @Captor private lateinit var receiverCaptor: ArgumentCaptor<BroadcastReceiver>

    private lateinit var underTest: InstalledTilesComponentRepositoryImpl

@@ -92,62 +82,11 @@ class InstalledTilesComponentRepositoryImplTest : SysuiTestCase() {
        underTest =
            InstalledTilesComponentRepositoryImpl(
                context,
                testDispatcher,
                kosmos.testDispatcher,
                kosmos.packageChangeRepository
            )
    }

    @Test
    fun registersAndUnregistersBroadcastReceiver() =
        testScope.runTest {
            val user = 10
            val job = launch { underTest.getInstalledTilesComponents(user).collect {} }
            runCurrent()

            verify(context)
                .registerReceiverAsUser(
                    capture(receiverCaptor),
                    eq(UserHandle.of(user)),
                    any(),
                    nullable(),
                    nullable(),
                )

            verify(context, never()).unregisterReceiver(receiverCaptor.value)

            job.cancel()
            runCurrent()
            verify(context).unregisterReceiver(receiverCaptor.value)
        }

    @Test
    fun intentFilterForCorrectActionsAndScheme() =
        testScope.runTest {
            val filterCaptor = argumentCaptor<IntentFilter>()

            backgroundScope.launch { underTest.getInstalledTilesComponents(0).collect {} }
            runCurrent()

            verify(context)
                .registerReceiverAsUser(
                    any(),
                    any(),
                    capture(filterCaptor),
                    nullable(),
                    nullable(),
                )

            with(filterCaptor.value) {
                assertThat(matchAction(Intent.ACTION_PACKAGE_CHANGED)).isTrue()
                assertThat(matchAction(Intent.ACTION_PACKAGE_ADDED)).isTrue()
                assertThat(matchAction(Intent.ACTION_PACKAGE_REMOVED)).isTrue()
                assertThat(matchAction(Intent.ACTION_PACKAGE_REPLACED)).isTrue()
                assertThat(countActions()).isEqualTo(4)

                assertThat(hasDataScheme("package")).isTrue()
                assertThat(countDataSchemes()).isEqualTo(1)
            }
        }

    @Test
    fun componentsLoadedOnStart() =
        testScope.runTest {
@@ -169,7 +108,7 @@ class InstalledTilesComponentRepositoryImplTest : SysuiTestCase() {
        }

    @Test
    fun componentAdded_foundAfterBroadcast() =
    fun componentAdded_foundAfterPackageChange() =
        testScope.runTest {
            val userId = 0
            val resolveInfo =
@@ -186,7 +125,7 @@ class InstalledTilesComponentRepositoryImplTest : SysuiTestCase() {
                    )
                )
                .thenReturn(listOf(resolveInfo))
            getRegisteredReceiver().onReceive(context, Intent(Intent.ACTION_PACKAGE_ADDED))
            kosmos.fakePackageChangeRepository.notifyChange(PackageChangeModel.Empty)

            assertThat(componentNames).containsExactly(TEST_COMPONENT)
        }
@@ -275,19 +214,6 @@ class InstalledTilesComponentRepositoryImplTest : SysuiTestCase() {
            assertThat(componentNames).containsExactly(TEST_COMPONENT)
        }

    private fun getRegisteredReceiver(): BroadcastReceiver {
        verify(context)
            .registerReceiverAsUser(
                capture(receiverCaptor),
                any(),
                any(),
                nullable(),
                nullable(),
            )

        return receiverCaptor.value
    }

    companion object {
        private val INTENT = Intent(TileService.ACTION_QS_TILE)
        private val FLAGS =
+1 −0
Original line number Diff line number Diff line
@@ -31,6 +31,7 @@ private fun getChangeString(model: PackageChangeModel) =
        is PackageChangeModel.UpdateStarted -> "started updating"
        is PackageChangeModel.UpdateFinished -> "finished updating"
        is PackageChangeModel.Changed -> "changed"
        is PackageChangeModel.Empty -> throw IllegalStateException("Unexpected empty value: $model")
    }

/** A debug logger for [PackageChangeRepository]. */
+8 −0
Original line number Diff line number Diff line
@@ -23,6 +23,14 @@ sealed interface PackageChangeModel {
    val packageName: String
    val packageUid: Int

    /** Empty change, provided for convenience when a sensible default value is needed. */
    data object Empty : PackageChangeModel {
        override val packageName: String
            get() = ""
        override val packageUid: Int
            get() = 0
    }

    /**
     * An existing application package was uninstalled.
     *
+6 −30
Original line number Diff line number Diff line
@@ -18,23 +18,21 @@ package com.android.systemui.qs.pipeline.data.repository

import android.Manifest.permission.BIND_QUICK_SETTINGS_TILE
import android.annotation.WorkerThread
import android.content.BroadcastReceiver
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.PackageManager
import android.content.pm.PackageManager.ResolveInfoFlags
import android.os.UserHandle
import android.service.quicksettings.TileService
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.common.data.repository.PackageChangeRepository
import com.android.systemui.common.data.shared.model.PackageChangeModel
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.util.kotlin.isComponentActuallyEnabled
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOn
@@ -52,6 +50,7 @@ class InstalledTilesComponentRepositoryImpl
constructor(
    @Application private val applicationContext: Context,
    @Background private val backgroundDispatcher: CoroutineDispatcher,
    private val packageChangeRepository: PackageChangeRepository
) : InstalledTilesComponentRepository {

    override fun getInstalledTilesComponents(userId: Int): Flow<Set<ComponentName>> {
@@ -70,24 +69,9 @@ constructor(
                    )
                    .packageManager
            }
        return conflatedCallbackFlow {
                val receiver =
                    object : BroadcastReceiver() {
                        override fun onReceive(context: Context?, intent: Intent?) {
                            trySend(Unit)
                        }
                    }
                applicationContext.registerReceiverAsUser(
                    receiver,
                    UserHandle.of(userId),
                    INTENT_FILTER,
                    /* broadcastPermission = */ null,
                    /* scheduler = */ null
                )

                awaitClose { applicationContext.unregisterReceiver(receiver) }
            }
            .onStart { emit(Unit) }
        return packageChangeRepository
            .packageChanged(UserHandle.of(userId))
            .onStart { emit(PackageChangeModel.Empty) }
            .map { reloadComponents(userId, packageManager) }
            .distinctUntilChanged()
            .flowOn(backgroundDispatcher)
@@ -104,14 +88,6 @@ constructor(
    }

    companion object {
        private val INTENT_FILTER =
            IntentFilter().apply {
                addAction(Intent.ACTION_PACKAGE_ADDED)
                addAction(Intent.ACTION_PACKAGE_CHANGED)
                addAction(Intent.ACTION_PACKAGE_REMOVED)
                addAction(Intent.ACTION_PACKAGE_REPLACED)
                addDataScheme("package")
            }
        private val INTENT = Intent(TileService.ACTION_QS_TILE)
        private val FLAGS =
            ResolveInfoFlags.of(