Loading packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt +14 −5 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.systemui.communal.ui.compose import android.content.ComponentName import android.os.UserHandle import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.runtime.toMutableStateList Loading @@ -33,9 +34,10 @@ fun rememberContentListState( return remember(communalContent) { ContentListState( communalContent, { componentName, priority -> { componentName, user, priority -> viewModel.onAddWidget( componentName, user, priority, widgetConfigurator, ) Loading @@ -54,7 +56,8 @@ fun rememberContentListState( class ContentListState internal constructor( communalContent: List<CommunalContentModel>, private val onAddWidget: (componentName: ComponentName, priority: Int) -> Unit, private val onAddWidget: (componentName: ComponentName, user: UserHandle, priority: Int) -> Unit, private val onDeleteWidget: (id: Int) -> Unit, private val onReorderWidgets: (widgetIdToPriorityMap: Map<Int, Int>) -> Unit, ) { Loading @@ -81,10 +84,16 @@ internal constructor( * * @param newItemComponentName name of the new widget that was dropped into the list; null if no * new widget was added. * @param newItemUser user profile associated with the new widget that was dropped into the * list; null if no new widget was added. * @param newItemIndex index at which the a new widget was dropped into the list; null if no new * widget was dropped. */ fun onSaveList(newItemComponentName: ComponentName? = null, newItemIndex: Int? = null) { fun onSaveList( newItemComponentName: ComponentName? = null, newItemUser: UserHandle? = null, newItemIndex: Int? = null ) { // filters placeholder, but, maintains the indices of the widgets as if the placeholder was // in the list. When persisted in DB, this leaves space for the new item (to be added) at // the correct priority. Loading @@ -100,8 +109,8 @@ internal constructor( .toMap() // reorder and then add the new widget onReorderWidgets(widgetIdToPriorityMap) if (newItemComponentName != null && newItemIndex != null) { onAddWidget(newItemComponentName, /*priority=*/ list.size - newItemIndex) if (newItemComponentName != null && newItemUser != null && newItemIndex != null) { onAddWidget(newItemComponentName, newItemUser, /*priority=*/ list.size - newItemIndex) } } Loading packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt +10 −11 Original line number Diff line number Diff line Loading @@ -17,8 +17,6 @@ package com.android.systemui.communal.ui.compose import android.content.ClipDescription import android.content.ComponentName import android.content.Intent import android.view.DragEvent import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.draganddrop.dragAndDropTarget Loading @@ -44,6 +42,8 @@ import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.dp import com.android.systemui.communal.domain.model.CommunalContentModel import com.android.systemui.communal.ui.compose.extensions.plus import com.android.systemui.communal.util.WidgetPickerIntentUtils import com.android.systemui.communal.util.WidgetPickerIntentUtils.getWidgetExtraFromIntent import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.isActive Loading Loading @@ -201,12 +201,14 @@ internal class DragAndDropTargetState( return false } return placeHolderIndex?.let { dropIndex -> val componentName = event.maybeWidgetComponentName() if (componentName != null) { val widgetExtra = event.maybeWidgetExtra() ?: return false val (componentName, user) = widgetExtra if (componentName != null && user != null) { // Placeholder isn't removed yet to allow the setting the right priority for items // before adding in the new item. contentListState.onSaveList( newItemComponentName = componentName, newItemUser = user, newItemIndex = dropIndex ) return@let true Loading Loading @@ -260,15 +262,12 @@ internal class DragAndDropTargetState( } /** * Parses and returns the component name of the widget that was dropped into the communal grid. * Parses and returns the intent extra associated with the widget that is dropped into the grid. * * Returns null if the drop event didn't include the widget information. * Returns null if the drop event didn't include intent information. */ private fun DragAndDropEvent.maybeWidgetComponentName(): ComponentName? { private fun DragAndDropEvent.maybeWidgetExtra(): WidgetPickerIntentUtils.WidgetExtra? { val clipData = this.toAndroidDragEvent().clipData.takeIf { it.itemCount != 0 } return clipData ?.getItemAt(0) ?.intent ?.getParcelableExtra(Intent.EXTRA_COMPONENT_NAME, ComponentName::class.java) return clipData?.getItemAt(0)?.intent?.let { intent -> getWidgetExtraFromIntent(intent) } } } packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt +42 −13 Original line number Diff line number Diff line Loading @@ -21,15 +21,16 @@ import android.appwidget.AppWidgetProviderInfo import android.appwidget.AppWidgetProviderInfo.WIDGET_FEATURE_CONFIGURATION_OPTIONAL import android.appwidget.AppWidgetProviderInfo.WIDGET_FEATURE_RECONFIGURABLE import android.content.ComponentName 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.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.shared.CommunalWidgetHost import com.android.systemui.communal.shared.model.CommunalWidgetContentModel import com.android.systemui.communal.widgets.CommunalAppWidgetHost import com.android.systemui.communal.widgets.CommunalWidgetHost import com.android.systemui.communal.widgets.widgetConfiguratorFail import com.android.systemui.communal.widgets.widgetConfiguratorSuccess import com.android.systemui.coroutines.collectLastValue Loading Loading @@ -136,14 +137,20 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() { val provider = ComponentName("pkg_name", "cls_name") val id = 1 val priority = 1 val user = UserHandle(0) whenever(communalWidgetHost.getAppWidgetInfo(id)) .thenReturn(PROVIDER_INFO_REQUIRES_CONFIGURATION) whenever(communalWidgetHost.allocateIdAndBindWidget(any<ComponentName>())) whenever( communalWidgetHost.allocateIdAndBindWidget( any<ComponentName>(), any<UserHandle>() ) ) .thenReturn(id) underTest.addWidget(provider, priority, kosmos.widgetConfiguratorSuccess) underTest.addWidget(provider, user, priority, kosmos.widgetConfiguratorSuccess) runCurrent() verify(communalWidgetHost).allocateIdAndBindWidget(provider) verify(communalWidgetHost).allocateIdAndBindWidget(provider, user) verify(communalWidgetDao).addWidget(id, provider, priority) } Loading @@ -153,13 +160,20 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() { val provider = ComponentName("pkg_name", "cls_name") val id = 1 val priority = 1 val user = UserHandle(0) whenever(communalWidgetHost.getAppWidgetInfo(id)) .thenReturn(PROVIDER_INFO_REQUIRES_CONFIGURATION) whenever(communalWidgetHost.allocateIdAndBindWidget(provider)).thenReturn(id) underTest.addWidget(provider, priority, kosmos.widgetConfiguratorFail) whenever( communalWidgetHost.allocateIdAndBindWidget( any<ComponentName>(), any<UserHandle>() ) ) .thenReturn(id) underTest.addWidget(provider, user, priority, kosmos.widgetConfiguratorFail) runCurrent() verify(communalWidgetHost).allocateIdAndBindWidget(provider) verify(communalWidgetHost).allocateIdAndBindWidget(provider, user) verify(communalWidgetDao, never()).addWidget(id, provider, priority) verify(appWidgetHost).deleteAppWidgetId(id) } Loading @@ -170,13 +184,22 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() { val provider = ComponentName("pkg_name", "cls_name") val id = 1 val priority = 1 val user = UserHandle(0) whenever(communalWidgetHost.getAppWidgetInfo(id)) .thenReturn(PROVIDER_INFO_REQUIRES_CONFIGURATION) whenever(communalWidgetHost.allocateIdAndBindWidget(provider)).thenReturn(id) underTest.addWidget(provider, priority) { throw IllegalStateException("some error") } whenever( communalWidgetHost.allocateIdAndBindWidget( any<ComponentName>(), any<UserHandle>() ) ) .thenReturn(id) underTest.addWidget(provider, user, priority) { throw IllegalStateException("some error") } runCurrent() verify(communalWidgetHost).allocateIdAndBindWidget(provider) verify(communalWidgetHost).allocateIdAndBindWidget(provider, user) verify(communalWidgetDao, never()).addWidget(id, provider, priority) verify(appWidgetHost).deleteAppWidgetId(id) } Loading @@ -187,14 +210,20 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() { val provider = ComponentName("pkg_name", "cls_name") val id = 1 val priority = 1 val user = UserHandle(0) whenever(communalWidgetHost.getAppWidgetInfo(id)) .thenReturn(PROVIDER_INFO_CONFIGURATION_OPTIONAL) whenever(communalWidgetHost.allocateIdAndBindWidget(any<ComponentName>())) whenever( communalWidgetHost.allocateIdAndBindWidget( any<ComponentName>(), any<UserHandle>() ) ) .thenReturn(id) underTest.addWidget(provider, priority, kosmos.widgetConfiguratorFail) underTest.addWidget(provider, user, priority, kosmos.widgetConfiguratorFail) runCurrent() verify(communalWidgetHost).allocateIdAndBindWidget(provider) verify(communalWidgetHost).allocateIdAndBindWidget(provider, user) verify(communalWidgetDao).addWidget(id, provider, priority) } Loading packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalWidgetHostTest.kt 0 → 100644 +166 −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.widgets import android.appwidget.AppWidgetManager import android.content.ComponentName import android.content.pm.UserInfo 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.coroutines.collectLastValue import com.android.systemui.kosmos.testScope import com.android.systemui.log.logcatLogBuffer import com.android.systemui.testKosmos import com.android.systemui.user.data.model.SelectedUserModel import com.android.systemui.user.data.model.SelectionStatus import com.android.systemui.user.data.repository.fakeUserRepository import com.android.systemui.user.domain.interactor.SelectedUserInteractor import com.android.systemui.user.domain.interactor.selectedUserInteractor import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.nullable import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import java.util.Optional 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.Mock import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @SmallTest @OptIn(ExperimentalCoroutinesApi::class) @RunWith(AndroidJUnit4::class) class CommunalWidgetHostTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope @Mock private lateinit var appWidgetManager: AppWidgetManager @Mock private lateinit var appWidgetHost: CommunalAppWidgetHost private val selectedUserInteractor: SelectedUserInteractor by lazy { kosmos.selectedUserInteractor } private lateinit var underTest: CommunalWidgetHost @Before fun setUp() { MockitoAnnotations.initMocks(this) underTest = CommunalWidgetHost( Optional.of(appWidgetManager), appWidgetHost, selectedUserInteractor, logcatLogBuffer("CommunalWidgetHostTest"), ) } @Test fun allocateIdAndBindWidget_withCurrentUser() = testScope.runTest { val provider = ComponentName("pkg_name", "cls_name") val widgetId = 1 val userId by collectLastValue(selectedUserInteractor.selectedUser) selectUser() runCurrent() val user = UserHandle(checkNotNull(userId)) whenever(appWidgetHost.allocateAppWidgetId()).thenReturn(widgetId) whenever( appWidgetManager.bindAppWidgetIdIfAllowed( any<Int>(), any<UserHandle>(), any<ComponentName>(), nullable() ) ) .thenReturn(true) // bind the widget with the current user when no user is explicitly set val result = underTest.allocateIdAndBindWidget(provider) verify(appWidgetHost).allocateAppWidgetId() verify(appWidgetManager).bindAppWidgetIdIfAllowed(widgetId, user, provider, null) assertThat(result).isEqualTo(widgetId) } @Test fun allocateIdAndBindWidget_onSuccess() = testScope.runTest { val provider = ComponentName("pkg_name", "cls_name") val widgetId = 1 val user = UserHandle(0) whenever(appWidgetHost.allocateAppWidgetId()).thenReturn(widgetId) whenever( appWidgetManager.bindAppWidgetIdIfAllowed( any<Int>(), any<UserHandle>(), any<ComponentName>(), nullable() ) ) .thenReturn(true) // provider and user handle are both set val result = underTest.allocateIdAndBindWidget(provider, user) verify(appWidgetHost).allocateAppWidgetId() verify(appWidgetManager).bindAppWidgetIdIfAllowed(widgetId, user, provider, null) assertThat(result).isEqualTo(widgetId) } @Test fun allocateIdAndBindWidget_onFailure() = testScope.runTest { val provider = ComponentName("pkg_name", "cls_name") val widgetId = 1 val user = UserHandle(0) whenever(appWidgetHost.allocateAppWidgetId()).thenReturn(widgetId) // failed to bind widget whenever( appWidgetManager.bindAppWidgetIdIfAllowed( any<Int>(), any<UserHandle>(), any<ComponentName>(), nullable() ) ) .thenReturn(false) val result = underTest.allocateIdAndBindWidget(provider, user) verify(appWidgetHost).allocateAppWidgetId() verify(appWidgetManager).bindAppWidgetIdIfAllowed(widgetId, user, provider, null) verify(appWidgetHost).deleteAppWidgetId(widgetId) assertThat(result).isNull() } private fun selectUser() { kosmos.fakeUserRepository.selectedUser.value = SelectedUserModel( userInfo = UserInfo(0, "Current user", 0), selectionStatus = SelectionStatus.SELECTION_COMPLETE ) } } packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt +2 −0 Original line number Diff line number Diff line Loading @@ -23,6 +23,7 @@ import com.android.systemui.communal.data.repository.CommunalRepositoryModule import com.android.systemui.communal.data.repository.CommunalSettingsRepositoryModule import com.android.systemui.communal.data.repository.CommunalTutorialRepositoryModule import com.android.systemui.communal.data.repository.CommunalWidgetRepositoryModule import com.android.systemui.communal.widgets.CommunalWidgetModule import com.android.systemui.communal.widgets.EditWidgetsActivityStarter import com.android.systemui.communal.widgets.EditWidgetsActivityStarterImpl import dagger.Binds Loading @@ -36,6 +37,7 @@ import dagger.Module CommunalTutorialRepositoryModule::class, CommunalWidgetRepositoryModule::class, CommunalDatabaseModule::class, CommunalWidgetModule::class, CommunalPrefsRepositoryModule::class, CommunalSettingsRepositoryModule::class, ] Loading Loading
packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt +14 −5 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.systemui.communal.ui.compose import android.content.ComponentName import android.os.UserHandle import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.runtime.toMutableStateList Loading @@ -33,9 +34,10 @@ fun rememberContentListState( return remember(communalContent) { ContentListState( communalContent, { componentName, priority -> { componentName, user, priority -> viewModel.onAddWidget( componentName, user, priority, widgetConfigurator, ) Loading @@ -54,7 +56,8 @@ fun rememberContentListState( class ContentListState internal constructor( communalContent: List<CommunalContentModel>, private val onAddWidget: (componentName: ComponentName, priority: Int) -> Unit, private val onAddWidget: (componentName: ComponentName, user: UserHandle, priority: Int) -> Unit, private val onDeleteWidget: (id: Int) -> Unit, private val onReorderWidgets: (widgetIdToPriorityMap: Map<Int, Int>) -> Unit, ) { Loading @@ -81,10 +84,16 @@ internal constructor( * * @param newItemComponentName name of the new widget that was dropped into the list; null if no * new widget was added. * @param newItemUser user profile associated with the new widget that was dropped into the * list; null if no new widget was added. * @param newItemIndex index at which the a new widget was dropped into the list; null if no new * widget was dropped. */ fun onSaveList(newItemComponentName: ComponentName? = null, newItemIndex: Int? = null) { fun onSaveList( newItemComponentName: ComponentName? = null, newItemUser: UserHandle? = null, newItemIndex: Int? = null ) { // filters placeholder, but, maintains the indices of the widgets as if the placeholder was // in the list. When persisted in DB, this leaves space for the new item (to be added) at // the correct priority. Loading @@ -100,8 +109,8 @@ internal constructor( .toMap() // reorder and then add the new widget onReorderWidgets(widgetIdToPriorityMap) if (newItemComponentName != null && newItemIndex != null) { onAddWidget(newItemComponentName, /*priority=*/ list.size - newItemIndex) if (newItemComponentName != null && newItemUser != null && newItemIndex != null) { onAddWidget(newItemComponentName, newItemUser, /*priority=*/ list.size - newItemIndex) } } Loading
packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt +10 −11 Original line number Diff line number Diff line Loading @@ -17,8 +17,6 @@ package com.android.systemui.communal.ui.compose import android.content.ClipDescription import android.content.ComponentName import android.content.Intent import android.view.DragEvent import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.draganddrop.dragAndDropTarget Loading @@ -44,6 +42,8 @@ import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.dp import com.android.systemui.communal.domain.model.CommunalContentModel import com.android.systemui.communal.ui.compose.extensions.plus import com.android.systemui.communal.util.WidgetPickerIntentUtils import com.android.systemui.communal.util.WidgetPickerIntentUtils.getWidgetExtraFromIntent import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.isActive Loading Loading @@ -201,12 +201,14 @@ internal class DragAndDropTargetState( return false } return placeHolderIndex?.let { dropIndex -> val componentName = event.maybeWidgetComponentName() if (componentName != null) { val widgetExtra = event.maybeWidgetExtra() ?: return false val (componentName, user) = widgetExtra if (componentName != null && user != null) { // Placeholder isn't removed yet to allow the setting the right priority for items // before adding in the new item. contentListState.onSaveList( newItemComponentName = componentName, newItemUser = user, newItemIndex = dropIndex ) return@let true Loading Loading @@ -260,15 +262,12 @@ internal class DragAndDropTargetState( } /** * Parses and returns the component name of the widget that was dropped into the communal grid. * Parses and returns the intent extra associated with the widget that is dropped into the grid. * * Returns null if the drop event didn't include the widget information. * Returns null if the drop event didn't include intent information. */ private fun DragAndDropEvent.maybeWidgetComponentName(): ComponentName? { private fun DragAndDropEvent.maybeWidgetExtra(): WidgetPickerIntentUtils.WidgetExtra? { val clipData = this.toAndroidDragEvent().clipData.takeIf { it.itemCount != 0 } return clipData ?.getItemAt(0) ?.intent ?.getParcelableExtra(Intent.EXTRA_COMPONENT_NAME, ComponentName::class.java) return clipData?.getItemAt(0)?.intent?.let { intent -> getWidgetExtraFromIntent(intent) } } }
packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt +42 −13 Original line number Diff line number Diff line Loading @@ -21,15 +21,16 @@ import android.appwidget.AppWidgetProviderInfo import android.appwidget.AppWidgetProviderInfo.WIDGET_FEATURE_CONFIGURATION_OPTIONAL import android.appwidget.AppWidgetProviderInfo.WIDGET_FEATURE_RECONFIGURABLE import android.content.ComponentName 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.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.shared.CommunalWidgetHost import com.android.systemui.communal.shared.model.CommunalWidgetContentModel import com.android.systemui.communal.widgets.CommunalAppWidgetHost import com.android.systemui.communal.widgets.CommunalWidgetHost import com.android.systemui.communal.widgets.widgetConfiguratorFail import com.android.systemui.communal.widgets.widgetConfiguratorSuccess import com.android.systemui.coroutines.collectLastValue Loading Loading @@ -136,14 +137,20 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() { val provider = ComponentName("pkg_name", "cls_name") val id = 1 val priority = 1 val user = UserHandle(0) whenever(communalWidgetHost.getAppWidgetInfo(id)) .thenReturn(PROVIDER_INFO_REQUIRES_CONFIGURATION) whenever(communalWidgetHost.allocateIdAndBindWidget(any<ComponentName>())) whenever( communalWidgetHost.allocateIdAndBindWidget( any<ComponentName>(), any<UserHandle>() ) ) .thenReturn(id) underTest.addWidget(provider, priority, kosmos.widgetConfiguratorSuccess) underTest.addWidget(provider, user, priority, kosmos.widgetConfiguratorSuccess) runCurrent() verify(communalWidgetHost).allocateIdAndBindWidget(provider) verify(communalWidgetHost).allocateIdAndBindWidget(provider, user) verify(communalWidgetDao).addWidget(id, provider, priority) } Loading @@ -153,13 +160,20 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() { val provider = ComponentName("pkg_name", "cls_name") val id = 1 val priority = 1 val user = UserHandle(0) whenever(communalWidgetHost.getAppWidgetInfo(id)) .thenReturn(PROVIDER_INFO_REQUIRES_CONFIGURATION) whenever(communalWidgetHost.allocateIdAndBindWidget(provider)).thenReturn(id) underTest.addWidget(provider, priority, kosmos.widgetConfiguratorFail) whenever( communalWidgetHost.allocateIdAndBindWidget( any<ComponentName>(), any<UserHandle>() ) ) .thenReturn(id) underTest.addWidget(provider, user, priority, kosmos.widgetConfiguratorFail) runCurrent() verify(communalWidgetHost).allocateIdAndBindWidget(provider) verify(communalWidgetHost).allocateIdAndBindWidget(provider, user) verify(communalWidgetDao, never()).addWidget(id, provider, priority) verify(appWidgetHost).deleteAppWidgetId(id) } Loading @@ -170,13 +184,22 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() { val provider = ComponentName("pkg_name", "cls_name") val id = 1 val priority = 1 val user = UserHandle(0) whenever(communalWidgetHost.getAppWidgetInfo(id)) .thenReturn(PROVIDER_INFO_REQUIRES_CONFIGURATION) whenever(communalWidgetHost.allocateIdAndBindWidget(provider)).thenReturn(id) underTest.addWidget(provider, priority) { throw IllegalStateException("some error") } whenever( communalWidgetHost.allocateIdAndBindWidget( any<ComponentName>(), any<UserHandle>() ) ) .thenReturn(id) underTest.addWidget(provider, user, priority) { throw IllegalStateException("some error") } runCurrent() verify(communalWidgetHost).allocateIdAndBindWidget(provider) verify(communalWidgetHost).allocateIdAndBindWidget(provider, user) verify(communalWidgetDao, never()).addWidget(id, provider, priority) verify(appWidgetHost).deleteAppWidgetId(id) } Loading @@ -187,14 +210,20 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() { val provider = ComponentName("pkg_name", "cls_name") val id = 1 val priority = 1 val user = UserHandle(0) whenever(communalWidgetHost.getAppWidgetInfo(id)) .thenReturn(PROVIDER_INFO_CONFIGURATION_OPTIONAL) whenever(communalWidgetHost.allocateIdAndBindWidget(any<ComponentName>())) whenever( communalWidgetHost.allocateIdAndBindWidget( any<ComponentName>(), any<UserHandle>() ) ) .thenReturn(id) underTest.addWidget(provider, priority, kosmos.widgetConfiguratorFail) underTest.addWidget(provider, user, priority, kosmos.widgetConfiguratorFail) runCurrent() verify(communalWidgetHost).allocateIdAndBindWidget(provider) verify(communalWidgetHost).allocateIdAndBindWidget(provider, user) verify(communalWidgetDao).addWidget(id, provider, priority) } Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalWidgetHostTest.kt 0 → 100644 +166 −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.widgets import android.appwidget.AppWidgetManager import android.content.ComponentName import android.content.pm.UserInfo 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.coroutines.collectLastValue import com.android.systemui.kosmos.testScope import com.android.systemui.log.logcatLogBuffer import com.android.systemui.testKosmos import com.android.systemui.user.data.model.SelectedUserModel import com.android.systemui.user.data.model.SelectionStatus import com.android.systemui.user.data.repository.fakeUserRepository import com.android.systemui.user.domain.interactor.SelectedUserInteractor import com.android.systemui.user.domain.interactor.selectedUserInteractor import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.nullable import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import java.util.Optional 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.Mock import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @SmallTest @OptIn(ExperimentalCoroutinesApi::class) @RunWith(AndroidJUnit4::class) class CommunalWidgetHostTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope @Mock private lateinit var appWidgetManager: AppWidgetManager @Mock private lateinit var appWidgetHost: CommunalAppWidgetHost private val selectedUserInteractor: SelectedUserInteractor by lazy { kosmos.selectedUserInteractor } private lateinit var underTest: CommunalWidgetHost @Before fun setUp() { MockitoAnnotations.initMocks(this) underTest = CommunalWidgetHost( Optional.of(appWidgetManager), appWidgetHost, selectedUserInteractor, logcatLogBuffer("CommunalWidgetHostTest"), ) } @Test fun allocateIdAndBindWidget_withCurrentUser() = testScope.runTest { val provider = ComponentName("pkg_name", "cls_name") val widgetId = 1 val userId by collectLastValue(selectedUserInteractor.selectedUser) selectUser() runCurrent() val user = UserHandle(checkNotNull(userId)) whenever(appWidgetHost.allocateAppWidgetId()).thenReturn(widgetId) whenever( appWidgetManager.bindAppWidgetIdIfAllowed( any<Int>(), any<UserHandle>(), any<ComponentName>(), nullable() ) ) .thenReturn(true) // bind the widget with the current user when no user is explicitly set val result = underTest.allocateIdAndBindWidget(provider) verify(appWidgetHost).allocateAppWidgetId() verify(appWidgetManager).bindAppWidgetIdIfAllowed(widgetId, user, provider, null) assertThat(result).isEqualTo(widgetId) } @Test fun allocateIdAndBindWidget_onSuccess() = testScope.runTest { val provider = ComponentName("pkg_name", "cls_name") val widgetId = 1 val user = UserHandle(0) whenever(appWidgetHost.allocateAppWidgetId()).thenReturn(widgetId) whenever( appWidgetManager.bindAppWidgetIdIfAllowed( any<Int>(), any<UserHandle>(), any<ComponentName>(), nullable() ) ) .thenReturn(true) // provider and user handle are both set val result = underTest.allocateIdAndBindWidget(provider, user) verify(appWidgetHost).allocateAppWidgetId() verify(appWidgetManager).bindAppWidgetIdIfAllowed(widgetId, user, provider, null) assertThat(result).isEqualTo(widgetId) } @Test fun allocateIdAndBindWidget_onFailure() = testScope.runTest { val provider = ComponentName("pkg_name", "cls_name") val widgetId = 1 val user = UserHandle(0) whenever(appWidgetHost.allocateAppWidgetId()).thenReturn(widgetId) // failed to bind widget whenever( appWidgetManager.bindAppWidgetIdIfAllowed( any<Int>(), any<UserHandle>(), any<ComponentName>(), nullable() ) ) .thenReturn(false) val result = underTest.allocateIdAndBindWidget(provider, user) verify(appWidgetHost).allocateAppWidgetId() verify(appWidgetManager).bindAppWidgetIdIfAllowed(widgetId, user, provider, null) verify(appWidgetHost).deleteAppWidgetId(widgetId) assertThat(result).isNull() } private fun selectUser() { kosmos.fakeUserRepository.selectedUser.value = SelectedUserModel( userInfo = UserInfo(0, "Current user", 0), selectionStatus = SelectionStatus.SELECTION_COMPLETE ) } }
packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt +2 −0 Original line number Diff line number Diff line Loading @@ -23,6 +23,7 @@ import com.android.systemui.communal.data.repository.CommunalRepositoryModule import com.android.systemui.communal.data.repository.CommunalSettingsRepositoryModule import com.android.systemui.communal.data.repository.CommunalTutorialRepositoryModule import com.android.systemui.communal.data.repository.CommunalWidgetRepositoryModule import com.android.systemui.communal.widgets.CommunalWidgetModule import com.android.systemui.communal.widgets.EditWidgetsActivityStarter import com.android.systemui.communal.widgets.EditWidgetsActivityStarterImpl import dagger.Binds Loading @@ -36,6 +37,7 @@ import dagger.Module CommunalTutorialRepositoryModule::class, CommunalWidgetRepositoryModule::class, CommunalDatabaseModule::class, CommunalWidgetModule::class, CommunalPrefsRepositoryModule::class, CommunalSettingsRepositoryModule::class, ] Loading