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

Commit 5eae77ad authored by Darrell Shi's avatar Darrell Shi Committed by Android (Google) Code Review
Browse files

Merge "Skip populating default widgets after a restore" into main

parents 0474c8ae 9ff5cd76
Loading
Loading
Loading
Loading
+157 −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.communal.data.db

import android.content.ComponentName
import android.os.UserHandle
import android.os.UserManager
import androidx.sqlite.db.SupportSQLiteDatabase
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.data.db.DefaultWidgetPopulation.SkipReason.RESTORED_FROM_BACKUP
import com.android.systemui.communal.widgets.CommunalWidgetHost
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testScope
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.testKosmos
import kotlinx.coroutines.ExperimentalCoroutinesApi
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.ArgumentMatchers.anyInt
import org.mockito.kotlin.any
import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.verify

@SmallTest
@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidJUnit4::class)
class DefaultWidgetPopulationTest : SysuiTestCase() {
    private val kosmos = testKosmos()
    private val testScope = kosmos.testScope

    private val communalWidgetHost =
        mock<CommunalWidgetHost> {
            var nextId = 0
            on { allocateIdAndBindWidget(any(), anyOrNull()) }.thenAnswer { nextId++ }
        }
    private val communalWidgetDao = mock<CommunalWidgetDao>()
    private val database = mock<SupportSQLiteDatabase>()
    private val mainUser = UserHandle(0)
    private val userManager =
        mock<UserManager> {
            on { mainUser }.thenReturn(mainUser)
            on { getUserSerialNumber(0) }.thenReturn(0)
        }

    private val defaultWidgets =
        arrayOf(
            "com.android.test_package_1/fake_widget_1",
            "com.android.test_package_2/fake_widget_2",
            "com.android.test_package_3/fake_widget_3",
        )

    private lateinit var underTest: DefaultWidgetPopulation

    @Before
    fun setUp() {
        underTest =
            DefaultWidgetPopulation(
                bgScope = kosmos.applicationCoroutineScope,
                communalWidgetHost = communalWidgetHost,
                communalWidgetDaoProvider = { communalWidgetDao },
                defaultWidgets = defaultWidgets,
                logBuffer = logcatLogBuffer("DefaultWidgetPopulationTest"),
                userManager = userManager,
            )
    }

    @Test
    fun testPopulateDefaultWidgetsWhenDatabaseCreated() =
        testScope.runTest {
            // Database created
            underTest.onCreate(database)
            runCurrent()

            // Verify default widgets bound
            verify(communalWidgetHost)
                .allocateIdAndBindWidget(
                    provider = eq(ComponentName.unflattenFromString(defaultWidgets[0])!!),
                    user = eq(mainUser),
                )
            verify(communalWidgetHost)
                .allocateIdAndBindWidget(
                    provider = eq(ComponentName.unflattenFromString(defaultWidgets[1])!!),
                    user = eq(mainUser),
                )
            verify(communalWidgetHost)
                .allocateIdAndBindWidget(
                    provider = eq(ComponentName.unflattenFromString(defaultWidgets[2])!!),
                    user = eq(mainUser),
                )

            // Verify default widgets added in database
            verify(communalWidgetDao)
                .addWidget(
                    widgetId = 0,
                    componentName = defaultWidgets[0],
                    priority = 3,
                    userSerialNumber = 0,
                )
            verify(communalWidgetDao)
                .addWidget(
                    widgetId = 1,
                    componentName = defaultWidgets[1],
                    priority = 2,
                    userSerialNumber = 0,
                )
            verify(communalWidgetDao)
                .addWidget(
                    widgetId = 2,
                    componentName = defaultWidgets[2],
                    priority = 1,
                    userSerialNumber = 0,
                )
        }

    @Test
    fun testSkipDefaultWidgetsPopulation() =
        testScope.runTest {
            // Skip default widgets population
            underTest.skipDefaultWidgetsPopulation(RESTORED_FROM_BACKUP)

            // Database created
            underTest.onCreate(database)
            runCurrent()

            // Verify no widget bounded or added to the database
            verify(communalWidgetHost, never()).allocateIdAndBindWidget(any(), any())
            verify(communalWidgetDao, never())
                .addWidget(
                    widgetId = anyInt(),
                    componentName = any(),
                    priority = anyInt(),
                    userSerialNumber = anyInt(),
                )
        }
}
+2 −0
Original line number Diff line number Diff line
@@ -34,6 +34,7 @@ import com.android.systemui.communal.data.backup.CommunalBackupUtils
import com.android.systemui.communal.data.db.CommunalItemRank
import com.android.systemui.communal.data.db.CommunalWidgetDao
import com.android.systemui.communal.data.db.CommunalWidgetItem
import com.android.systemui.communal.data.db.defaultWidgetPopulation
import com.android.systemui.communal.nano.CommunalHubState
import com.android.systemui.communal.proto.toByteArray
import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
@@ -134,6 +135,7 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
                backupUtils,
                packageChangeRepository,
                userManager,
                kosmos.defaultWidgetPopulation,
            )
    }

+43 −19
Original line number Diff line number Diff line
@@ -27,7 +27,7 @@ import androidx.sqlite.db.SupportSQLiteDatabase
import com.android.systemui.communal.nano.CommunalHubState
import com.android.systemui.communal.widgets.CommunalWidgetHost
import com.android.systemui.communal.widgets.CommunalWidgetModule.Companion.DEFAULT_WIDGETS
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.Logger
@@ -35,21 +35,19 @@ import com.android.systemui.log.dagger.CommunalLog
import javax.inject.Inject
import javax.inject.Named
import javax.inject.Provider
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext

/**
 * Callback that will be invoked when the Room database is created. Then the database will be
 * populated with pre-configured default widgets to be rendered in the glanceable hub.
 */
@SysUISingleton
class DefaultWidgetPopulation
@Inject
constructor(
    @Application private val applicationScope: CoroutineScope,
    @Background private val bgDispatcher: CoroutineDispatcher,
    @Background private val bgScope: CoroutineScope,
    private val communalWidgetHost: CommunalWidgetHost,
    private val communalWidgetDaoProvider: Provider<CommunalWidgetDao>,
    @Named(DEFAULT_WIDGETS) private val defaultWidgets: Array<String>,
@@ -62,36 +60,43 @@ constructor(

    private val logger = Logger(logBuffer, TAG)

    /**
     * Reason for skipping default widgets population. Do not skip if this value is
     * [SkipReason.NONE].
     */
    private var skipReason = SkipReason.NONE

    override fun onCreate(db: SupportSQLiteDatabase) {
        super.onCreate(db)
        applicationScope.launch { addDefaultWidgets() }

        if (skipReason != SkipReason.NONE) {
            logger.i("Skipped populating default widgets. Reason: $skipReason")
            return
        }

    // Read default widgets from config.xml and populate the database.
    private suspend fun addDefaultWidgets() =
        withContext(bgDispatcher) {
        bgScope.launch {
            // Default widgets should be associated with the main user.
            val userSerialNumber =
                userManager.mainUser?.let { mainUser ->
                    userManager.getUserSerialNumber(mainUser.identifier)
                }
            if (userSerialNumber == null) {
            val user = userManager.mainUser

            if (user == null) {
                logger.w(
                    "Skipped populating default widgets because device does not have a main user"
                    "Skipped populating default widgets. Reason: device does not have a main user"
                )
                return@withContext
                return@launch
            }

            val userSerialNumber = userManager.getUserSerialNumber(user.identifier)

            defaultWidgets.forEachIndexed { index, name ->
                val provider = ComponentName.unflattenFromString(name)
                provider?.let {
                    val id = communalWidgetHost.allocateIdAndBindWidget(provider)
                    val id = communalWidgetHost.allocateIdAndBindWidget(provider, user)
                    id?.let {
                        communalWidgetDaoProvider
                            .get()
                            .addWidget(
                                widgetId = id,
                                provider = provider,
                                componentName = name,
                                priority = defaultWidgets.size - index,
                                userSerialNumber = userSerialNumber,
                            )
@@ -103,6 +108,25 @@ constructor(
        }
    }

    /**
     * Skip populating default widgets in the Glanceable Hub when the database is created. This has
     * no effect if default widgets have been populated already.
     *
     * @param skipReason Reason for skipping the default widgets population.
     */
    fun skipDefaultWidgetsPopulation(skipReason: SkipReason) {
        this.skipReason = skipReason
    }

    /** Reason for skipping default widgets population. */
    enum class SkipReason {
        /** Do not skip. */
        NONE,
        /** Widgets are restored from a backup. */
        RESTORED_FROM_BACKUP,
    }
}

@Dao
interface CommunalWidgetDao {
    @Query(
+6 −0
Original line number Diff line number Diff line
@@ -26,6 +26,8 @@ import com.android.systemui.common.shared.model.PackageInstallSession
import com.android.systemui.communal.data.backup.CommunalBackupUtils
import com.android.systemui.communal.data.db.CommunalWidgetDao
import com.android.systemui.communal.data.db.CommunalWidgetItem
import com.android.systemui.communal.data.db.DefaultWidgetPopulation
import com.android.systemui.communal.data.db.DefaultWidgetPopulation.SkipReason.RESTORED_FROM_BACKUP
import com.android.systemui.communal.nano.CommunalHubState
import com.android.systemui.communal.proto.toCommunalHubState
import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
@@ -101,6 +103,7 @@ constructor(
    private val backupUtils: CommunalBackupUtils,
    packageChangeRepository: PackageChangeRepository,
    private val userManager: UserManager,
    private val defaultWidgetPopulation: DefaultWidgetPopulation,
) : CommunalWidgetRepository {
    companion object {
        const val TAG = "CommunalWidgetRepository"
@@ -321,6 +324,9 @@ constructor(
                }
            val newState = CommunalHubState().apply { widgets = newWidgets.toTypedArray() }

            // Skip default widgets population
            defaultWidgetPopulation.skipDefaultWidgetsPopulation(RESTORED_FROM_BACKUP)

            // Restore database
            logger.i("Restoring communal database:\n$newState")
            communalWidgetDao.restoreCommunalHubState(newState)
+23 −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.communal.data.db

import com.android.systemui.kosmos.Kosmos
import org.mockito.Mockito.mock

val Kosmos.defaultWidgetPopulation by
    Kosmos.Fixture<DefaultWidgetPopulation> { mock(DefaultWidgetPopulation::class.java) }