Loading packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/db/DefaultWidgetPopulationTest.kt 0 → 100644 +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(), ) } } packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt +2 −0 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -134,6 +135,7 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() { backupUtils, packageChangeRepository, userManager, kosmos.defaultWidgetPopulation, ) } Loading packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt +43 −19 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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>, Loading @@ -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, ) Loading @@ -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( Loading packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt +6 −0 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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" Loading Loading @@ -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) Loading packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/db/DefaultWidgetPopulationKosmos.kt 0 → 100644 +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) } Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/db/DefaultWidgetPopulationTest.kt 0 → 100644 +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(), ) } }
packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt +2 −0 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -134,6 +135,7 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() { backupUtils, packageChangeRepository, userManager, kosmos.defaultWidgetPopulation, ) } Loading
packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt +43 −19 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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>, Loading @@ -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, ) Loading @@ -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( Loading
packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt +6 −0 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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" Loading Loading @@ -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) Loading
packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/db/DefaultWidgetPopulationKosmos.kt 0 → 100644 +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) }