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

Commit b8dad2fb authored by Lucas Silva's avatar Lucas Silva Committed by Android (Google) Code Review
Browse files

Merge "Handle uninstall for selected controls package" into main

parents ac407f6c 1721f9fd
Loading
Loading
Loading
Loading
+209 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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.common.data.repository

import android.content.Context
import android.content.pm.PackageManager
import android.os.Handler
import android.os.UserHandle
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.data.shared.model.PackageChangeModel
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.log.logcatLogBuffer
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 org.mockito.Mock
import org.mockito.MockitoAnnotations

@SmallTest
@RunWith(AndroidJUnit4::class)
class PackageChangeRepositoryTest : SysuiTestCase() {

    private val kosmos = testKosmos()

    @Mock private lateinit var context: Context
    @Mock private lateinit var packageManager: PackageManager
    @Mock private lateinit var handler: Handler

    private lateinit var repository: PackageChangeRepository
    private lateinit var updateMonitor: PackageUpdateMonitor

    @Before
    fun setUp() =
        with(kosmos) {
            MockitoAnnotations.initMocks(this@PackageChangeRepositoryTest)
            whenever(context.packageManager).thenReturn(packageManager)

            repository = PackageChangeRepositoryImpl { user ->
                updateMonitor =
                    PackageUpdateMonitor(
                        user = user,
                        bgDispatcher = testDispatcher,
                        scope = applicationCoroutineScope,
                        context = context,
                        bgHandler = handler,
                        logger = PackageUpdateLogger(logcatLogBuffer())
                    )
                updateMonitor
            }
        }

    @Test
    fun packageUninstalled() =
        with(kosmos) {
            testScope.runTest {
                val packageChange by collectLastValue(repository.packageChanged(USER_100))
                assertThat(packageChange).isNull()

                updateMonitor.onPackageRemoved(
                    packageName = TEST_PACKAGE,
                    uid = UserHandle.getUid(/* userId = */ 100, /* appId = */ 10)
                )

                assertThat(packageChange).isInstanceOf(PackageChangeModel.Uninstalled::class.java)
                assertThat(packageChange?.packageName).isEqualTo(TEST_PACKAGE)
            }
        }

    @Test
    fun packageUpdateStarted() =
        with(kosmos) {
            testScope.runTest {
                val packageChange by collectLastValue(repository.packageChanged(USER_100))
                assertThat(packageChange).isNull()

                updateMonitor.onPackageUpdateStarted(
                    packageName = TEST_PACKAGE,
                    uid = UserHandle.getUid(/* userId = */ 100, /* appId = */ 10)
                )

                assertThat(packageChange).isInstanceOf(PackageChangeModel.UpdateStarted::class.java)
                assertThat(packageChange?.packageName).isEqualTo(TEST_PACKAGE)
            }
        }

    @Test
    fun packageUpdateFinished() =
        with(kosmos) {
            testScope.runTest {
                val packageChange by collectLastValue(repository.packageChanged(USER_100))
                assertThat(packageChange).isNull()

                updateMonitor.onPackageUpdateFinished(
                    packageName = TEST_PACKAGE,
                    uid = UserHandle.getUid(/* userId = */ 100, /* appId = */ 10)
                )

                assertThat(packageChange)
                    .isInstanceOf(PackageChangeModel.UpdateFinished::class.java)
                assertThat(packageChange?.packageName).isEqualTo(TEST_PACKAGE)
            }
        }

    @Test
    fun packageInstalled() =
        with(kosmos) {
            testScope.runTest {
                val packageChange by collectLastValue(repository.packageChanged(UserHandle.ALL))
                assertThat(packageChange).isNull()

                updateMonitor.onPackageAdded(
                    packageName = TEST_PACKAGE,
                    uid = UserHandle.getUid(/* userId = */ 100, /* appId = */ 10)
                )

                assertThat(packageChange).isInstanceOf(PackageChangeModel.Installed::class.java)
                assertThat(packageChange?.packageName).isEqualTo(TEST_PACKAGE)
            }
        }

    @Test
    fun packageIsChanged() =
        with(kosmos) {
            testScope.runTest {
                val packageChange by collectLastValue(repository.packageChanged(USER_100))
                assertThat(packageChange).isNull()

                updateMonitor.onPackageChanged(
                    packageName = TEST_PACKAGE,
                    uid = UserHandle.getUid(/* userId = */ 100, /* appId = */ 10),
                    components = emptyArray()
                )

                assertThat(packageChange).isInstanceOf(PackageChangeModel.Changed::class.java)
                assertThat(packageChange?.packageName).isEqualTo(TEST_PACKAGE)
            }
        }

    @Test
    fun filtersOutUpdatesFromOtherUsers() =
        with(kosmos) {
            testScope.runTest {
                val packageChange by collectLastValue(repository.packageChanged(USER_100))
                assertThat(packageChange).isNull()

                updateMonitor.onPackageUpdateFinished(
                    packageName = TEST_PACKAGE,
                    uid = UserHandle.getUid(/* userId = */ 101, /* appId = */ 10)
                )

                updateMonitor.onPackageAdded(
                    packageName = TEST_PACKAGE,
                    uid = UserHandle.getUid(/* userId = */ 99, /* appId = */ 10)
                )

                assertThat(packageChange).isNull()
            }
        }

    @Test
    fun listenToUpdatesFromAllUsers() =
        with(kosmos) {
            testScope.runTest {
                val packageChanges by collectValues(repository.packageChanged(UserHandle.ALL))
                assertThat(packageChanges).isEmpty()

                updateMonitor.onPackageUpdateFinished(
                    packageName = TEST_PACKAGE,
                    uid = UserHandle.getUid(/* userId = */ 101, /* appId = */ 10)
                )

                updateMonitor.onPackageAdded(
                    packageName = TEST_PACKAGE,
                    uid = UserHandle.getUid(/* userId = */ 99, /* appId = */ 10)
                )

                assertThat(packageChanges).hasSize(2)
            }
        }

    private companion object {
        val USER_100 = UserHandle.of(100)
        const val TEST_PACKAGE = "pkg.test"
    }
}
+210 −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.common.data.repository

import android.content.Context
import android.content.pm.PackageManager
import android.os.Handler
import android.os.UserHandle
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.data.shared.model.PackageChangeModel
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
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.Mock
import org.mockito.MockitoAnnotations

@ExperimentalCoroutinesApi
@SmallTest
@RunWith(AndroidJUnit4::class)
class PackageUpdateMonitorTest : SysuiTestCase() {
    private val kosmos = testKosmos()

    @Mock private lateinit var context: Context
    @Mock private lateinit var packageManager: PackageManager
    @Mock private lateinit var handler: Handler

    private lateinit var monitor: PackageUpdateMonitor

    @Before
    fun setUp() =
        with(kosmos) {
            MockitoAnnotations.initMocks(this@PackageUpdateMonitorTest)
            whenever(context.packageManager).thenReturn(packageManager)

            monitor =
                PackageUpdateMonitor(
                    user = USER_100,
                    bgDispatcher = testDispatcher,
                    bgHandler = handler,
                    context = context,
                    scope = applicationCoroutineScope,
                    logger = PackageUpdateLogger(logcatLogBuffer())
                )
        }

    @Test
    fun becomesActiveWhenFlowCollected() =
        with(kosmos) {
            testScope.runTest {
                assertThat(monitor.isActive).isFalse()
                val job = monitor.packageChanged.launchIn(this)
                runCurrent()
                assertThat(monitor.isActive).isTrue()
                job.cancel()
                runCurrent()
                assertThat(monitor.isActive).isFalse()
            }
        }

    @Test
    fun packageAdded() =
        with(kosmos) {
            testScope.runTest {
                val packageChange by collectLastValue(monitor.packageChanged)
                assertThat(packageChange).isNull()

                monitor.onPackageAdded(TEST_PACKAGE, 123)

                assertThat(packageChange)
                    .isEqualTo(
                        PackageChangeModel.Installed(packageName = TEST_PACKAGE, packageUid = 123)
                    )
            }
        }

    @Test
    fun packageRemoved() =
        with(kosmos) {
            testScope.runTest {
                val packageChange by collectLastValue(monitor.packageChanged)
                assertThat(packageChange).isNull()

                monitor.onPackageRemoved(TEST_PACKAGE, 123)

                assertThat(packageChange)
                    .isEqualTo(
                        PackageChangeModel.Uninstalled(packageName = TEST_PACKAGE, packageUid = 123)
                    )
            }
        }

    @Test
    fun packageChanged() =
        with(kosmos) {
            testScope.runTest {
                val packageChange by collectLastValue(monitor.packageChanged)
                assertThat(packageChange).isNull()

                monitor.onPackageChanged(TEST_PACKAGE, 123, emptyArray())

                assertThat(packageChange)
                    .isEqualTo(
                        PackageChangeModel.Changed(packageName = TEST_PACKAGE, packageUid = 123)
                    )
            }
        }

    @Test
    fun packageUpdateStarted() =
        with(kosmos) {
            testScope.runTest {
                val packageChange by collectLastValue(monitor.packageChanged)
                assertThat(packageChange).isNull()

                monitor.onPackageUpdateStarted(TEST_PACKAGE, 123)

                assertThat(packageChange)
                    .isEqualTo(
                        PackageChangeModel.UpdateStarted(
                            packageName = TEST_PACKAGE,
                            packageUid = 123
                        )
                    )
            }
        }

    @Test
    fun packageUpdateFinished() =
        with(kosmos) {
            testScope.runTest {
                val packageChange by collectLastValue(monitor.packageChanged)
                assertThat(packageChange).isNull()

                monitor.onPackageUpdateFinished(TEST_PACKAGE, 123)

                assertThat(packageChange)
                    .isEqualTo(
                        PackageChangeModel.UpdateFinished(
                            packageName = TEST_PACKAGE,
                            packageUid = 123
                        )
                    )
            }
        }

    @Test
    fun handlesBackflow() =
        with(kosmos) {
            testScope.runTest {
                val latch = MutableSharedFlow<Unit>()
                val packageChanges by collectValues(monitor.packageChanged.onEach { latch.first() })
                assertThat(packageChanges).isEmpty()

                monitor.onPackageAdded(TEST_PACKAGE, 123)
                monitor.onPackageUpdateStarted(TEST_PACKAGE, 123)
                monitor.onPackageUpdateFinished(TEST_PACKAGE, 123)

                assertThat(packageChanges).isEmpty()
                latch.emit(Unit)
                assertThat(packageChanges).hasSize(1)
                latch.emit(Unit)
                assertThat(packageChanges).hasSize(2)
                latch.emit(Unit)
                assertThat(packageChanges)
                    .containsExactly(
                        PackageChangeModel.Installed(TEST_PACKAGE, 123),
                        PackageChangeModel.UpdateStarted(TEST_PACKAGE, 123),
                        PackageChangeModel.UpdateFinished(TEST_PACKAGE, 123),
                    )
                    .inOrder()
            }
        }

    companion object {
        private val USER_100 = UserHandle.of(100)
        private const val TEST_PACKAGE = "pkg.test"
    }
}
+11 −1
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@

package com.android.systemui.common.data

import com.android.systemui.common.data.repository.PackageChangeRepository
import com.android.systemui.common.data.repository.PackageChangeRepositoryImpl
import com.android.systemui.common.ui.data.repository.ConfigurationRepository
import com.android.systemui.common.ui.data.repository.ConfigurationRepositoryImpl
import dagger.Binds
@@ -23,5 +25,13 @@ import dagger.Module

@Module
abstract class CommonDataLayerModule {
    @Binds abstract fun bindRepository(impl: ConfigurationRepositoryImpl): ConfigurationRepository
    @Binds
    abstract fun bindConfigurationRepository(
        impl: ConfigurationRepositoryImpl
    ): ConfigurationRepository

    @Binds
    abstract fun bindPackageChangeRepository(
        impl: PackageChangeRepositoryImpl
    ): PackageChangeRepository
}
+31 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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.common.data.repository

import android.os.UserHandle
import com.android.systemui.common.data.shared.model.PackageChangeModel
import kotlinx.coroutines.flow.Flow

interface PackageChangeRepository {
    /**
     * Emits values when packages for the specified user are changed. See supported modifications in
     * [PackageChangeModel]
     *
     * [UserHandle.USER_ALL] may be used to listen to all users.
     */
    fun packageChanged(user: UserHandle): Flow<PackageChangeModel>
}
+42 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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.common.data.repository

import android.os.UserHandle
import com.android.systemui.common.data.shared.model.PackageChangeModel
import com.android.systemui.dagger.SysUISingleton
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.filter

@SysUISingleton
class PackageChangeRepositoryImpl
@Inject
constructor(
    private val monitorFactory: PackageUpdateMonitor.Factory,
) : PackageChangeRepository {
    /**
     * A [PackageUpdateMonitor] which monitors package updates for all users. The per-user filtering
     * is done by [packageChanged].
     */
    private val monitor by lazy { monitorFactory.create(UserHandle.ALL) }

    override fun packageChanged(user: UserHandle): Flow<PackageChangeModel> =
        monitor.packageChanged.filter {
            user == UserHandle.ALL || user == UserHandle.getUserHandleForUid(it.packageUid)
        }
}
Loading