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 Original line Diff line number Diff line
@@ -17,11 +17,9 @@
package com.android.systemui.qs.pipeline.data.repository
package com.android.systemui.qs.pipeline.data.repository


import android.Manifest.permission.BIND_QUICK_SETTINGS_TILE
import android.Manifest.permission.BIND_QUICK_SETTINGS_TILE
import android.content.BroadcastReceiver
import android.content.ComponentName
import android.content.ComponentName
import android.content.Context
import android.content.Context
import android.content.Intent
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.ApplicationInfo
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.content.pm.PackageManager
import android.content.pm.PackageManager.ResolveInfoFlags
import android.content.pm.PackageManager.ResolveInfoFlags
@@ -33,44 +31,36 @@ import android.testing.TestableLooper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
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.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.any
import com.android.systemui.util.mockito.argThat
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.eq
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.nullable
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
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 kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Before
import org.junit.Test
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mock
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
import org.mockito.MockitoAnnotations


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


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


    private lateinit var underTest: InstalledTilesComponentRepositoryImpl
    private lateinit var underTest: InstalledTilesComponentRepositoryImpl


@@ -92,62 +82,11 @@ class InstalledTilesComponentRepositoryImplTest : SysuiTestCase() {
        underTest =
        underTest =
            InstalledTilesComponentRepositoryImpl(
            InstalledTilesComponentRepositoryImpl(
                context,
                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
    @Test
    fun componentsLoadedOnStart() =
    fun componentsLoadedOnStart() =
        testScope.runTest {
        testScope.runTest {
@@ -169,7 +108,7 @@ class InstalledTilesComponentRepositoryImplTest : SysuiTestCase() {
        }
        }


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


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


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

        return receiverCaptor.value
    }

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


/** A debug logger for [PackageChangeRepository]. */
/** A debug logger for [PackageChangeRepository]. */
+8 −0
Original line number Original line Diff line number Diff line
@@ -23,6 +23,14 @@ sealed interface PackageChangeModel {
    val packageName: String
    val packageName: String
    val packageUid: Int
    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.
     * An existing application package was uninstalled.
     *
     *
+6 −30
Original line number Original line 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.Manifest.permission.BIND_QUICK_SETTINGS_TILE
import android.annotation.WorkerThread
import android.annotation.WorkerThread
import android.content.BroadcastReceiver
import android.content.ComponentName
import android.content.ComponentName
import android.content.Context
import android.content.Context
import android.content.Intent
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.PackageManager
import android.content.pm.PackageManager
import android.content.pm.PackageManager.ResolveInfoFlags
import android.content.pm.PackageManager.ResolveInfoFlags
import android.os.UserHandle
import android.os.UserHandle
import android.service.quicksettings.TileService
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.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.util.kotlin.isComponentActuallyEnabled
import com.android.systemui.util.kotlin.isComponentActuallyEnabled
import javax.inject.Inject
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.flowOn
@@ -52,6 +50,7 @@ class InstalledTilesComponentRepositoryImpl
constructor(
constructor(
    @Application private val applicationContext: Context,
    @Application private val applicationContext: Context,
    @Background private val backgroundDispatcher: CoroutineDispatcher,
    @Background private val backgroundDispatcher: CoroutineDispatcher,
    private val packageChangeRepository: PackageChangeRepository
) : InstalledTilesComponentRepository {
) : InstalledTilesComponentRepository {


    override fun getInstalledTilesComponents(userId: Int): Flow<Set<ComponentName>> {
    override fun getInstalledTilesComponents(userId: Int): Flow<Set<ComponentName>> {
@@ -70,24 +69,9 @@ constructor(
                    )
                    )
                    .packageManager
                    .packageManager
            }
            }
        return conflatedCallbackFlow {
        return packageChangeRepository
                val receiver =
            .packageChanged(UserHandle.of(userId))
                    object : BroadcastReceiver() {
            .onStart { emit(PackageChangeModel.Empty) }
                        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) }
            .map { reloadComponents(userId, packageManager) }
            .map { reloadComponents(userId, packageManager) }
            .distinctUntilChanged()
            .distinctUntilChanged()
            .flowOn(backgroundDispatcher)
            .flowOn(backgroundDispatcher)
@@ -104,14 +88,6 @@ constructor(
    }
    }


    companion object {
    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 INTENT = Intent(TileService.ACTION_QS_TILE)
        private val FLAGS =
        private val FLAGS =
            ResolveInfoFlags.of(
            ResolveInfoFlags.of(