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

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

Merge "Unpause work profile from GH" into main

parents 93f9d877 70d7d044
Loading
Loading
Loading
Loading
+25 −3
Original line number Diff line number Diff line
@@ -119,7 +119,7 @@ import com.android.systemui.communal.ui.compose.Dimensions.CardOutlineWidth
import com.android.systemui.communal.ui.compose.extensions.allowGestures
import com.android.systemui.communal.ui.compose.extensions.detectLongPressGesture
import com.android.systemui.communal.ui.compose.extensions.firstItemAtOffset
import com.android.systemui.communal.ui.compose.extensions.observeTapsWithoutConsuming
import com.android.systemui.communal.ui.compose.extensions.observeTaps
import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
@@ -168,7 +168,7 @@ fun CommunalHub(
                .pointerInput(gridState, contentOffset, contentListState) {
                    // If not in edit mode, don't allow selecting items.
                    if (!viewModel.isEditMode) return@pointerInput
                    observeTapsWithoutConsuming { offset ->
                    observeTaps { offset ->
                        val adjustedOffset = offset - contentOffset
                        val index = firstIndexAtOffset(gridState, adjustedOffset)
                        val key = index?.let { keyAtIndexIfEditable(contentListState.list, index) }
@@ -277,13 +277,26 @@ fun CommunalHub(
        if (viewModel is CommunalViewModel && dialogFactory != null) {
            val isEnableWidgetDialogShowing by
                viewModel.isEnableWidgetDialogShowing.collectAsState(false)
            val isEnableWorkProfileDialogShowing by
                viewModel.isEnableWorkProfileDialogShowing.collectAsState(false)

            EnableWidgetDialog(
                isEnableWidgetDialogVisible = isEnableWidgetDialogShowing,
                dialogFactory = dialogFactory,
                title = stringResource(id = R.string.dialog_title_to_allow_any_widget),
                positiveButtonText = stringResource(id = R.string.button_text_to_open_settings),
                onConfirm = viewModel::onEnableWidgetDialogConfirm,
                onCancel = viewModel::onEnableWidgetDialogCancel
            )

            EnableWidgetDialog(
                isEnableWidgetDialogVisible = isEnableWorkProfileDialogShowing,
                dialogFactory = dialogFactory,
                title = stringResource(id = R.string.work_mode_off_title),
                positiveButtonText = stringResource(id = R.string.work_mode_turn_on),
                onConfirm = viewModel::onEnableWorkProfileDialogConfirm,
                onCancel = viewModel::onEnableWorkProfileDialogCancel
            )
        }

        // This spacer covers the edge of the LazyHorizontalGrid and prevents it from receiving
@@ -769,7 +782,16 @@ private fun WidgetContent(
    modifier: Modifier = Modifier,
) {
    Box(
        modifier = modifier,
        modifier =
            modifier.thenIf(!viewModel.isEditMode && model.inQuietMode) {
                Modifier.pointerInput(Unit) {
                    // consume tap to prevent the child view from triggering interactions with the
                    // app widget
                    observeTaps(shouldConsume = true) { _ ->
                        viewModel.onOpenEnableWorkProfileDialog()
                    }
                }
            }
    ) {
        AndroidView(
            modifier = Modifier.fillMaxSize().allowGestures(allowed = !viewModel.isEditMode),
+94 −23
Original line number Diff line number Diff line
@@ -16,50 +16,62 @@

package com.android.systemui.communal.ui.compose

import android.app.Dialog
import android.content.DialogInterface
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
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.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import com.android.compose.theme.LocalAndroidColorScheme
import com.android.systemui.res.R
import com.android.systemui.statusbar.phone.ComponentSystemUIDialog
import com.android.systemui.statusbar.phone.SystemUIDialogFactory
import com.android.systemui.statusbar.phone.create

/**
 * Dialog shown upon tapping a disabled widget. It enables users to navigate to settings and modify
 * allowed widget categories.
 */
/** Dialog shown upon tapping a disabled widget which allows users to enable the widget. */
@Composable
fun EnableWidgetDialog(
    isEnableWidgetDialogVisible: Boolean,
    dialogFactory: SystemUIDialogFactory,
    title: String,
    positiveButtonText: String,
    onConfirm: () -> Unit,
    onCancel: () -> Unit
) {
    var dialog: Dialog? by remember { mutableStateOf(null) }
    var dialog: ComponentSystemUIDialog? 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()
                dialogFactory.create(
                    context = context,
                ) {
                    DialogComposable(title, positiveButtonText, onConfirm, onCancel)
                }
                    setCancelable(false)
            dialog?.apply {
                setCancelable(true)
                setCanceledOnTouchOutside(true)
                setOnCancelListener { onCancel() }
                show()
            }
        }
@@ -70,3 +82,62 @@ fun EnableWidgetDialog(
        }
    }
}

@Composable
private fun DialogComposable(
    title: String,
    positiveButtonText: String,
    onConfirm: () -> Unit,
    onCancel: () -> Unit,
) {
    Box(
        Modifier.fillMaxWidth()
            .padding(top = 18.dp, bottom = 8.dp)
            .background(LocalAndroidColorScheme.current.surfaceBright, RoundedCornerShape(28.dp))
    ) {
        Column(
            modifier = Modifier.fillMaxWidth(),
            verticalArrangement = Arrangement.spacedBy(20.dp),
        ) {
            Box(
                modifier = Modifier.padding(horizontal = 24.dp).fillMaxWidth().wrapContentHeight(),
                contentAlignment = Alignment.TopStart
            ) {
                Text(
                    text = title,
                    style = MaterialTheme.typography.titleMedium,
                    color = LocalAndroidColorScheme.current.onSurface,
                    textAlign = TextAlign.Center,
                    maxLines = 1,
                )
            }

            Box(
                modifier = Modifier.padding(end = 12.dp).fillMaxWidth().wrapContentHeight(),
                contentAlignment = Alignment.Center
            ) {
                Row(
                    modifier = Modifier.fillMaxWidth(),
                    horizontalArrangement = Arrangement.End,
                ) {
                    TextButton(
                        contentPadding = PaddingValues(16.dp),
                        onClick = onCancel,
                    ) {
                        Text(
                            text = stringResource(R.string.cancel),
                        )
                    }
                    TextButton(
                        contentPadding = PaddingValues(16.dp),
                        onClick = onConfirm,
                    ) {
                        Text(
                            text = positiveButtonText,
                        )
                    }
                }
            }
        }
    }
}
+5 −3
Original line number Diff line number Diff line
@@ -30,16 +30,18 @@ import androidx.compose.ui.util.fastForEach
import kotlinx.coroutines.coroutineScope

/**
 * Observe taps without actually consuming them, so child elements can still respond to them. Long
 * Observe taps without consuming them by default, so child elements can still respond to them. Long
 * presses are excluded.
 */
suspend fun PointerInputScope.observeTapsWithoutConsuming(
suspend fun PointerInputScope.observeTaps(
    pass: PointerEventPass = PointerEventPass.Initial,
    shouldConsume: Boolean = false,
    onTap: ((Offset) -> Unit)? = null,
) = coroutineScope {
    if (onTap == null) return@coroutineScope
    awaitEachGesture {
        awaitFirstDown(pass = pass)
        val down = awaitFirstDown(pass = pass)
        if (shouldConsume) down.consume()
        val tapTimeout = viewConfiguration.longPressTimeoutMillis
        val up = withTimeoutOrNull(tapTimeout) { waitForUpOrCancellation(pass = pass) }
        if (up != null) {
+69 −1
Original line number Diff line number Diff line
@@ -22,6 +22,8 @@ import android.appwidget.AppWidgetProviderInfo
import android.content.Intent
import android.content.pm.UserInfo
import android.os.UserHandle
import android.os.UserManager
import android.os.userManager
import android.provider.Settings
import android.provider.Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED
import android.widget.RemoteViews
@@ -65,6 +67,7 @@ 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.any
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.mock
@@ -79,6 +82,7 @@ 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.ArgumentMatchers.eq
import org.mockito.Mock
import org.mockito.Mockito.mock
@@ -111,6 +115,7 @@ class CommunalInteractorTest : SysuiTestCase() {
    private lateinit var sceneInteractor: SceneInteractor
    private lateinit var userTracker: FakeUserTracker
    private lateinit var activityStarter: ActivityStarter
    private lateinit var userManager: UserManager

    private lateinit var underTest: CommunalInteractor

@@ -130,9 +135,12 @@ class CommunalInteractorTest : SysuiTestCase() {
        sceneInteractor = kosmos.sceneInteractor
        userTracker = kosmos.fakeUserTracker
        activityStarter = kosmos.activityStarter
        userManager = kosmos.userManager

        whenever(mainUser.isMain).thenReturn(true)
        whenever(secondaryUser.isMain).thenReturn(false)
        whenever(userManager.isQuietModeEnabled(any<UserHandle>())).thenReturn(false)
        whenever(userManager.isManagedProfile(anyInt())).thenReturn(false)
        userRepository.setUserInfos(listOf(mainUser, secondaryUser))

        kosmos.fakeFeatureFlagsClassic.set(Flags.COMMUNAL_SERVICE_ENABLED, true)
@@ -850,6 +858,63 @@ class CommunalInteractorTest : SysuiTestCase() {
            }
        }

    @Test
    fun widgetContent_inQuietMode() =
        testScope.runTest {
            // Keyguard showing, and tutorial completed.
            keyguardRepository.setKeyguardShowing(true)
            keyguardRepository.setKeyguardOccluded(false)
            tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)

            // Work profile is set up.
            val userInfos = listOf(MAIN_USER_INFO, USER_INFO_WORK)
            userRepository.setUserInfos(userInfos)
            userTracker.set(
                userInfos = userInfos,
                selectedUserIndex = 0,
            )
            runCurrent()

            // Keyguard widgets are allowed.
            kosmos.fakeSettings.putIntForUser(
                CommunalSettingsRepositoryImpl.GLANCEABLE_HUB_CONTENT_SETTING,
                AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD,
                mainUser.id
            )
            runCurrent()

            // When work profile is paused.
            whenever(userManager.isQuietModeEnabled(eq(UserHandle.of(USER_INFO_WORK.id))))
                .thenReturn(true)
            whenever(userManager.isManagedProfile(eq(USER_INFO_WORK.id))).thenReturn(true)

            val widgetContent by collectLastValue(underTest.widgetContent)
            val widget1 = createWidgetForUser(1, USER_INFO_WORK.id)
            val widget2 = createWidgetForUser(2, MAIN_USER_INFO.id)
            val widget3 = createWidgetForUser(3, MAIN_USER_INFO.id)
            val widgets = listOf(widget1, widget2, widget3)
            widgetRepository.setCommunalWidgets(widgets)

            // The work profile widget is in quiet mode, while other widgets are not.
            assertThat(widgetContent).hasSize(3)
            widgetContent!!.forEach { model ->
                assertThat(model)
                    .isInstanceOf(CommunalContentModel.WidgetContent.Widget::class.java)
            }
            assertThat(
                    (widgetContent!![0] as CommunalContentModel.WidgetContent.Widget).inQuietMode
                )
                .isTrue()
            assertThat(
                    (widgetContent!![1] as CommunalContentModel.WidgetContent.Widget).inQuietMode
                )
                .isFalse()
            assertThat(
                    (widgetContent!![2] as CommunalContentModel.WidgetContent.Widget).inQuietMode
                )
                .isFalse()
        }

    @Test
    fun widgetContent_containsDisabledWidgets_whenCategoryNotAllowed() =
        testScope.runTest {
@@ -951,7 +1016,10 @@ class CommunalInteractorTest : SysuiTestCase() {
    private fun createWidgetForUser(appWidgetId: Int, userId: Int): CommunalWidgetContentModel =
        mock<CommunalWidgetContentModel> {
            whenever(this.appWidgetId).thenReturn(appWidgetId)
            val providerInfo = mock<AppWidgetProviderInfo>()
            val providerInfo =
                mock<AppWidgetProviderInfo>().apply {
                    widgetCategory = AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD
                }
            whenever(providerInfo.profile).thenReturn(UserHandle(userId))
            whenever(this.providerInfo).thenReturn(providerInfo)
        }
+4 −0
Original line number Diff line number Diff line
@@ -1139,6 +1139,10 @@
    <string name="dialog_title_to_allow_any_widget">Allow any widget on lock screen?</string>
    <!-- Text for the button in the dialog that opens when tapping on disabled widgets. [CHAR LIMIT=NONE] -->
    <string name="button_text_to_open_settings">Open settings</string>
    <!-- Title of a dialog. This text is confirming that the user wants to turn on access to their work apps, which the user had previously paused. "Work" is an adjective. [CHAR LIMIT=30] -->
    <string name="work_mode_off_title">Unpause work apps?</string>
    <!-- Title for button to unpause on work profile. [CHAR LIMIT=NONE] -->
    <string name="work_mode_turn_on">Unpause</string>

    <!-- Related to user switcher --><skip/>

Loading