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

Commit b9c1b6ee authored by Lucas Silva's avatar Lucas Silva
Browse files

Allow widgets to be reconfigured

When in edit mode, allow widgets to be reconfigured if they allow
reconfiguration.

This also refactors the existing configuration logic to delegate
configuration to a standalone class.

Bug: 318537189
Test: atest WidgetConfigurationControllerTest
Test: atest CommunalWidgetRepositoryImplTest
Test: flashed device and verified configuration works
Flag: ACONFIG com.android.systemui.communal_hub DEVELOPMENT
Change-Id: I32239eff8ac91cb81fc9ef14e41ac64dad03a8f7
parent 542e50c6
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ import androidx.lifecycle.LifecycleOwner
import com.android.systemui.bouncer.ui.BouncerDialogFactory
import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
import com.android.systemui.communal.widgets.WidgetConfigurator
import com.android.systemui.people.ui.viewmodel.PeopleViewModel
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
import com.android.systemui.scene.shared.model.Scene
@@ -52,6 +53,7 @@ object ComposeFacade : BaseComposeFacade {
    override fun setCommunalEditWidgetActivityContent(
        activity: ComponentActivity,
        viewModel: BaseCommunalViewModel,
        widgetConfigurator: WidgetConfigurator,
        onOpenWidgetPicker: () -> Unit,
        onEditDone: () -> Unit,
    ) {
+3 −0
Original line number Diff line number Diff line
@@ -37,6 +37,7 @@ import com.android.systemui.common.ui.compose.windowinsets.DisplayCutoutProvider
import com.android.systemui.communal.ui.compose.CommunalContainer
import com.android.systemui.communal.ui.compose.CommunalHub
import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
import com.android.systemui.communal.widgets.WidgetConfigurator
import com.android.systemui.people.ui.compose.PeopleScreen
import com.android.systemui.people.ui.viewmodel.PeopleViewModel
import com.android.systemui.qs.footer.ui.compose.FooterActions
@@ -69,6 +70,7 @@ object ComposeFacade : BaseComposeFacade {
    override fun setCommunalEditWidgetActivityContent(
        activity: ComponentActivity,
        viewModel: BaseCommunalViewModel,
        widgetConfigurator: WidgetConfigurator,
        onOpenWidgetPicker: () -> Unit,
        onEditDone: () -> Unit,
    ) {
@@ -77,6 +79,7 @@ object ComposeFacade : BaseComposeFacade {
                CommunalHub(
                    viewModel = viewModel,
                    onOpenWidgetPicker = onOpenWidgetPicker,
                    widgetConfigurator = widgetConfigurator,
                    onEditDone = onEditDone,
                )
            }
+76 −7
Original line number Diff line number Diff line
@@ -20,7 +20,10 @@ import android.appwidget.AppWidgetHostView
import android.os.Bundle
import android.util.SizeF
import android.widget.FrameLayout
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
@@ -47,6 +50,7 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Edit
import androidx.compose.material.icons.outlined.Delete
import androidx.compose.material.icons.outlined.Edit
import androidx.compose.material.icons.outlined.TouchApp
import androidx.compose.material.icons.outlined.Widgets
import androidx.compose.material3.Button
@@ -54,8 +58,10 @@ import androidx.compose.material3.ButtonColors
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.FilledIconButton
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.IconButtonColors
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Text
@@ -66,6 +72,7 @@ import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -99,12 +106,15 @@ import com.android.systemui.communal.ui.compose.extensions.firstItemAtOffset
import com.android.systemui.communal.ui.compose.extensions.observeTapsWithoutConsuming
import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel
import com.android.systemui.communal.widgets.WidgetConfigurator
import com.android.systemui.res.R
import kotlinx.coroutines.launch

@Composable
fun CommunalHub(
    modifier: Modifier = Modifier,
    viewModel: BaseCommunalViewModel,
    widgetConfigurator: WidgetConfigurator? = null,
    onOpenWidgetPicker: (() -> Unit)? = null,
    onEditDone: (() -> Unit)? = null,
) {
@@ -116,7 +126,7 @@ fun CommunalHub(
    var gridCoordinates: LayoutCoordinates? by remember { mutableStateOf(null) }
    var isDraggingToRemove by remember { mutableStateOf(false) }
    val gridState = rememberLazyGridState()
    val contentListState = rememberContentListState(communalContent, viewModel)
    val contentListState = rememberContentListState(widgetConfigurator, communalContent, viewModel)
    val reorderingWidgets by viewModel.reorderingWidgets.collectAsState()
    val selectedIndex = viewModel.selectedIndex.collectAsState()
    val removeButtonEnabled by remember {
@@ -167,7 +177,8 @@ fun CommunalHub(
            onOpenWidgetPicker = onOpenWidgetPicker,
            gridState = gridState,
            contentListState = contentListState,
            selectedIndex = selectedIndex
            selectedIndex = selectedIndex,
            widgetConfigurator = widgetConfigurator,
        )

        if (viewModel.isEditMode && onOpenWidgetPicker != null && onEditDone != null) {
@@ -221,6 +232,7 @@ private fun BoxScope.CommunalHubLazyGrid(
    setGridCoordinates: (coordinates: LayoutCoordinates) -> Unit,
    updateDragPositionForRemove: (offset: Offset) -> Boolean,
    onOpenWidgetPicker: (() -> Unit)? = null,
    widgetConfigurator: WidgetConfigurator?,
) {
    var gridModifier = Modifier.align(Alignment.CenterStart)
    var list = communalContent
@@ -283,21 +295,24 @@ private fun BoxScope.CommunalHubLazyGrid(
                    enabled = list[index] is CommunalContentModel.Widget,
                    index = index,
                    size = size
                ) { _ ->
                ) { isDragging ->
                    CommunalContent(
                        modifier = cardModifier,
                        model = list[index],
                        viewModel = viewModel,
                        size = size,
                        onOpenWidgetPicker = onOpenWidgetPicker,
                        selected = selected && !isDragging,
                        widgetConfigurator = widgetConfigurator,
                    )
                }
            } else {
                CommunalContent(
                    modifier = cardModifier,
                    model = list[index],
                    viewModel = viewModel,
                    size = size,
                    selected = false,
                    modifier = cardModifier,
                )
            }
        }
@@ -453,11 +468,14 @@ private fun CommunalContent(
    model: CommunalContentModel,
    viewModel: BaseCommunalViewModel,
    size: SizeF,
    selected: Boolean,
    modifier: Modifier = Modifier,
    onOpenWidgetPicker: (() -> Unit)? = null,
    widgetConfigurator: WidgetConfigurator? = null,
) {
    when (model) {
        is CommunalContentModel.Widget -> WidgetContent(viewModel, model, size, modifier)
        is CommunalContentModel.Widget ->
            WidgetContent(viewModel, model, size, selected, widgetConfigurator, modifier)
        is CommunalContentModel.WidgetPlaceholder -> HighlightedItem(size)
        is CommunalContentModel.CtaTileInViewMode ->
            CtaTileInViewModeContent(viewModel, size, modifier)
@@ -594,15 +612,17 @@ private fun WidgetContent(
    viewModel: BaseCommunalViewModel,
    model: CommunalContentModel.Widget,
    size: SizeF,
    selected: Boolean,
    widgetConfigurator: WidgetConfigurator?,
    modifier: Modifier = Modifier,
) {
    Box(
        modifier = modifier.height(size.height.dp),
        contentAlignment = Alignment.Center,
    ) {
        val paddingInPx = with(LocalDensity.current) { CardOutlineWidth.toPx().toInt() }
        AndroidView(
            modifier = modifier.allowGestures(allowed = !viewModel.isEditMode),
            modifier =
                modifier.align(Alignment.Center).allowGestures(allowed = !viewModel.isEditMode),
            factory = { context ->
                // The AppWidgetHostView will inherit the interaction handler from the
                // AppWidgetHost. So set the interaction handler here before creating the view, and
@@ -624,6 +644,55 @@ private fun WidgetContent(
            // For reusing composition in lazy lists.
            onReset = {},
        )
        if (
            viewModel is CommunalEditModeViewModel &&
                model.reconfigurable &&
                widgetConfigurator != null
        ) {
            WidgetConfigureButton(
                visible = selected,
                model = model,
                widgetConfigurator = widgetConfigurator,
                modifier = Modifier.align(Alignment.BottomEnd)
            )
        }
    }
}

@Composable
fun WidgetConfigureButton(
    visible: Boolean,
    model: CommunalContentModel.Widget,
    modifier: Modifier = Modifier,
    widgetConfigurator: WidgetConfigurator,
) {
    val colors = LocalAndroidColorScheme.current
    val scope = rememberCoroutineScope()

    AnimatedVisibility(
        visible = visible,
        enter = fadeIn(),
        exit = fadeOut(),
        modifier = modifier.padding(16.dp),
    ) {
        FilledIconButton(
            shape = RoundedCornerShape(16.dp),
            modifier = Modifier.size(48.dp),
            colors =
                IconButtonColors(
                    containerColor = colors.primary,
                    contentColor = colors.onPrimary,
                    disabledContainerColor = Color.Transparent,
                    disabledContentColor = Color.Transparent
                ),
            onClick = { scope.launch { widgetConfigurator.configureWidget(model.appWidgetId) } },
        ) {
            Icon(
                imageVector = Icons.Outlined.Edit,
                contentDescription = stringResource(id = R.string.edit_widget),
                modifier = Modifier.padding(12.dp)
            )
        }
    }
}

+9 −1
Original line number Diff line number Diff line
@@ -22,16 +22,24 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.toMutableStateList
import com.android.systemui.communal.domain.model.CommunalContentModel
import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
import com.android.systemui.communal.widgets.WidgetConfigurator

@Composable
fun rememberContentListState(
    widgetConfigurator: WidgetConfigurator?,
    communalContent: List<CommunalContentModel>,
    viewModel: BaseCommunalViewModel,
): ContentListState {
    return remember(communalContent) {
        ContentListState(
            communalContent,
            viewModel::onAddWidget,
            { componentName, priority ->
                viewModel.onAddWidget(
                    componentName,
                    priority,
                    widgetConfigurator,
                )
            },
            viewModel::onDeleteWidget,
            viewModel::onReorderWidgets,
        )
+39 −16
Original line number Diff line number Diff line
@@ -18,6 +18,8 @@ package com.android.systemui.communal.data.repository

import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProviderInfo
import android.appwidget.AppWidgetProviderInfo.WIDGET_FEATURE_CONFIGURATION_OPTIONAL
import android.appwidget.AppWidgetProviderInfo.WIDGET_FEATURE_RECONFIGURABLE
import android.content.ComponentName
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -28,6 +30,9 @@ 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.WidgetConfigurator
import com.android.systemui.communal.widgets.widgetConfiguratorFail
import com.android.systemui.communal.widgets.widgetConfiguratorSuccess
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
@@ -71,12 +76,12 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {

    @Mock private lateinit var communalWidgetDao: CommunalWidgetDao

    private val kosmos = testKosmos()
    private lateinit var logBuffer: LogBuffer

    private val kosmos = testKosmos()
    private val testDispatcher = kosmos.testDispatcher
    private val testScope = kosmos.testScope

    private lateinit var logBuffer: LogBuffer

    private val fakeAllowlist =
        listOf(
            "com.android.fake/WidgetProviderA",
@@ -157,10 +162,11 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
            val provider = ComponentName("pkg_name", "cls_name")
            val id = 1
            val priority = 1
            whenever(communalWidgetHost.requiresConfiguration(id)).thenReturn(true)
            whenever(communalWidgetHost.getAppWidgetInfo(id))
                .thenReturn(PROVIDER_INFO_REQUIRES_CONFIGURATION)
            whenever(communalWidgetHost.allocateIdAndBindWidget(any<ComponentName>()))
                .thenReturn(id)
            underTest.addWidget(provider, priority) { true }
            underTest.addWidget(provider, priority, kosmos.widgetConfiguratorSuccess)
            runCurrent()

            verify(communalWidgetHost).allocateIdAndBindWidget(provider)
@@ -175,9 +181,10 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
            val provider = ComponentName("pkg_name", "cls_name")
            val id = 1
            val priority = 1
            whenever(communalWidgetHost.requiresConfiguration(id)).thenReturn(true)
            whenever(communalWidgetHost.getAppWidgetInfo(id))
                .thenReturn(PROVIDER_INFO_REQUIRES_CONFIGURATION)
            whenever(communalWidgetHost.allocateIdAndBindWidget(provider)).thenReturn(id)
            underTest.addWidget(provider, priority) { false }
            underTest.addWidget(provider, priority, kosmos.widgetConfiguratorFail)
            runCurrent()

            verify(communalWidgetHost).allocateIdAndBindWidget(provider)
@@ -193,9 +200,18 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
            val provider = ComponentName("pkg_name", "cls_name")
            val id = 1
            val priority = 1
            whenever(communalWidgetHost.requiresConfiguration(id)).thenReturn(true)
            whenever(communalWidgetHost.getAppWidgetInfo(id))
                .thenReturn(PROVIDER_INFO_REQUIRES_CONFIGURATION)
            whenever(communalWidgetHost.allocateIdAndBindWidget(provider)).thenReturn(id)
            underTest.addWidget(provider, priority) { throw IllegalStateException("some error") }
            underTest.addWidget(
                provider,
                priority,
                object : WidgetConfigurator {
                    override suspend fun configureWidget(appWidgetId: Int): Boolean {
                        throw IllegalStateException("some error")
                    }
                }
            )
            runCurrent()

            verify(communalWidgetHost).allocateIdAndBindWidget(provider)
@@ -211,19 +227,15 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
            val provider = ComponentName("pkg_name", "cls_name")
            val id = 1
            val priority = 1
            whenever(communalWidgetHost.requiresConfiguration(id)).thenReturn(false)
            whenever(communalWidgetHost.getAppWidgetInfo(id))
                .thenReturn(PROVIDER_INFO_CONFIGURATION_OPTIONAL)
            whenever(communalWidgetHost.allocateIdAndBindWidget(any<ComponentName>()))
                .thenReturn(id)
            var configured = false
            underTest.addWidget(provider, priority) {
                configured = true
                true
            }
            underTest.addWidget(provider, priority, kosmos.widgetConfiguratorFail)
            runCurrent()

            verify(communalWidgetHost).allocateIdAndBindWidget(provider)
            verify(communalWidgetDao).addWidget(id, provider, priority)
            assertThat(configured).isFalse()
        }

    @Test
@@ -280,4 +292,15 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
    private fun setAppWidgetIds(ids: List<Int>) {
        whenever(appWidgetHost.appWidgetIds).thenReturn(ids.toIntArray())
    }

    private companion object {
        val PROVIDER_INFO_REQUIRES_CONFIGURATION =
            AppWidgetProviderInfo().apply { configure = ComponentName("test.pkg", "test.cmp") }
        val PROVIDER_INFO_CONFIGURATION_OPTIONAL =
            AppWidgetProviderInfo().apply {
                configure = ComponentName("test.pkg", "test.cmp")
                widgetFeatures =
                    WIDGET_FEATURE_CONFIGURATION_OPTIONAL or WIDGET_FEATURE_RECONFIGURABLE
            }
    }
}
Loading