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

Commit d66833e3 authored by Coco Duan's avatar Coco Duan
Browse files

Show a dialog after clicking on disabled widgets

When clicking on a disabled non-keyguard widget, show a dialog that
enables users to open settings and update whether to allow any widget
in hub mode.

Bug: b/324455650
Test: atest CommunalInteractorTest
Test: tap on disabled non-keyguard widgets and the dialog shows
Flag: ACONFIG com.android.systemui.communal_hub STAGING
Change-Id: I3a21193e703468613e21061bd2e1c8c5bbc3e7c7
parent 0acf75e1
Loading
Loading
Loading
Loading
+7 −2
Original line number Diff line number Diff line
@@ -28,6 +28,7 @@ import com.android.systemui.communal.ui.compose.extensions.allowGestures
import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.res.R
import com.android.systemui.statusbar.phone.SystemUIDialogFactory

object Communal {
    object Elements {
@@ -63,6 +64,7 @@ val sceneTransitions = transitions {
fun CommunalContainer(
    modifier: Modifier = Modifier,
    viewModel: CommunalViewModel,
    dialogFactory: SystemUIDialogFactory,
) {
    val currentScene: SceneKey by viewModel.currentScene.collectAsState(CommunalScenes.Blank)
    val sceneTransitionLayoutState =
@@ -104,7 +106,7 @@ fun CommunalContainer(
            userActions =
                mapOf(Swipe(SwipeDirection.Right, fromSource = Edge.Left) to CommunalScenes.Blank),
        ) {
            CommunalScene(viewModel, modifier = modifier)
            CommunalScene(viewModel, dialogFactory, modifier = modifier)
        }
    }
}
@@ -113,6 +115,7 @@ fun CommunalContainer(
@Composable
private fun SceneScope.CommunalScene(
    viewModel: BaseCommunalViewModel,
    dialogFactory: SystemUIDialogFactory,
    modifier: Modifier = Modifier,
) {
    Box(
@@ -121,5 +124,7 @@ private fun SceneScope.CommunalScene(
                .fillMaxSize()
                .background(LocalAndroidColorScheme.current.outlineVariant),
    )
    Box(modifier.element(Communal.Elements.Content)) { CommunalHub(viewModel = viewModel) }
    Box(modifier.element(Communal.Elements.Content)) {
        CommunalHub(viewModel = viewModel, dialogFactory = dialogFactory)
    }
}
+29 −5
Original line number Diff line number Diff line
@@ -29,6 +29,7 @@ import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
@@ -118,8 +119,10 @@ 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.ui.viewmodel.CommunalViewModel
import com.android.systemui.communal.widgets.WidgetConfigurator
import com.android.systemui.res.R
import com.android.systemui.statusbar.phone.SystemUIDialogFactory
import kotlinx.coroutines.launch

@OptIn(ExperimentalComposeUiApi::class)
@@ -127,6 +130,7 @@ import kotlinx.coroutines.launch
fun CommunalHub(
    modifier: Modifier = Modifier,
    viewModel: BaseCommunalViewModel,
    dialogFactory: SystemUIDialogFactory? = null,
    widgetConfigurator: WidgetConfigurator? = null,
    onOpenWidgetPicker: (() -> Unit)? = null,
    onEditDone: (() -> Unit)? = null,
@@ -254,6 +258,18 @@ fun CommunalHub(
            )
        }

        if (viewModel is CommunalViewModel && dialogFactory != null) {
            val isEnableWidgetDialogShowing by
                viewModel.isEnableWidgetDialogShowing.collectAsState(false)

            EnableWidgetDialog(
                isEnableWidgetDialogVisible = isEnableWidgetDialogShowing,
                dialogFactory = dialogFactory,
                onConfirm = viewModel::onEnableWidgetDialogConfirm,
                onCancel = viewModel::onEnableWidgetDialogCancel
            )
        }

        // This spacer covers the edge of the LazyHorizontalGrid and prevents it from receiving
        // touches, so that the SceneTransitionLayout can intercept the touches and allow an edge
        // swipe back to the blank scene.
@@ -551,7 +567,7 @@ private fun CommunalContent(
            WidgetContent(viewModel, model, size, selected, widgetConfigurator, modifier)
        is CommunalContentModel.WidgetPlaceholder -> HighlightedItem(modifier)
        is CommunalContentModel.WidgetContent.DisabledWidget ->
            DisabledWidgetPlaceholder(model, modifier)
            DisabledWidgetPlaceholder(model, viewModel, modifier)
        is CommunalContentModel.CtaTileInViewMode -> CtaTileInViewModeContent(viewModel, modifier)
        is CommunalContentModel.CtaTileInEditMode ->
            CtaTileInEditModeContent(modifier, onOpenWidgetPicker)
@@ -765,6 +781,7 @@ fun WidgetConfigureButton(
@Composable
fun DisabledWidgetPlaceholder(
    model: CommunalContentModel.WidgetContent.DisabledWidget,
    viewModel: BaseCommunalViewModel,
    modifier: Modifier = Modifier,
) {
    val context = LocalContext.current
@@ -778,9 +795,16 @@ fun DisabledWidgetPlaceholder(

    Column(
        modifier =
            modifier.background(
            modifier
                .background(
                    MaterialTheme.colorScheme.surfaceVariant,
                    RoundedCornerShape(dimensionResource(system_app_widget_background_radius))
                )
                .clickable(
                    enabled = !viewModel.isEditMode,
                    interactionSource = null,
                    indication = null,
                    onClick = viewModel::onOpenEnableWidgetDialog
                ),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally,
+3 −1
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@ import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.ui.composable.ComposableScene
import com.android.systemui.statusbar.phone.SystemUIDialogFactory
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@@ -38,6 +39,7 @@ class CommunalScene
@Inject
constructor(
    private val viewModel: CommunalViewModel,
    private val dialogFactory: SystemUIDialogFactory,
) : ComposableScene {
    override val key = Scenes.Communal

@@ -51,6 +53,6 @@ constructor(

    @Composable
    override fun SceneScope.Content(modifier: Modifier) {
        CommunalHub(modifier, viewModel)
        CommunalHub(modifier, viewModel, dialogFactory)
    }
}
+72 −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.ui.compose

import android.app.Dialog
import android.content.DialogInterface
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalView
import com.android.systemui.res.R
import com.android.systemui.statusbar.phone.SystemUIDialogFactory

/**
 * Dialog shown upon tapping a disabled widget. It enables users to navigate to settings and modify
 * allowed widget categories.
 */
@Composable
fun EnableWidgetDialog(
    isEnableWidgetDialogVisible: Boolean,
    dialogFactory: SystemUIDialogFactory,
    onConfirm: () -> Unit,
    onCancel: () -> Unit
) {
    var dialog: Dialog? by remember { mutableStateOf(null) }
    val context = LocalView.current.context

    DisposableEffect(isEnableWidgetDialogVisible) {
        if (isEnableWidgetDialogVisible) {
            dialog =
                dialogFactory.create(context = context).apply {
                    setTitle(context.getString(R.string.dialog_title_to_allow_any_widget))
                    setButton(
                        DialogInterface.BUTTON_NEGATIVE,
                        context.getString(R.string.cancel)
                    ) { _, _ ->
                        onCancel()
                    }
                    setButton(
                        DialogInterface.BUTTON_POSITIVE,
                        context.getString(R.string.button_text_to_open_settings)
                    ) { _, _ ->
                        onConfirm()
                    }
                    setCancelable(false)
                    show()
                }
        }

        onDispose {
            dialog?.dismiss()
            dialog = null
        }
    }
}
+19 −0
Original line number Diff line number Diff line
@@ -19,8 +19,10 @@ package com.android.systemui.communal.domain.interactor

import android.app.smartspace.SmartspaceTarget
import android.appwidget.AppWidgetProviderInfo
import android.content.Intent
import android.content.pm.UserInfo
import android.os.UserHandle
import android.provider.Settings
import android.provider.Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED
import android.widget.RemoteViews
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -50,6 +52,8 @@ import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.activityStarter
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
@@ -61,6 +65,8 @@ import com.android.systemui.smartspace.data.repository.fakeSmartspaceRepository
import com.android.systemui.testKosmos
import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.user.data.repository.fakeUserRepository
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.settings.fakeSettings
@@ -73,6 +79,7 @@ import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.eq
import org.mockito.Mock
import org.mockito.Mockito.mock
import org.mockito.Mockito.verify
@@ -103,6 +110,7 @@ class CommunalInteractorTest : SysuiTestCase() {
    private lateinit var editWidgetsActivityStarter: EditWidgetsActivityStarter
    private lateinit var sceneInteractor: SceneInteractor
    private lateinit var userTracker: FakeUserTracker
    private lateinit var activityStarter: ActivityStarter

    private lateinit var underTest: CommunalInteractor

@@ -121,6 +129,7 @@ class CommunalInteractorTest : SysuiTestCase() {
        communalPrefsRepository = kosmos.fakeCommunalPrefsRepository
        sceneInteractor = kosmos.sceneInteractor
        userTracker = kosmos.fakeUserTracker
        activityStarter = kosmos.activityStarter

        whenever(mainUser.isMain).thenReturn(true)
        whenever(secondaryUser.isMain).thenReturn(false)
@@ -799,6 +808,16 @@ class CommunalInteractorTest : SysuiTestCase() {
            verify(editWidgetsActivityStarter).startActivity(widgetKey)
        }

    @Test
    fun navigateToCommunalWidgetSettings_startsActivity() =
        testScope.runTest {
            underTest.navigateToCommunalWidgetSettings()
            val intentCaptor = argumentCaptor<Intent>()
            verify(activityStarter)
                .postStartActivityDismissingKeyguard(capture(intentCaptor), eq(0))
            assertThat(intentCaptor.value.action).isEqualTo(Settings.ACTION_COMMUNAL_SETTING)
        }

    @Test
    fun filterWidgets_whenUserProfileRemoved() =
        testScope.runTest {
Loading