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

Commit 577cd7f8 authored by Coco Duan's avatar Coco Duan Committed by Android (Google) Code Review
Browse files

Merge "Show placeholder for disabled widgets" into main

parents 2c957790 e79d75b3
Loading
Loading
Loading
Loading
+76 −8
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.systemui.communal.ui.compose

import android.appwidget.AppWidgetHostView
import android.graphics.drawable.Icon
import android.os.Bundle
import android.util.SizeF
import android.widget.FrameLayout
@@ -26,6 +27,7 @@ import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
@@ -77,6 +79,8 @@ import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.ColorMatrix
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.layout.LayoutCoordinates
@@ -85,8 +89,10 @@ import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.layout.positionInWindow
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.testTagsAsResourceId
@@ -101,6 +107,8 @@ import androidx.compose.ui.window.Popup
import androidx.core.view.setPadding
import com.android.compose.modifiers.thenIf
import com.android.compose.theme.LocalAndroidColorScheme
import com.android.compose.ui.graphics.painter.rememberDrawablePainter
import com.android.internal.R.dimen.system_app_widget_background_radius
import com.android.systemui.communal.domain.model.CommunalContentModel
import com.android.systemui.communal.shared.model.CommunalContentSize
import com.android.systemui.communal.ui.compose.Dimensions.CardOutlineWidth
@@ -178,7 +186,7 @@ fun CommunalHub(
                            // not display this button.
                            if (
                                index == null ||
                                    communalContent[index].isWidget() ||
                                    communalContent[index].isWidgetContent() ||
                                    communalContent[index] is CommunalContentModel.CtaTileInViewMode
                            ) {
                                isButtonToEditWidgetsShowing = true
@@ -330,7 +338,7 @@ private fun BoxScope.CommunalHubLazyGrid(
                DraggableItem(
                    dragDropState = dragDropState,
                    selected = selected,
                    enabled = list[index] is CommunalContentModel.Widget,
                    enabled = list[index].isWidgetContent(),
                    index = index,
                ) { isDragging ->
                    CommunalContent(
@@ -539,9 +547,11 @@ private fun CommunalContent(
    widgetConfigurator: WidgetConfigurator? = null,
) {
    when (model) {
        is CommunalContentModel.Widget ->
        is CommunalContentModel.WidgetContent.Widget ->
            WidgetContent(viewModel, model, size, selected, widgetConfigurator, modifier)
        is CommunalContentModel.WidgetPlaceholder -> HighlightedItem(modifier)
        is CommunalContentModel.WidgetContent.DisabledWidget ->
            DisabledWidgetPlaceholder(model, modifier)
        is CommunalContentModel.CtaTileInViewMode -> CtaTileInViewModeContent(viewModel, modifier)
        is CommunalContentModel.CtaTileInEditMode ->
            CtaTileInEditModeContent(modifier, onOpenWidgetPicker)
@@ -672,7 +682,7 @@ private fun CtaTileInEditModeContent(
@Composable
private fun WidgetContent(
    viewModel: BaseCommunalViewModel,
    model: CommunalContentModel.Widget,
    model: CommunalContentModel.WidgetContent.Widget,
    size: SizeF,
    selected: Boolean,
    widgetConfigurator: WidgetConfigurator?,
@@ -692,8 +702,9 @@ private fun WidgetContent(
            },
            update = { view ->
                // Remove the extra padding applied to AppWidgetHostView to allow widgets to
                // occupy the entire box. The added padding is now adjusted to leave only sufficient
                // space for displaying the outline around the box when the widget is selected.
                // occupy the entire box. The added padding is now adjusted to leave only
                // sufficient space for displaying the outline around the box when the widget
                // is selected.
                view.setPadding(paddingInPx)
            },
            // For reusing composition in lazy lists.
@@ -717,7 +728,7 @@ private fun WidgetContent(
@Composable
fun WidgetConfigureButton(
    visible: Boolean,
    model: CommunalContentModel.Widget,
    model: CommunalContentModel.WidgetContent.Widget,
    modifier: Modifier = Modifier,
    widgetConfigurator: WidgetConfigurator,
) {
@@ -751,6 +762,38 @@ fun WidgetConfigureButton(
    }
}

@Composable
fun DisabledWidgetPlaceholder(
    model: CommunalContentModel.WidgetContent.DisabledWidget,
    modifier: Modifier = Modifier,
) {
    val context = LocalContext.current
    val appInfo = model.appInfo
    val icon: Icon =
        if (appInfo == null || appInfo.icon == 0) {
            Icon.createWithResource(context, android.R.drawable.sym_def_app_icon)
        } else {
            Icon.createWithResource(appInfo.packageName, appInfo.icon)
        }

    Column(
        modifier =
            modifier.background(
                MaterialTheme.colorScheme.surfaceVariant,
                RoundedCornerShape(dimensionResource(system_app_widget_background_radius))
            ),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally,
    ) {
        Image(
            painter = rememberDrawablePainter(icon.loadDrawable(context)),
            contentDescription = stringResource(R.string.icon_description_for_disabled_widget),
            modifier = Modifier.size(48.dp),
            colorFilter = ColorFilter.colorMatrix(Colors.DisabledColorFilter),
        )
    }
}

@Composable
private fun SmartspaceContent(
    model: CommunalContentModel.Smartspace,
@@ -852,7 +895,7 @@ private fun firstIndexAtOffset(gridState: LazyGridState, offset: Offset): Int? =

/** Returns the key of item if it's editable at the given index. Only widget is editable. */
private fun keyAtIndexIfEditable(list: List<CommunalContentModel>, index: Int): String? =
    if (index in list.indices && list[index].isWidget()) list[index].key else null
    if (index in list.indices && list[index].isWidgetContent()) list[index].key else null

data class ContentPaddingInPx(val start: Float, val top: Float) {
    fun toOffset(): Offset = Offset(start, top)
@@ -882,5 +925,30 @@ object Dimensions {
    val IconSize = 48.dp
}

private object Colors {
    val DisabledColorFilter by lazy { disabledColorMatrix() }

    /** Returns the disabled image filter. Ported over from [DisableImageView]. */
    private fun disabledColorMatrix(): ColorMatrix {
        val brightnessMatrix = ColorMatrix()
        val brightnessAmount = 0.5f
        val brightnessRgb = (255 * brightnessAmount).toInt().toFloat()
        // Brightness: C-new = C-old*(1-amount) + amount
        val scale = 1f - brightnessAmount
        val mat = brightnessMatrix.values
        mat[0] = scale
        mat[6] = scale
        mat[12] = scale
        mat[4] = brightnessRgb
        mat[9] = brightnessRgb
        mat[14] = brightnessRgb

        return ColorMatrix().apply {
            setToSaturation(0F)
            timesAssign(brightnessMatrix)
        }
    }
}

/** The resource id of communal hub accessible from UiAutomator. */
private const val COMMUNAL_HUB_TEST_TAG = "communal_hub"
+4 −4
Original line number Diff line number Diff line
@@ -71,8 +71,8 @@ internal constructor(

    /** Remove widget from the list and the database. */
    fun onRemove(indexToRemove: Int) {
        if (list[indexToRemove] is CommunalContentModel.Widget) {
            val widget = list[indexToRemove] as CommunalContentModel.Widget
        if (list[indexToRemove].isWidgetContent()) {
            val widget = list[indexToRemove] as CommunalContentModel.WidgetContent
            list.apply { removeAt(indexToRemove) }
            onDeleteWidget(widget.appWidgetId)
        }
@@ -100,7 +100,7 @@ internal constructor(
        val widgetIdToPriorityMap: Map<Int, Int> =
            list
                .mapIndexedNotNull { index, item ->
                    if (item is CommunalContentModel.Widget) {
                    if (item is CommunalContentModel.WidgetContent) {
                        item.appWidgetId to list.size - index
                    } else {
                        null
@@ -115,5 +115,5 @@ internal constructor(
    }

    /** Returns true if the item at given index is editable. */
    fun isItemEditable(index: Int) = list[index] is CommunalContentModel.Widget
    fun isItemEditable(index: Int) = list[index].isWidgetContent()
}
+102 −0
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.data.repository.CommunalSettingsRepositoryImpl
import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository
import com.android.systemui.communal.data.repository.FakeCommunalPrefsRepository
import com.android.systemui.communal.data.repository.FakeCommunalRepository
@@ -62,6 +63,7 @@ import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.user.data.repository.fakeUserRepository
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.settings.fakeSettings
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
@@ -831,6 +833,95 @@ class CommunalInteractorTest : SysuiTestCase() {
            }
        }

    @Test
    fun widgetContent_containsDisabledWidgets_whenCategoryNotAllowed() =
        testScope.runTest {
            // Communal available, and tutorial completed.
            keyguardRepository.setKeyguardShowing(true)
            keyguardRepository.setKeyguardOccluded(false)
            tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
            userRepository.setSelectedUserInfo(mainUser)

            val userInfos = listOf(MAIN_USER_INFO, USER_INFO_WORK)
            userRepository.setUserInfos(userInfos)
            userTracker.set(
                userInfos = userInfos,
                selectedUserIndex = 0,
            )
            runCurrent()

            // Widgets available.
            val widget1 =
                createWidgetWithCategory(1, AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN)
            val widget2 =
                createWidgetWithCategory(2, AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD)
            val widget3 =
                createWidgetWithCategory(3, AppWidgetProviderInfo.WIDGET_CATEGORY_SEARCHBOX)
            val widgets = listOf(widget1, widget2, widget3)
            widgetRepository.setCommunalWidgets(widgets)

            val widgetContent by collectLastValue(underTest.widgetContent)
            kosmos.fakeSettings.putIntForUser(
                CommunalSettingsRepositoryImpl.GLANCEABLE_HUB_CONTENT_SETTING,
                AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD,
                mainUser.id
            )
            runCurrent()

            // Only the keyguard widget is enabled.
            assertThat(widgetContent).hasSize(3)
            assertThat(widgetContent!!.get(0))
                .isInstanceOf(CommunalContentModel.WidgetContent.DisabledWidget::class.java)
            assertThat(widgetContent!!.get(1))
                .isInstanceOf(CommunalContentModel.WidgetContent.Widget::class.java)
            assertThat(widgetContent!!.get(2))
                .isInstanceOf(CommunalContentModel.WidgetContent.DisabledWidget::class.java)
        }

    @Test
    fun widgetContent_allEnabled_whenCategoryAllowed() =
        testScope.runTest {
            // Communal available, and tutorial completed.
            keyguardRepository.setKeyguardShowing(true)
            keyguardRepository.setKeyguardOccluded(false)
            tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
            userRepository.setSelectedUserInfo(mainUser)

            val userInfos = listOf(MAIN_USER_INFO, USER_INFO_WORK)
            userRepository.setUserInfos(userInfos)
            userTracker.set(
                userInfos = userInfos,
                selectedUserIndex = 0,
            )
            runCurrent()

            // Widgets available.
            val widget1 =
                createWidgetWithCategory(1, AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN)
            val widget2 =
                createWidgetWithCategory(2, AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD)
            val widget3 =
                createWidgetWithCategory(3, AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD)
            val widgets = listOf(widget1, widget2, widget3)
            widgetRepository.setCommunalWidgets(widgets)

            val widgetContent by collectLastValue(underTest.widgetContent)
            kosmos.fakeSettings.putIntForUser(
                CommunalSettingsRepositoryImpl.GLANCEABLE_HUB_CONTENT_SETTING,
                AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD or
                    AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN,
                mainUser.id
            )
            runCurrent()

            // All widgets are enabled.
            assertThat(widgetContent).hasSize(3)
            widgetContent!!.forEach { model ->
                assertThat(model)
                    .isInstanceOf(CommunalContentModel.WidgetContent.Widget::class.java)
            }
        }

    private fun smartspaceTimer(id: String, timestamp: Long = 0L): SmartspaceTarget {
        val timer = mock(SmartspaceTarget::class.java)
        whenever(timer.smartspaceTargetId).thenReturn(id)
@@ -848,6 +939,17 @@ class CommunalInteractorTest : SysuiTestCase() {
            whenever(this.providerInfo).thenReturn(providerInfo)
        }

    private fun createWidgetWithCategory(
        appWidgetId: Int,
        category: Int
    ): CommunalWidgetContentModel =
        mock<CommunalWidgetContentModel> {
            whenever(this.appWidgetId).thenReturn(appWidgetId)
            val providerInfo = mock<AppWidgetProviderInfo>().apply { widgetCategory = category }
            whenever(providerInfo.profile).thenReturn(UserHandle(MAIN_USER_INFO.id))
            whenever(this.providerInfo).thenReturn(providerInfo)
        }

    private companion object {
        val MAIN_USER_INFO = UserInfo(0, "primary", UserInfo.FLAG_MAIN)
        val USER_INFO_WORK = UserInfo(10, "work", UserInfo.FLAG_PROFILE)
+6 −5
Original line number Diff line number Diff line
@@ -135,9 +135,9 @@ class CommunalEditModeViewModelTest : SysuiTestCase() {
            // Only Widgets and CTA tile are shown.
            assertThat(communalContent?.size).isEqualTo(3)
            assertThat(communalContent?.get(0))
                .isInstanceOf(CommunalContentModel.Widget::class.java)
                .isInstanceOf(CommunalContentModel.WidgetContent::class.java)
            assertThat(communalContent?.get(1))
                .isInstanceOf(CommunalContentModel.Widget::class.java)
                .isInstanceOf(CommunalContentModel.WidgetContent::class.java)
            assertThat(communalContent?.get(2))
                .isInstanceOf(CommunalContentModel.CtaTileInEditMode::class.java)
        }
@@ -181,9 +181,9 @@ class CommunalEditModeViewModelTest : SysuiTestCase() {
            // Widgets and CTA tile are shown.
            assertThat(communalContent?.size).isEqualTo(3)
            assertThat(communalContent?.get(0))
                .isInstanceOf(CommunalContentModel.Widget::class.java)
                .isInstanceOf(CommunalContentModel.WidgetContent::class.java)
            assertThat(communalContent?.get(1))
                .isInstanceOf(CommunalContentModel.Widget::class.java)
                .isInstanceOf(CommunalContentModel.WidgetContent::class.java)
            assertThat(communalContent?.get(2))
                .isInstanceOf(CommunalContentModel.CtaTileInEditMode::class.java)

@@ -192,7 +192,8 @@ class CommunalEditModeViewModelTest : SysuiTestCase() {
            // Only one widget and CTA tile remain.
            assertThat(communalContent?.size).isEqualTo(2)
            val item = communalContent?.get(0)
            val appWidgetId = if (item is CommunalContentModel.Widget) item.appWidgetId else null
            val appWidgetId =
                if (item is CommunalContentModel.WidgetContent) item.appWidgetId else null
            assertThat(appWidgetId).isEqualTo(widgets.get(1).appWidgetId)
            assertThat(communalContent?.get(1))
                .isInstanceOf(CommunalContentModel.CtaTileInEditMode::class.java)
+2 −2
Original line number Diff line number Diff line
@@ -184,9 +184,9 @@ class CommunalViewModelTest : SysuiTestCase() {
                .isInstanceOf(CommunalContentModel.Smartspace::class.java)
            assertThat(communalContent?.get(1)).isInstanceOf(CommunalContentModel.Umo::class.java)
            assertThat(communalContent?.get(2))
                .isInstanceOf(CommunalContentModel.Widget::class.java)
                .isInstanceOf(CommunalContentModel.WidgetContent::class.java)
            assertThat(communalContent?.get(3))
                .isInstanceOf(CommunalContentModel.Widget::class.java)
                .isInstanceOf(CommunalContentModel.WidgetContent::class.java)
            assertThat(communalContent?.get(4))
                .isInstanceOf(CommunalContentModel.CtaTileInViewMode::class.java)
        }
Loading