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

Commit 17cbe981 authored by Darrell Shi's avatar Darrell Shi
Browse files

Hide communal hub before strong auth granted

Test: atest KeyguardRepositoryImplTest
Test: atest CommunalWidgetRepositoryImplTest
Test: atest CommunalInteractorTest
Test: atest CommunalInteractorCommunalDisabledTest
Test: verified on device that communal hub is hidden after reboot
Bug: 317275638
Fix: 317275638
Flag: ACONFIG com.android.systemui.communal_hub DEVELOPMENT
Change-Id: I3d2c6670252d0ea081acf81f3ce0db6c7a1e8763
parent fbc19564
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -67,8 +67,9 @@ fun CommunalContainer(
    // Don't show hub mode UI if keyguard is not present. This is important since we're in the
    // shade, which can be opened from many locations.
    val isKeyguardShowing by viewModel.isKeyguardVisible.collectAsState(initial = false)
    val isCommunalAvailable by viewModel.isCommunalAvailable.collectAsState()

    if (!isKeyguardShowing) {
    if (!isKeyguardShowing || !isCommunalAvailable) {
        return
    }

+37 −139
Original line number Diff line number Diff line
@@ -19,10 +19,6 @@ package com.android.systemui.communal.data.repository
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProviderInfo
import android.content.ComponentName
import android.content.Intent
import android.content.Intent.ACTION_USER_UNLOCKED
import android.os.UserHandle
import android.os.UserManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -38,7 +34,6 @@ import com.android.systemui.kosmos.testScope
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.res.R
import com.android.systemui.settings.UserTracker
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.whenever
@@ -68,12 +63,6 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {

    @Mock private lateinit var appWidgetHost: CommunalAppWidgetHost

    @Mock private lateinit var userManager: UserManager

    @Mock private lateinit var userHandle: UserHandle

    @Mock private lateinit var userTracker: UserTracker

    @Mock private lateinit var stopwatchProviderInfo: AppWidgetProviderInfo

    @Mock private lateinit var providerInfoA: AppWidgetProviderInfo
@@ -86,8 +75,6 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {

    private val testScope = kosmos.testScope

    private lateinit var communalRepository: FakeCommunalRepository

    private lateinit var logBuffer: LogBuffer

    private val fakeAllowlist =
@@ -97,68 +84,60 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
            "com.android.fake/WidgetProviderC",
        )

    private lateinit var underTest: CommunalWidgetRepositoryImpl

    @Before
    fun setUp() {
        MockitoAnnotations.initMocks(this)

        logBuffer = logcatLogBuffer(name = "CommunalWidgetRepoImplTest")
        communalRepository = kosmos.fakeCommunalRepository

        communalEnabled(true)
        setAppWidgetIds(emptyList())

        overrideResource(R.array.config_communalWidgetAllowlist, fakeAllowlist.toTypedArray())

        whenever(stopwatchProviderInfo.loadLabel(any())).thenReturn("Stopwatch")
        whenever(userTracker.userHandle).thenReturn(userHandle)
        whenever(communalWidgetDao.getWidgets()).thenReturn(flowOf(emptyMap()))
        whenever(appWidgetManagerOptional.isPresent).thenReturn(true)
        whenever(appWidgetManagerOptional.get()).thenReturn(appWidgetManager)
    }

    @Test
    fun neverQueryDbForWidgets_whenFeatureIsDisabled() =
        testScope.runTest {
            communalEnabled(false)
            val repository = initCommunalWidgetRepository()
            repository.communalWidgets.launchIn(backgroundScope)
            runCurrent()

            verify(communalWidgetDao, never()).getWidgets()
        underTest =
            CommunalWidgetRepositoryImpl(
                appWidgetManagerOptional,
                appWidgetHost,
                testScope.backgroundScope,
                kosmos.testDispatcher,
                communalWidgetHost,
                communalWidgetDao,
                logBuffer,
            )
    }

    @Test
    fun neverQueryDbForWidgets_whenFeatureEnabled_andUserLocked() =
    fun neverQueryDbForWidgets_whenHostIsInactive() =
        testScope.runTest {
            userUnlocked(false)
            val repository = initCommunalWidgetRepository()
            repository.communalWidgets.launchIn(backgroundScope)
            underTest.updateAppWidgetHostActive(false)
            underTest.communalWidgets.launchIn(testScope.backgroundScope)
            runCurrent()

            verify(communalWidgetDao, never()).getWidgets()
        }

    @Test
    fun communalWidgets_whenUserUnlocked_queryWidgetsFromDb() =
    fun communalWidgets_whenHostIsActive_queryWidgetsFromDb() =
        testScope.runTest {
            userUnlocked(false)
            val repository = initCommunalWidgetRepository()
            val communalWidgets by collectLastValue(repository.communalWidgets)
            runCurrent()
            underTest.updateAppWidgetHostActive(true)

            val communalItemRankEntry = CommunalItemRank(uid = 1L, rank = 1)
            val communalWidgetItemEntry = CommunalWidgetItem(uid = 1L, 1, "pk_name/cls_name", 1L)
            whenever(communalWidgetDao.getWidgets())
                .thenReturn(flowOf(mapOf(communalItemRankEntry to communalWidgetItemEntry)))
            whenever(appWidgetManager.getAppWidgetInfo(anyInt())).thenReturn(providerInfoA)

            userUnlocked(true)
            installedProviders(listOf(stopwatchProviderInfo))
            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
                context,
                Intent(ACTION_USER_UNLOCKED)
            )
            runCurrent()

            val communalWidgets by collectLastValue(underTest.communalWidgets)
            runCurrent()
            verify(communalWidgetDao).getWidgets()
            assertThat(communalWidgets)
                .containsExactly(
@@ -173,9 +152,7 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
    @Test
    fun addWidget_allocateId_bindWidget_andAddToDb() =
        testScope.runTest {
            userUnlocked(true)
            val repository = initCommunalWidgetRepository()
            runCurrent()
            underTest.updateAppWidgetHostActive(true)

            val provider = ComponentName("pkg_name", "cls_name")
            val id = 1
@@ -183,7 +160,7 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
            whenever(communalWidgetHost.requiresConfiguration(id)).thenReturn(true)
            whenever(communalWidgetHost.allocateIdAndBindWidget(any<ComponentName>()))
                .thenReturn(id)
            repository.addWidget(provider, priority) { true }
            underTest.addWidget(provider, priority) { true }
            runCurrent()

            verify(communalWidgetHost).allocateIdAndBindWidget(provider)
@@ -193,16 +170,14 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
    @Test
    fun addWidget_configurationFails_doNotAddWidgetToDb() =
        testScope.runTest {
            userUnlocked(true)
            val repository = initCommunalWidgetRepository()
            runCurrent()
            underTest.updateAppWidgetHostActive(true)

            val provider = ComponentName("pkg_name", "cls_name")
            val id = 1
            val priority = 1
            whenever(communalWidgetHost.requiresConfiguration(id)).thenReturn(true)
            whenever(communalWidgetHost.allocateIdAndBindWidget(provider)).thenReturn(id)
            repository.addWidget(provider, priority) { false }
            underTest.addWidget(provider, priority) { false }
            runCurrent()

            verify(communalWidgetHost).allocateIdAndBindWidget(provider)
@@ -213,16 +188,14 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
    @Test
    fun addWidget_configurationThrowsError_doNotAddWidgetToDb() =
        testScope.runTest {
            userUnlocked(true)
            val repository = initCommunalWidgetRepository()
            runCurrent()
            underTest.updateAppWidgetHostActive(true)

            val provider = ComponentName("pkg_name", "cls_name")
            val id = 1
            val priority = 1
            whenever(communalWidgetHost.requiresConfiguration(id)).thenReturn(true)
            whenever(communalWidgetHost.allocateIdAndBindWidget(provider)).thenReturn(id)
            repository.addWidget(provider, priority) { throw IllegalStateException("some error") }
            underTest.addWidget(provider, priority) { throw IllegalStateException("some error") }
            runCurrent()

            verify(communalWidgetHost).allocateIdAndBindWidget(provider)
@@ -233,9 +206,7 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
    @Test
    fun addWidget_configurationNotRequired_doesNotConfigure_addWidgetToDb() =
        testScope.runTest {
            userUnlocked(true)
            val repository = initCommunalWidgetRepository()
            runCurrent()
            underTest.updateAppWidgetHostActive(true)

            val provider = ComponentName("pkg_name", "cls_name")
            val id = 1
@@ -244,7 +215,7 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
            whenever(communalWidgetHost.allocateIdAndBindWidget(any<ComponentName>()))
                .thenReturn(id)
            var configured = false
            repository.addWidget(provider, priority) {
            underTest.addWidget(provider, priority) {
                configured = true
                true
            }
@@ -258,12 +229,10 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
    @Test
    fun deleteWidget_removeWidgetId_andDeleteFromDb() =
        testScope.runTest {
            userUnlocked(true)
            val repository = initCommunalWidgetRepository()
            runCurrent()
            underTest.updateAppWidgetHostActive(true)

            val id = 1
            repository.deleteWidget(id)
            underTest.deleteWidget(id)
            runCurrent()

            verify(communalWidgetDao).deleteWidgetById(id)
@@ -273,108 +242,37 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
    @Test
    fun reorderWidgets_queryDb() =
        testScope.runTest {
            userUnlocked(true)
            val repository = initCommunalWidgetRepository()
            runCurrent()
            underTest.updateAppWidgetHostActive(true)

            val widgetIdToPriorityMap = mapOf(104 to 1, 103 to 2, 101 to 3)
            repository.updateWidgetOrder(widgetIdToPriorityMap)
            underTest.updateWidgetOrder(widgetIdToPriorityMap)
            runCurrent()

            verify(communalWidgetDao).updateWidgetOrder(widgetIdToPriorityMap)
        }

    @Test
    fun broadcastReceiver_communalDisabled_doNotRegisterUserUnlockedBroadcastReceiver() =
    fun appWidgetHost_startListening() =
        testScope.runTest {
            communalEnabled(false)
            val repository = initCommunalWidgetRepository()
            repository.communalWidgets.launchIn(backgroundScope)
            runCurrent()
            assertThat(fakeBroadcastDispatcher.numReceiversRegistered).isEqualTo(0)
        }

    @Test
    fun broadcastReceiver_featureEnabledAndUserLocked_registerBroadcastReceiver() =
        testScope.runTest {
            userUnlocked(false)
            val repository = initCommunalWidgetRepository()
            repository.communalWidgets.launchIn(backgroundScope)
            runCurrent()
            assertThat(fakeBroadcastDispatcher.numReceiversRegistered).isEqualTo(1)
        }

    @Test
    fun appWidgetHost_userUnlocked_startListening() =
        testScope.runTest {
            userUnlocked(false)
            val repository = initCommunalWidgetRepository()
            repository.communalWidgets.launchIn(backgroundScope)
            runCurrent()
            verify(appWidgetHost, never()).startListening()

            userUnlocked(true)
            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
                context,
                Intent(ACTION_USER_UNLOCKED)
            )
            runCurrent()
            underTest.updateAppWidgetHostActive(true)

            verify(appWidgetHost).startListening()
        }

    @Test
    fun appWidgetHost_userLockedAgain_stopListening() =
    fun appWidgetHost_stopListening() =
        testScope.runTest {
            userUnlocked(false)
            val repository = initCommunalWidgetRepository()
            repository.communalWidgets.launchIn(backgroundScope)
            runCurrent()

            userUnlocked(true)
            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
                context,
                Intent(ACTION_USER_UNLOCKED)
            )
            runCurrent()
            underTest.updateAppWidgetHostActive(true)

            verify(appWidgetHost).startListening()
            verify(appWidgetHost, never()).stopListening()

            userUnlocked(false)
            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
                context,
                Intent(ACTION_USER_UNLOCKED)
            )
            runCurrent()
            underTest.updateAppWidgetHostActive(false)

            verify(appWidgetHost).stopListening()
        }

    private fun initCommunalWidgetRepository(): CommunalWidgetRepositoryImpl {
        return CommunalWidgetRepositoryImpl(
            appWidgetManagerOptional,
            appWidgetHost,
            testScope.backgroundScope,
            kosmos.testDispatcher,
            fakeBroadcastDispatcher,
            communalRepository,
            communalWidgetHost,
            communalWidgetDao,
            userManager,
            userTracker,
            logBuffer,
        )
    }

    private fun communalEnabled(enabled: Boolean) {
        communalRepository.setIsCommunalEnabled(enabled)
    }

    private fun userUnlocked(userUnlocked: Boolean) {
        whenever(userManager.isUserUnlockingOrUnlocked(userHandle)).thenReturn(userUnlocked)
    }

    private fun installedProviders(providers: List<AppWidgetProviderInfo>) {
        whenever(appWidgetManager.installedProviders).thenReturn(providers)
    }
+95 −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.domain.interactor

import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.data.repository.FakeCommunalRepository
import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository
import com.android.systemui.communal.data.repository.fakeCommunalRepository
import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.domain.interactor.communalInteractor
import com.android.systemui.kosmos.testScope
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith

/**
 * This class is a variation of the [CommunalInteractorTest] for cases where communal is disabled.
 */
@SmallTest
@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidJUnit4::class)
class CommunalInteractorCommunalDisabledTest : SysuiTestCase() {
    private val kosmos = testKosmos()
    private val testScope = kosmos.testScope

    private lateinit var communalRepository: FakeCommunalRepository
    private lateinit var widgetRepository: FakeCommunalWidgetRepository
    private lateinit var keyguardRepository: FakeKeyguardRepository

    private lateinit var underTest: CommunalInteractor

    @Before
    fun setUp() {
        communalRepository = kosmos.fakeCommunalRepository
        widgetRepository = kosmos.fakeCommunalWidgetRepository
        keyguardRepository = kosmos.fakeKeyguardRepository

        communalRepository.setIsCommunalEnabled(false)

        underTest = kosmos.communalInteractor
    }

    @Test
    fun isCommunalEnabled_false() =
        testScope.runTest { assertThat(underTest.isCommunalEnabled).isFalse() }

    @Test
    fun isCommunalAvailable_whenStorageUnlock_false() =
        testScope.runTest {
            val isCommunalAvailable by collectLastValue(underTest.isCommunalAvailable)

            assertThat(isCommunalAvailable).isFalse()

            keyguardRepository.setIsEncryptedOrLockdown(false)
            runCurrent()

            assertThat(isCommunalAvailable).isFalse()
        }

    @Test
    fun updateAppWidgetHostActive_whenStorageUnlock_false() =
        testScope.runTest {
            assertThat(widgetRepository.isHostActive()).isFalse()

            keyguardRepository.setIsEncryptedOrLockdown(false)
            runCurrent()

            assertThat(widgetRepository.isHostActive()).isFalse()
        }
}
+36 −6
Original line number Diff line number Diff line
@@ -62,6 +62,10 @@ import org.junit.runner.RunWith
import org.mockito.Mockito.mock
import org.mockito.Mockito.verify

/**
 * This class of test cases assume that communal is enabled. For disabled cases, see
 * [CommunalInteractorCommunalDisabledTest].
 */
@SmallTest
@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidJUnit4::class)
@@ -95,17 +99,43 @@ class CommunalInteractorTest : SysuiTestCase() {
    }

    @Test
    fun communalEnabled() =
    fun communalEnabled_true() =
        testScope.runTest { assertThat(underTest.isCommunalEnabled).isTrue() }

    @Test
    fun isCommunalAvailable_trueWhenStorageUnlock() =
        testScope.runTest {
            val isAvailable by collectLastValue(underTest.isCommunalAvailable)
            assertThat(isAvailable).isFalse()

            keyguardRepository.setIsEncryptedOrLockdown(false)
            runCurrent()

            assertThat(isAvailable).isTrue()
        }

    @Test
    fun isCommunalAvailable_whenStorageUnlock_true() =
        testScope.runTest {
            communalRepository.setIsCommunalEnabled(true)
            assertThat(underTest.isCommunalEnabled).isTrue()
            val isAvailable by collectLastValue(underTest.isCommunalAvailable)
            assertThat(isAvailable).isFalse()

            keyguardRepository.setIsEncryptedOrLockdown(false)
            runCurrent()

            assertThat(isAvailable).isTrue()
        }

    @Test
    fun communalDisabled() =
    fun updateAppWidgetHostActive_uponStorageUnlock_true() =
        testScope.runTest {
            communalRepository.setIsCommunalEnabled(false)
            assertThat(underTest.isCommunalEnabled).isFalse()
            collectLastValue(underTest.isCommunalAvailable)
            assertThat(widgetRepository.isHostActive()).isFalse()

            keyguardRepository.setIsEncryptedOrLockdown(false)
            runCurrent()

            assertThat(widgetRepository.isHostActive()).isTrue()
        }

    @Test
+27 −0
Original line number Diff line number Diff line
@@ -35,6 +35,7 @@ import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
import com.android.systemui.keyguard.shared.model.DozeStateModel
import com.android.systemui.keyguard.shared.model.DozeTransitionModel
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
@@ -52,6 +53,8 @@ import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito.atLeastOnce
import org.mockito.Mockito.verify
@@ -68,6 +71,8 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
    @Mock private lateinit var authController: AuthController
    @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
    @Mock private lateinit var dreamOverlayCallbackController: DreamOverlayCallbackController
    @Mock private lateinit var userTracker: UserTracker
    @Captor private lateinit var updateCallbackCaptor: ArgumentCaptor<KeyguardUpdateMonitorCallback>
    private val mainDispatcher = StandardTestDispatcher()
    private val testDispatcher = StandardTestDispatcher()
    private val testScope = TestScope(testDispatcher)
@@ -93,6 +98,7 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
                testScope.backgroundScope,
                systemClock,
                facePropertyRepository,
                userTracker,
            )
    }

@@ -553,4 +559,25 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {

            job.cancel()
        }

    @Test
    fun isEncryptedOrLockdown() =
        testScope.runTest {
            whenever(userTracker.userId).thenReturn(0)
            whenever(keyguardUpdateMonitor.isEncryptedOrLockdown(0)).thenReturn(true)

            // Default value for isEncryptedOrLockdown is true
            val isEncryptedOrLockdown by collectLastValue(underTest.isEncryptedOrLockdown)
            assertThat(isEncryptedOrLockdown).isTrue()

            verify(keyguardUpdateMonitor).registerCallback(updateCallbackCaptor.capture())
            val updateCallback = updateCallbackCaptor.value

            // Strong auth state updated
            whenever(keyguardUpdateMonitor.isEncryptedOrLockdown(0)).thenReturn(false)
            updateCallback.onStrongAuthStateChanged(0)

            // Verify no longer encrypted or lockdown
            assertThat(isEncryptedOrLockdown).isFalse()
        }
}
Loading