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

Commit 20f797a8 authored by Coco Duan's avatar Coco Duan
Browse files

Filter out work profile widgets when not allowed by device policy

Existing work profile widgets should be filtered out if keyguard widgets
are disabled on the work profile but not on the main user by device policy manager.

Bug: b/323196422
Test: atest CommunalInteractorTest
Test: atest CommunalSettingsRepositoryImplTest
Test: verify by toggling the setting in TestDPC
Flag: ACONFIG com.android.systemui.communal_hub TEAMFOOD
Change-Id: I0ac2d766e4f1942a99f888f1ba8aff43cd700432
parent 9cc7ea55
Loading
Loading
Loading
Loading
+34 −0
Original line number Diff line number Diff line
@@ -23,6 +23,7 @@ import android.app.admin.devicePolicyManager
import android.appwidget.AppWidgetProviderInfo
import android.content.Intent
import android.content.pm.UserInfo
import android.os.UserManager.USER_TYPE_PROFILE_MANAGED
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.provider.Settings
@@ -59,6 +60,7 @@ class CommunalSettingsRepositoryImplTest : SysuiTestCase() {
        kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true)
        setKeyguardFeaturesDisabled(PRIMARY_USER, KEYGUARD_DISABLE_FEATURES_NONE)
        setKeyguardFeaturesDisabled(SECONDARY_USER, KEYGUARD_DISABLE_FEATURES_NONE)
        setKeyguardFeaturesDisabled(WORK_PROFILE, KEYGUARD_DISABLE_FEATURES_NONE)
        underTest = kosmos.communalSettingsRepository
    }

@@ -131,6 +133,30 @@ class CommunalSettingsRepositoryImplTest : SysuiTestCase() {
            assertThat(enabledState).containsExactly(DisabledReason.DISABLED_REASON_DEVICE_POLICY)
        }

    @EnableFlags(FLAG_COMMUNAL_HUB)
    @Test
    fun widgetsAllowedForWorkProfile_isFalse_whenDisallowedByDevicePolicy() =
        testScope.runTest {
            val widgetsAllowedForWorkProfile by
                collectLastValue(underTest.getAllowedByDevicePolicy(WORK_PROFILE))
            assertThat(widgetsAllowedForWorkProfile).isTrue()

            setKeyguardFeaturesDisabled(WORK_PROFILE, KEYGUARD_DISABLE_WIDGETS_ALL)
            assertThat(widgetsAllowedForWorkProfile).isFalse()
        }

    @EnableFlags(FLAG_COMMUNAL_HUB)
    @Test
    fun hubIsEnabled_whenDisallowedByDevicePolicyForWorkProfile() =
        testScope.runTest {
            val enabledStateForPrimaryUser by
                collectLastValue(underTest.getEnabledState(PRIMARY_USER))
            assertThat(enabledStateForPrimaryUser?.enabled).isTrue()

            setKeyguardFeaturesDisabled(WORK_PROFILE, KEYGUARD_DISABLE_WIDGETS_ALL)
            assertThat(enabledStateForPrimaryUser?.enabled).isTrue()
        }

    @EnableFlags(FLAG_COMMUNAL_HUB)
    @Test
    fun hubIsDisabledByUserAndDevicePolicy() =
@@ -189,5 +215,13 @@ class CommunalSettingsRepositoryImplTest : SysuiTestCase() {
        val PRIMARY_USER =
            UserInfo(/* id= */ 0, /* name= */ "primary user", /* flags= */ UserInfo.FLAG_MAIN)
        val SECONDARY_USER = UserInfo(/* id= */ 1, /* name= */ "secondary user", /* flags= */ 0)
        val WORK_PROFILE =
            UserInfo(
                10,
                "work",
                /* iconPath= */ "",
                /* flags= */ 0,
                USER_TYPE_PROFILE_MANAGED,
            )
    }
}
+96 −5
Original line number Diff line number Diff line
@@ -17,6 +17,8 @@

package com.android.systemui.communal.domain.interactor

import android.app.admin.DevicePolicyManager
import android.app.admin.devicePolicyManager
import android.app.smartspace.SmartspaceTarget
import android.appwidget.AppWidgetProviderInfo
import android.content.Intent
@@ -32,6 +34,7 @@ import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.broadcastDispatcher
import com.android.systemui.communal.data.repository.CommunalSettingsRepositoryImpl
import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository
import com.android.systemui.communal.data.repository.FakeCommunalPrefsRepository
@@ -71,6 +74,7 @@ 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
import com.android.systemui.util.mockito.nullable
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.settings.fakeSettings
import com.google.common.truth.Truth.assertThat
@@ -929,7 +933,6 @@ class CommunalInteractorTest : SysuiTestCase() {
            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)
@@ -937,6 +940,7 @@ class CommunalInteractorTest : SysuiTestCase() {
                userInfos = userInfos,
                selectedUserIndex = 0,
            )
            userRepository.setSelectedUserInfo(MAIN_USER_INFO)
            runCurrent()

            // Widgets available.
@@ -955,7 +959,6 @@ class CommunalInteractorTest : SysuiTestCase() {
                AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD,
                mainUser.id
            )
            runCurrent()

            // Only the keyguard widget is enabled.
            assertThat(widgetContent).hasSize(3)
@@ -974,7 +977,6 @@ class CommunalInteractorTest : SysuiTestCase() {
            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)
@@ -982,6 +984,7 @@ class CommunalInteractorTest : SysuiTestCase() {
                userInfos = userInfos,
                selectedUserIndex = 0,
            )
            userRepository.setSelectedUserInfo(MAIN_USER_INFO)
            runCurrent()

            // Widgets available.
@@ -1001,7 +1004,6 @@ class CommunalInteractorTest : SysuiTestCase() {
                    AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN,
                mainUser.id
            )
            runCurrent()

            // All widgets are enabled.
            assertThat(widgetContent).hasSize(3)
@@ -1011,6 +1013,79 @@ class CommunalInteractorTest : SysuiTestCase() {
            }
        }

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

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

            val widgetContent by collectLastValue(underTest.widgetContent)
            // Given three widgets, and one of them is associated with work profile.
            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)

            setKeyguardFeaturesDisabled(
                USER_INFO_WORK,
                DevicePolicyManager.KEYGUARD_DISABLE_WIDGETS_ALL
            )

            // Widget under work profile is filtered out and the remaining two link to main user id.
            assertThat(widgetContent).hasSize(2)
            widgetContent!!.forEach { model ->
                assertThat(model.providerInfo.profile?.identifier).isEqualTo(MAIN_USER_INFO.id)
            }
        }

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

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

            val widgetContent by collectLastValue(underTest.widgetContent)
            // Given three widgets, and one of them is associated with work profile.
            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)

            setKeyguardFeaturesDisabled(
                USER_INFO_WORK,
                DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_NONE
            )

            // Widget under work profile is available.
            assertThat(widgetContent).hasSize(3)
            assertThat(widgetContent!![0].providerInfo.profile?.identifier)
                .isEqualTo(USER_INFO_WORK.id)
        }

    private fun smartspaceTimer(id: String, timestamp: Long = 0L): SmartspaceTarget {
        val timer = mock(SmartspaceTarget::class.java)
        whenever(timer.smartspaceTargetId).thenReturn(id)
@@ -1020,6 +1095,15 @@ class CommunalInteractorTest : SysuiTestCase() {
        return timer
    }

    private fun setKeyguardFeaturesDisabled(user: UserInfo, disabledFlags: Int) {
        whenever(kosmos.devicePolicyManager.getKeyguardDisabledFeatures(nullable(), eq(user.id)))
            .thenReturn(disabledFlags)
        kosmos.broadcastDispatcher.sendIntentToMatchingReceiversOnly(
            context,
            Intent(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED),
        )
    }

    private fun createWidgetForUser(appWidgetId: Int, userId: Int): CommunalWidgetContentModel =
        mock<CommunalWidgetContentModel> {
            whenever(this.appWidgetId).thenReturn(appWidgetId)
@@ -1044,6 +1128,13 @@ class CommunalInteractorTest : SysuiTestCase() {

    private companion object {
        val MAIN_USER_INFO = UserInfo(0, "primary", UserInfo.FLAG_MAIN)
        val USER_INFO_WORK = UserInfo(10, "work", UserInfo.FLAG_PROFILE)
        val USER_INFO_WORK =
            UserInfo(
                10,
                "work",
                /* iconPath= */ "",
                /* flags= */ 0,
                UserManager.USER_TYPE_PROFILE_MANAGED,
            )
    }
}
+13 −10
Original line number Diff line number Diff line
@@ -57,6 +57,9 @@ interface CommunalSettingsRepository {
     * Settings.
     */
    fun getWidgetCategories(user: UserInfo): Flow<CommunalWidgetCategories>

    /** Keyguard widgets enabled state by Device Policy Manager for the specified user. */
    fun getAllowedByDevicePolicy(user: UserInfo): Flow<Boolean>
}

@SysUISingleton
@@ -115,6 +118,16 @@ constructor(
            }
            .flowOn(bgDispatcher)

    override fun getAllowedByDevicePolicy(user: UserInfo): Flow<Boolean> =
        broadcastDispatcher
            .broadcastFlow(
                filter =
                    IntentFilter(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED),
                user = user.userHandle
            )
            .emitOnStart()
            .map { devicePolicyManager.areKeyguardWidgetsAllowed(user.id) }

    private fun getEnabledByUser(user: UserInfo): Flow<Boolean> =
        secureSettings
            .observerFlow(userId = user.id, names = arrayOf(Settings.Secure.GLANCEABLE_HUB_ENABLED))
@@ -128,16 +141,6 @@ constructor(
                ) == 1
            }

    private fun getAllowedByDevicePolicy(user: UserInfo): Flow<Boolean> =
        broadcastDispatcher
            .broadcastFlow(
                filter =
                    IntentFilter(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED),
                user = user.userHandle
            )
            .emitOnStart()
            .map { devicePolicyManager.areKeyguardWidgetsAllowed(user.id) }

    companion object {
        const val GLANCEABLE_HUB_CONTENT_SETTING = "glanceable_hub_content_setting"
        private const val ENABLED_SETTING_DEFAULT = 1
+22 −2
Original line number Diff line number Diff line
@@ -99,7 +99,7 @@ constructor(
    mediaRepository: CommunalMediaRepository,
    smartspaceRepository: SmartspaceRepository,
    keyguardInteractor: KeyguardInteractor,
    communalSettingsInteractor: CommunalSettingsInteractor,
    private val communalSettingsInteractor: CommunalSettingsInteractor,
    private val appWidgetHost: CommunalAppWidgetHost,
    private val editWidgetsActivityStarter: EditWidgetsActivityStarter,
    private val userTracker: UserTracker,
@@ -358,7 +358,14 @@ constructor(
    /** A list of widget content to be displayed in the communal hub. */
    val widgetContent: Flow<List<WidgetContent>> =
        combine(
            widgetRepository.communalWidgets.map { filterWidgetsByExistingUsers(it) },
            widgetRepository.communalWidgets
                .map { filterWidgetsByExistingUsers(it) }
                .combine(communalSettingsInteractor.allowedByDevicePolicyForWorkProfile) {
                    // exclude widgets under work profile if not allowed by device policy
                    widgets,
                    allowedForWorkProfile ->
                    filterWidgetsAllowedByDevicePolicy(widgets, allowedForWorkProfile)
                },
            communalSettingsInteractor.communalWidgetCategories,
            updateOnWorkProfileBroadcastReceived,
        ) { widgets, allowedCategories, _ ->
@@ -380,6 +387,19 @@ constructor(
            }
        }

    /** Filter widgets based on whether their associated profile is allowed by device policy. */
    private fun filterWidgetsAllowedByDevicePolicy(
        list: List<CommunalWidgetContentModel>,
        allowedByDevicePolicyForWorkProfile: Boolean
    ): List<CommunalWidgetContentModel> =
        if (allowedByDevicePolicyForWorkProfile) {
            list
        } else {
            // Get associated work profile for the currently selected user.
            val workProfile = userTracker.userProfiles.find { it.isManagedProfile }
            list.filter { it.providerInfo.profile.identifier != workProfile?.id }
        }

    /** A flow of available smartspace targets. Currently only showing timers. */
    private val smartspaceTargets: Flow<List<SmartspaceTarget>> =
        if (!smartspaceRepository.isSmartspaceRemoteViewsEnabled) {
+38 −0
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@

package com.android.systemui.communal.domain.interactor

import android.content.pm.UserInfo
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.communal.data.model.CommunalEnabledState
import com.android.systemui.communal.data.model.CommunalWidgetCategories
import com.android.systemui.communal.data.repository.CommunalSettingsRepository
@@ -24,13 +26,18 @@ import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.log.dagger.CommunalTableLog
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.log.table.logDiffsForTable
import com.android.systemui.settings.UserTracker
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import java.util.concurrent.Executor
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn

@@ -40,8 +47,10 @@ class CommunalSettingsInteractor
@Inject
constructor(
    @Background private val bgScope: CoroutineScope,
    @Background private val bgExecutor: Executor,
    private val repository: CommunalSettingsRepository,
    userInteractor: SelectedUserInteractor,
    private val userTracker: UserTracker,
    @CommunalTableLog tableLogBuffer: TableLogBuffer,
) {
    /** Whether or not communal is enabled for the currently selected user. */
@@ -68,4 +77,33 @@ constructor(
                started = SharingStarted.Eagerly,
                initialValue = CommunalWidgetCategories().categories
            )

    private val workProfileUserInfoCallbackFlow: Flow<UserInfo?> = conflatedCallbackFlow {
        fun send(profiles: List<UserInfo>) {
            trySend(profiles.find { it.isManagedProfile })
        }

        val callback =
            object : UserTracker.Callback {
                override fun onProfilesChanged(profiles: List<UserInfo>) {
                    send(profiles)
                }
            }
        userTracker.addCallback(callback, bgExecutor)
        send(userTracker.userProfiles)

        awaitClose { userTracker.removeCallback(callback) }
    }

    /** Whether or not keyguard widgets are allowed for work profile by device policy manager. */
    val allowedByDevicePolicyForWorkProfile: StateFlow<Boolean> =
        workProfileUserInfoCallbackFlow
            .flatMapLatest { workProfile ->
                workProfile?.let { repository.getAllowedByDevicePolicy(it) } ?: flowOf(false)
            }
            .stateIn(
                scope = bgScope,
                started = SharingStarted.WhileSubscribed(),
                initialValue = false
            )
}
Loading