Loading packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt +52 −15 Original line number Diff line number Diff line Loading @@ -16,24 +16,28 @@ package com.android.systemui.communal.data.repository import android.content.pm.UserInfo 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.shared.model.CommunalSceneKey import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.FakeFeatureFlagsClassic import com.android.systemui.flags.Flags import com.android.systemui.scene.data.repository.SceneContainerRepository import com.android.systemui.flags.fakeFeatureFlagsClassic import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope import com.android.systemui.scene.data.repository.sceneContainerRepository import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel 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.settings.FakeSettings import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test Loading @@ -44,19 +48,23 @@ import org.junit.runner.RunWith class CommunalRepositoryImplTest : SysuiTestCase() { private lateinit var underTest: CommunalRepositoryImpl private val testDispatcher = StandardTestDispatcher() private val testScope = TestScope(testDispatcher) private lateinit var secureSettings: FakeSettings private lateinit var userRepository: FakeUserRepository private lateinit var featureFlagsClassic: FakeFeatureFlagsClassic private lateinit var sceneContainerRepository: SceneContainerRepository private val kosmos = testKosmos() private val testScope = kosmos.testScope private val sceneContainerRepository = kosmos.sceneContainerRepository @Before fun setUp() { val kosmos = testKosmos() sceneContainerRepository = kosmos.sceneContainerRepository featureFlagsClassic = FakeFeatureFlagsClassic() secureSettings = FakeSettings() userRepository = kosmos.fakeUserRepository featureFlagsClassic.set(Flags.COMMUNAL_SERVICE_ENABLED, true) val listOfUserInfo = listOf(MAIN_USER_INFO) userRepository.setUserInfos(listOfUserInfo) kosmos.fakeFeatureFlagsClassic.apply { set(Flags.COMMUNAL_SERVICE_ENABLED, true) } mSetFlagsRule.enableFlags(FLAG_COMMUNAL_HUB) underTest = createRepositoryImpl(false) } Loading @@ -64,9 +72,13 @@ class CommunalRepositoryImplTest : SysuiTestCase() { private fun createRepositoryImpl(sceneContainerEnabled: Boolean): CommunalRepositoryImpl { return CommunalRepositoryImpl( testScope.backgroundScope, featureFlagsClassic, FakeSceneContainerFlags(enabled = sceneContainerEnabled), testScope.backgroundScope, kosmos.testDispatcher, kosmos.fakeFeatureFlagsClassic, kosmos.fakeSceneContainerFlags.apply { enabled = sceneContainerEnabled }, sceneContainerRepository, kosmos.fakeUserRepository, secureSettings, ) } Loading Loading @@ -147,4 +159,29 @@ class CommunalRepositoryImplTest : SysuiTestCase() { assertThat(transitionState) .isEqualTo(ObservableCommunalTransitionState.Idle(CommunalSceneKey.DEFAULT)) } @Test fun communalEnabledState_false_whenGlanceableHubSettingFalse() = testScope.runTest { userRepository.setSelectedUserInfo(MAIN_USER_INFO) secureSettings.putIntForUser(GLANCEABLE_HUB_ENABLED, 0, MAIN_USER_INFO.id) val communalEnabled by collectLastValue(underTest.communalEnabledState) assertThat(communalEnabled).isFalse() } @Test fun communalEnabledState_true_whenGlanceableHubSettingTrue() = testScope.runTest { userRepository.setSelectedUserInfo(MAIN_USER_INFO) secureSettings.putIntForUser(GLANCEABLE_HUB_ENABLED, 1, MAIN_USER_INFO.id) val communalEnabled by collectLastValue(underTest.communalEnabledState) assertThat(communalEnabled).isTrue() } companion object { private const val GLANCEABLE_HUB_ENABLED = "glanceable_hub_enabled" private val MAIN_USER_INFO = UserInfo(0, "primary", UserInfo.FLAG_MAIN) } } packages/SystemUI/res/layout/super_notification_shade.xml +3 −2 Original line number Diff line number Diff line Loading @@ -27,10 +27,11 @@ android:fitsSystemWindows="true"> <!-- Placeholder for the communal UI that will be replaced if the feature is enabled. --> <ViewStub <View android:id="@+id/communal_ui_stub" android:layout_width="match_parent" android:layout_height="match_parent" /> android:layout_height="match_parent" android:visibility="gone" /> <com.android.systemui.scrim.ScrimView android:id="@+id/scrim_behind" Loading packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt +61 −1 Original line number Diff line number Diff line Loading @@ -20,13 +20,18 @@ import com.android.systemui.Flags.communalHub import com.android.systemui.communal.shared.model.CommunalSceneKey import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.flags.FeatureFlagsClassic import com.android.systemui.flags.Flags import com.android.systemui.scene.data.repository.SceneContainerRepository import com.android.systemui.scene.shared.flag.SceneContainerFlags import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.user.data.repository.UserRepository import com.android.systemui.util.settings.SecureSettings import com.android.systemui.util.settings.SettingsProxyExt.observerFlow import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow Loading @@ -34,16 +39,26 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.withContext /** Encapsulates the state of communal mode. */ interface CommunalRepository { /** Whether communal features are enabled. */ val isCommunalEnabled: Boolean /** * A {@link StateFlow} that tracks whether communal hub is enabled (it can be disabled in * settings). */ val communalEnabledState: StateFlow<Boolean> /** Whether the communal hub is showing. */ val isCommunalHubShowing: Flow<Boolean> Loading Loading @@ -72,13 +87,36 @@ interface CommunalRepository { class CommunalRepositoryImpl @Inject constructor( @Application private val applicationScope: CoroutineScope, @Background backgroundScope: CoroutineScope, @Background private val backgroundDispatcher: CoroutineDispatcher, private val featureFlagsClassic: FeatureFlagsClassic, sceneContainerFlags: SceneContainerFlags, sceneContainerRepository: SceneContainerRepository, userRepository: UserRepository, private val secureSettings: SecureSettings ) : CommunalRepository { private val communalEnabledSettingState: Flow<Boolean> = userRepository.selectedUserInfo .flatMapLatest { userInfo -> observeSettings(userInfo.id) } .shareIn(scope = applicationScope, started = SharingStarted.WhileSubscribed()) override val communalEnabledState: StateFlow<Boolean> = if (featureFlagsClassic.isEnabled(Flags.COMMUNAL_SERVICE_ENABLED) && communalHub()) { communalEnabledSettingState .filterNotNull() .stateIn( scope = applicationScope, started = SharingStarted.Eagerly, initialValue = true ) } else { MutableStateFlow(false) } override val isCommunalEnabled: Boolean get() = featureFlagsClassic.isEnabled(Flags.COMMUNAL_SERVICE_ENABLED) && communalHub() get() = communalEnabledState.value private val _desiredScene: MutableStateFlow<CommunalSceneKey> = MutableStateFlow(CommunalSceneKey.DEFAULT) Loading Loading @@ -115,4 +153,26 @@ constructor( } else { desiredScene.map { sceneKey -> sceneKey == CommunalSceneKey.Communal } } private fun observeSettings(userId: Int): Flow<Boolean> = secureSettings .observerFlow( userId = userId, names = arrayOf( GLANCEABLE_HUB_ENABLED, ) ) // Force an update .onStart { emit(Unit) } .map { readFromSettings(userId) } private suspend fun readFromSettings(userId: Int): Boolean = withContext(backgroundDispatcher) { secureSettings.getIntForUser(GLANCEABLE_HUB_ENABLED, 1, userId) == 1 } companion object { private const val GLANCEABLE_HUB_ENABLED = "glanceable_hub_enabled" } } packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt +6 −1 Original line number Diff line number Diff line Loading @@ -72,7 +72,12 @@ constructor( val isCommunalEnabled: Boolean get() = communalRepository.isCommunalEnabled val isCommunalAvailable = /** A {@link StateFlow} that tracks whether communal features are enabled. */ val communalEnabledState: StateFlow<Boolean> get() = communalRepository.communalEnabledState /** Whether communal features are enabled and available. */ val isCommunalAvailable: StateFlow<Boolean> = flowOf(isCommunalEnabled) .flatMapLatest { enabled -> if (enabled) Loading packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt +37 −29 Original line number Diff line number Diff line Loading @@ -22,6 +22,7 @@ import android.os.SystemClock import android.view.GestureDetector import android.view.MotionEvent import android.view.View import android.view.ViewGroup import com.android.internal.annotations.VisibleForTesting import com.android.systemui.communal.domain.interactor.CommunalInteractor import com.android.systemui.communal.ui.viewmodel.CommunalViewModel Loading @@ -33,6 +34,7 @@ import com.android.systemui.res.R import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.util.kotlin.collectFlow import javax.inject.Inject import kotlinx.coroutines.flow.StateFlow /** * Controller that's responsible for the glanceable hub container view and its touch handling. Loading @@ -49,7 +51,7 @@ constructor( private val powerManager: PowerManager, ) { /** The container view for the hub. This will not be initialized until [initView] is called. */ private lateinit var communalContainerView: View private var communalContainerView: View? = null /** * The width of the area in which a right edge swipe can open the hub, in pixels. Read from Loading Loading @@ -108,6 +110,11 @@ constructor( return communalInteractor.isCommunalEnabled && isComposeAvailable() } /** Returns a {@link StateFlow} that tracks whether communal hub is enabled. */ fun enabledState(): StateFlow<Boolean> { return communalInteractor.communalEnabledState } /** * Creates the container view containing the glanceable hub UI. * Loading @@ -125,42 +132,44 @@ constructor( if (!isEnabled()) { throw RuntimeException("Glanceable hub is not enabled") } if (::communalContainerView.isInitialized) { if (communalContainerView != null) { throw RuntimeException("Communal view has already been initialized") } communalContainerView = containerView rightEdgeSwipeRegionWidth = communalContainerView.resources.getDimensionPixelSize( containerView.resources.getDimensionPixelSize( R.dimen.communal_right_edge_swipe_region_width ) topEdgeSwipeRegionWidth = communalContainerView.resources.getDimensionPixelSize( containerView.resources.getDimensionPixelSize( R.dimen.communal_top_edge_swipe_region_height ) bottomEdgeSwipeRegionWidth = communalContainerView.resources.getDimensionPixelSize( containerView.resources.getDimensionPixelSize( R.dimen.communal_bottom_edge_swipe_region_height ) collectFlow( communalContainerView, containerView, keyguardTransitionInteractor.isFinishedInStateWhere(KeyguardState::isBouncerState), { anyBouncerShowing = it } ) collectFlow( communalContainerView, communalInteractor.isCommunalShowing, { hubShowing = it } ) collectFlow( communalContainerView, shadeInteractor.isAnyFullyExpanded, { shadeShowing = it } ) collectFlow(containerView, communalInteractor.isCommunalShowing, { hubShowing = it }) collectFlow(containerView, shadeInteractor.isAnyFullyExpanded, { shadeShowing = it }) return communalContainerView communalContainerView = containerView return containerView } /** Removes the container view from its parent. */ fun disposeView() { communalContainerView?.let { (it.parent as ViewGroup).removeView(it) communalContainerView = null } } /** Loading @@ -173,10 +182,10 @@ constructor( * to be fully in control of its own touch handling. */ fun onTouchEvent(ev: MotionEvent): Boolean { if (!::communalContainerView.isInitialized) { return false return communalContainerView?.let { handleTouchEventOnCommunalView(it, ev) } ?: false } private fun handleTouchEventOnCommunalView(view: View, ev: MotionEvent): Boolean { val isDown = ev.actionMasked == MotionEvent.ACTION_DOWN val isUp = ev.actionMasked == MotionEvent.ACTION_UP val isCancel = ev.actionMasked == MotionEvent.ACTION_CANCEL Loading @@ -190,7 +199,7 @@ constructor( if (hubShowing && isDown) { val y = ev.rawY val topSwipe: Boolean = y <= topEdgeSwipeRegionWidth val bottomSwipe = y >= communalContainerView.height - bottomEdgeSwipeRegionWidth val bottomSwipe = y >= view.height - bottomEdgeSwipeRegionWidth if (topSwipe || bottomSwipe) { // Don't intercept touches at the top/bottom edge so that swipes can open the Loading @@ -200,7 +209,7 @@ constructor( if (!hubOccluded) { isTrackingHubTouch = true dispatchTouchEvent(ev) dispatchTouchEvent(view, ev) // Return true regardless of dispatch result as some touches at the start of a // gesture may return false from dispatchTouchEvent. return true Loading @@ -209,7 +218,7 @@ constructor( if (isUp || isCancel) { isTrackingHubTouch = false } dispatchTouchEvent(ev) dispatchTouchEvent(view, ev) // Return true regardless of dispatch result as some touches at the start of a gesture // may return false from dispatchTouchEvent. return true Loading @@ -223,11 +232,10 @@ constructor( if (!isTrackingOpenGesture && isDown) { val x = ev.rawX val inOpeningSwipeRegion: Boolean = x >= communalContainerView.width - rightEdgeSwipeRegionWidth val inOpeningSwipeRegion: Boolean = x >= view.width - rightEdgeSwipeRegionWidth if (inOpeningSwipeRegion && !hubOccluded) { isTrackingOpenGesture = true dispatchTouchEvent(ev) dispatchTouchEvent(view, ev) // Return true regardless of dispatch result as some touches at the start of a // gesture may return false from dispatchTouchEvent. return true Loading @@ -236,7 +244,7 @@ constructor( if (isUp || isCancel) { isTrackingOpenGesture = false } dispatchTouchEvent(ev) dispatchTouchEvent(view, ev) // Return true regardless of dispatch result as some touches at the start of a gesture // may return false from dispatchTouchEvent. return true Loading @@ -249,8 +257,8 @@ constructor( * Dispatches the touch event to the communal container and sends a user activity event to reset * the screen timeout. */ private fun dispatchTouchEvent(ev: MotionEvent) { communalContainerView.dispatchTouchEvent(ev) private fun dispatchTouchEvent(view: View, ev: MotionEvent) { view.dispatchTouchEvent(ev) powerManager.userActivity( SystemClock.uptimeMillis(), PowerManager.USER_ACTIVITY_EVENT_TOUCH, Loading Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt +52 −15 Original line number Diff line number Diff line Loading @@ -16,24 +16,28 @@ package com.android.systemui.communal.data.repository import android.content.pm.UserInfo 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.shared.model.CommunalSceneKey import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.FakeFeatureFlagsClassic import com.android.systemui.flags.Flags import com.android.systemui.scene.data.repository.SceneContainerRepository import com.android.systemui.flags.fakeFeatureFlagsClassic import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope import com.android.systemui.scene.data.repository.sceneContainerRepository import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel 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.settings.FakeSettings import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test Loading @@ -44,19 +48,23 @@ import org.junit.runner.RunWith class CommunalRepositoryImplTest : SysuiTestCase() { private lateinit var underTest: CommunalRepositoryImpl private val testDispatcher = StandardTestDispatcher() private val testScope = TestScope(testDispatcher) private lateinit var secureSettings: FakeSettings private lateinit var userRepository: FakeUserRepository private lateinit var featureFlagsClassic: FakeFeatureFlagsClassic private lateinit var sceneContainerRepository: SceneContainerRepository private val kosmos = testKosmos() private val testScope = kosmos.testScope private val sceneContainerRepository = kosmos.sceneContainerRepository @Before fun setUp() { val kosmos = testKosmos() sceneContainerRepository = kosmos.sceneContainerRepository featureFlagsClassic = FakeFeatureFlagsClassic() secureSettings = FakeSettings() userRepository = kosmos.fakeUserRepository featureFlagsClassic.set(Flags.COMMUNAL_SERVICE_ENABLED, true) val listOfUserInfo = listOf(MAIN_USER_INFO) userRepository.setUserInfos(listOfUserInfo) kosmos.fakeFeatureFlagsClassic.apply { set(Flags.COMMUNAL_SERVICE_ENABLED, true) } mSetFlagsRule.enableFlags(FLAG_COMMUNAL_HUB) underTest = createRepositoryImpl(false) } Loading @@ -64,9 +72,13 @@ class CommunalRepositoryImplTest : SysuiTestCase() { private fun createRepositoryImpl(sceneContainerEnabled: Boolean): CommunalRepositoryImpl { return CommunalRepositoryImpl( testScope.backgroundScope, featureFlagsClassic, FakeSceneContainerFlags(enabled = sceneContainerEnabled), testScope.backgroundScope, kosmos.testDispatcher, kosmos.fakeFeatureFlagsClassic, kosmos.fakeSceneContainerFlags.apply { enabled = sceneContainerEnabled }, sceneContainerRepository, kosmos.fakeUserRepository, secureSettings, ) } Loading Loading @@ -147,4 +159,29 @@ class CommunalRepositoryImplTest : SysuiTestCase() { assertThat(transitionState) .isEqualTo(ObservableCommunalTransitionState.Idle(CommunalSceneKey.DEFAULT)) } @Test fun communalEnabledState_false_whenGlanceableHubSettingFalse() = testScope.runTest { userRepository.setSelectedUserInfo(MAIN_USER_INFO) secureSettings.putIntForUser(GLANCEABLE_HUB_ENABLED, 0, MAIN_USER_INFO.id) val communalEnabled by collectLastValue(underTest.communalEnabledState) assertThat(communalEnabled).isFalse() } @Test fun communalEnabledState_true_whenGlanceableHubSettingTrue() = testScope.runTest { userRepository.setSelectedUserInfo(MAIN_USER_INFO) secureSettings.putIntForUser(GLANCEABLE_HUB_ENABLED, 1, MAIN_USER_INFO.id) val communalEnabled by collectLastValue(underTest.communalEnabledState) assertThat(communalEnabled).isTrue() } companion object { private const val GLANCEABLE_HUB_ENABLED = "glanceable_hub_enabled" private val MAIN_USER_INFO = UserInfo(0, "primary", UserInfo.FLAG_MAIN) } }
packages/SystemUI/res/layout/super_notification_shade.xml +3 −2 Original line number Diff line number Diff line Loading @@ -27,10 +27,11 @@ android:fitsSystemWindows="true"> <!-- Placeholder for the communal UI that will be replaced if the feature is enabled. --> <ViewStub <View android:id="@+id/communal_ui_stub" android:layout_width="match_parent" android:layout_height="match_parent" /> android:layout_height="match_parent" android:visibility="gone" /> <com.android.systemui.scrim.ScrimView android:id="@+id/scrim_behind" Loading
packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt +61 −1 Original line number Diff line number Diff line Loading @@ -20,13 +20,18 @@ import com.android.systemui.Flags.communalHub import com.android.systemui.communal.shared.model.CommunalSceneKey import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.flags.FeatureFlagsClassic import com.android.systemui.flags.Flags import com.android.systemui.scene.data.repository.SceneContainerRepository import com.android.systemui.scene.shared.flag.SceneContainerFlags import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.user.data.repository.UserRepository import com.android.systemui.util.settings.SecureSettings import com.android.systemui.util.settings.SettingsProxyExt.observerFlow import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow Loading @@ -34,16 +39,26 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.withContext /** Encapsulates the state of communal mode. */ interface CommunalRepository { /** Whether communal features are enabled. */ val isCommunalEnabled: Boolean /** * A {@link StateFlow} that tracks whether communal hub is enabled (it can be disabled in * settings). */ val communalEnabledState: StateFlow<Boolean> /** Whether the communal hub is showing. */ val isCommunalHubShowing: Flow<Boolean> Loading Loading @@ -72,13 +87,36 @@ interface CommunalRepository { class CommunalRepositoryImpl @Inject constructor( @Application private val applicationScope: CoroutineScope, @Background backgroundScope: CoroutineScope, @Background private val backgroundDispatcher: CoroutineDispatcher, private val featureFlagsClassic: FeatureFlagsClassic, sceneContainerFlags: SceneContainerFlags, sceneContainerRepository: SceneContainerRepository, userRepository: UserRepository, private val secureSettings: SecureSettings ) : CommunalRepository { private val communalEnabledSettingState: Flow<Boolean> = userRepository.selectedUserInfo .flatMapLatest { userInfo -> observeSettings(userInfo.id) } .shareIn(scope = applicationScope, started = SharingStarted.WhileSubscribed()) override val communalEnabledState: StateFlow<Boolean> = if (featureFlagsClassic.isEnabled(Flags.COMMUNAL_SERVICE_ENABLED) && communalHub()) { communalEnabledSettingState .filterNotNull() .stateIn( scope = applicationScope, started = SharingStarted.Eagerly, initialValue = true ) } else { MutableStateFlow(false) } override val isCommunalEnabled: Boolean get() = featureFlagsClassic.isEnabled(Flags.COMMUNAL_SERVICE_ENABLED) && communalHub() get() = communalEnabledState.value private val _desiredScene: MutableStateFlow<CommunalSceneKey> = MutableStateFlow(CommunalSceneKey.DEFAULT) Loading Loading @@ -115,4 +153,26 @@ constructor( } else { desiredScene.map { sceneKey -> sceneKey == CommunalSceneKey.Communal } } private fun observeSettings(userId: Int): Flow<Boolean> = secureSettings .observerFlow( userId = userId, names = arrayOf( GLANCEABLE_HUB_ENABLED, ) ) // Force an update .onStart { emit(Unit) } .map { readFromSettings(userId) } private suspend fun readFromSettings(userId: Int): Boolean = withContext(backgroundDispatcher) { secureSettings.getIntForUser(GLANCEABLE_HUB_ENABLED, 1, userId) == 1 } companion object { private const val GLANCEABLE_HUB_ENABLED = "glanceable_hub_enabled" } }
packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt +6 −1 Original line number Diff line number Diff line Loading @@ -72,7 +72,12 @@ constructor( val isCommunalEnabled: Boolean get() = communalRepository.isCommunalEnabled val isCommunalAvailable = /** A {@link StateFlow} that tracks whether communal features are enabled. */ val communalEnabledState: StateFlow<Boolean> get() = communalRepository.communalEnabledState /** Whether communal features are enabled and available. */ val isCommunalAvailable: StateFlow<Boolean> = flowOf(isCommunalEnabled) .flatMapLatest { enabled -> if (enabled) Loading
packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt +37 −29 Original line number Diff line number Diff line Loading @@ -22,6 +22,7 @@ import android.os.SystemClock import android.view.GestureDetector import android.view.MotionEvent import android.view.View import android.view.ViewGroup import com.android.internal.annotations.VisibleForTesting import com.android.systemui.communal.domain.interactor.CommunalInteractor import com.android.systemui.communal.ui.viewmodel.CommunalViewModel Loading @@ -33,6 +34,7 @@ import com.android.systemui.res.R import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.util.kotlin.collectFlow import javax.inject.Inject import kotlinx.coroutines.flow.StateFlow /** * Controller that's responsible for the glanceable hub container view and its touch handling. Loading @@ -49,7 +51,7 @@ constructor( private val powerManager: PowerManager, ) { /** The container view for the hub. This will not be initialized until [initView] is called. */ private lateinit var communalContainerView: View private var communalContainerView: View? = null /** * The width of the area in which a right edge swipe can open the hub, in pixels. Read from Loading Loading @@ -108,6 +110,11 @@ constructor( return communalInteractor.isCommunalEnabled && isComposeAvailable() } /** Returns a {@link StateFlow} that tracks whether communal hub is enabled. */ fun enabledState(): StateFlow<Boolean> { return communalInteractor.communalEnabledState } /** * Creates the container view containing the glanceable hub UI. * Loading @@ -125,42 +132,44 @@ constructor( if (!isEnabled()) { throw RuntimeException("Glanceable hub is not enabled") } if (::communalContainerView.isInitialized) { if (communalContainerView != null) { throw RuntimeException("Communal view has already been initialized") } communalContainerView = containerView rightEdgeSwipeRegionWidth = communalContainerView.resources.getDimensionPixelSize( containerView.resources.getDimensionPixelSize( R.dimen.communal_right_edge_swipe_region_width ) topEdgeSwipeRegionWidth = communalContainerView.resources.getDimensionPixelSize( containerView.resources.getDimensionPixelSize( R.dimen.communal_top_edge_swipe_region_height ) bottomEdgeSwipeRegionWidth = communalContainerView.resources.getDimensionPixelSize( containerView.resources.getDimensionPixelSize( R.dimen.communal_bottom_edge_swipe_region_height ) collectFlow( communalContainerView, containerView, keyguardTransitionInteractor.isFinishedInStateWhere(KeyguardState::isBouncerState), { anyBouncerShowing = it } ) collectFlow( communalContainerView, communalInteractor.isCommunalShowing, { hubShowing = it } ) collectFlow( communalContainerView, shadeInteractor.isAnyFullyExpanded, { shadeShowing = it } ) collectFlow(containerView, communalInteractor.isCommunalShowing, { hubShowing = it }) collectFlow(containerView, shadeInteractor.isAnyFullyExpanded, { shadeShowing = it }) return communalContainerView communalContainerView = containerView return containerView } /** Removes the container view from its parent. */ fun disposeView() { communalContainerView?.let { (it.parent as ViewGroup).removeView(it) communalContainerView = null } } /** Loading @@ -173,10 +182,10 @@ constructor( * to be fully in control of its own touch handling. */ fun onTouchEvent(ev: MotionEvent): Boolean { if (!::communalContainerView.isInitialized) { return false return communalContainerView?.let { handleTouchEventOnCommunalView(it, ev) } ?: false } private fun handleTouchEventOnCommunalView(view: View, ev: MotionEvent): Boolean { val isDown = ev.actionMasked == MotionEvent.ACTION_DOWN val isUp = ev.actionMasked == MotionEvent.ACTION_UP val isCancel = ev.actionMasked == MotionEvent.ACTION_CANCEL Loading @@ -190,7 +199,7 @@ constructor( if (hubShowing && isDown) { val y = ev.rawY val topSwipe: Boolean = y <= topEdgeSwipeRegionWidth val bottomSwipe = y >= communalContainerView.height - bottomEdgeSwipeRegionWidth val bottomSwipe = y >= view.height - bottomEdgeSwipeRegionWidth if (topSwipe || bottomSwipe) { // Don't intercept touches at the top/bottom edge so that swipes can open the Loading @@ -200,7 +209,7 @@ constructor( if (!hubOccluded) { isTrackingHubTouch = true dispatchTouchEvent(ev) dispatchTouchEvent(view, ev) // Return true regardless of dispatch result as some touches at the start of a // gesture may return false from dispatchTouchEvent. return true Loading @@ -209,7 +218,7 @@ constructor( if (isUp || isCancel) { isTrackingHubTouch = false } dispatchTouchEvent(ev) dispatchTouchEvent(view, ev) // Return true regardless of dispatch result as some touches at the start of a gesture // may return false from dispatchTouchEvent. return true Loading @@ -223,11 +232,10 @@ constructor( if (!isTrackingOpenGesture && isDown) { val x = ev.rawX val inOpeningSwipeRegion: Boolean = x >= communalContainerView.width - rightEdgeSwipeRegionWidth val inOpeningSwipeRegion: Boolean = x >= view.width - rightEdgeSwipeRegionWidth if (inOpeningSwipeRegion && !hubOccluded) { isTrackingOpenGesture = true dispatchTouchEvent(ev) dispatchTouchEvent(view, ev) // Return true regardless of dispatch result as some touches at the start of a // gesture may return false from dispatchTouchEvent. return true Loading @@ -236,7 +244,7 @@ constructor( if (isUp || isCancel) { isTrackingOpenGesture = false } dispatchTouchEvent(ev) dispatchTouchEvent(view, ev) // Return true regardless of dispatch result as some touches at the start of a gesture // may return false from dispatchTouchEvent. return true Loading @@ -249,8 +257,8 @@ constructor( * Dispatches the touch event to the communal container and sends a user activity event to reset * the screen timeout. */ private fun dispatchTouchEvent(ev: MotionEvent) { communalContainerView.dispatchTouchEvent(ev) private fun dispatchTouchEvent(view: View, ev: MotionEvent) { view.dispatchTouchEvent(ev) powerManager.userActivity( SystemClock.uptimeMillis(), PowerManager.USER_ACTIVITY_EVENT_TOUCH, Loading