diff options
| author | 2023-08-16 17:16:39 -0700 | |
|---|---|---|
| committer | 2023-08-21 23:35:22 +0000 | |
| commit | c73f5616bc7ec67641ad2b4b75bf5cb9a9e329e8 (patch) | |
| tree | 788eff4260dd8e2179042b153d835f4ad15283d5 | |
| parent | 8a5c6453efe75b481bd400fcd2c44e61bfec1ab8 (diff) | |
[flexiglass] Lockscreen scene
Adds the standalone KeyguardRootView to LockscreenScene.
Note how we remove it from its parent ViewGroup, if there is one,
whenever our view factory gets called. This is needed so there is only
one KeyguardRootView at all times (or it crashes because the NSSL gets
initialized twice).
Bug: 280879610
Test: manually verified that the lockscreen scene renders the real
lockscreen
Test: manually verified that pulling down brings down the shade and
pulling up brings up the bouncer
Test: manually verified the ability to unlock via PIN bouncer and face
unlock and then re-lock the device via power button + timeout - no crash
Test: manually verified correct rendering in AOD
Change-Id: I54c11bccc7a10e43afea5dbaa09013e72b0a806f
7 files changed, 92 insertions, 133 deletions
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt index da48762e1960..0a100babde75 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt @@ -14,26 +14,20 @@ * limitations under the License. */ +@file:OptIn(ExperimentalCoroutinesApi::class) + package com.android.systemui.keyguard.ui.composable -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.material3.Button -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text +import android.view.View +import android.view.ViewGroup import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp +import androidx.compose.ui.viewinterop.AndroidView import com.android.compose.animation.scene.SceneScope -import com.android.systemui.common.shared.model.Icon -import com.android.systemui.common.ui.compose.Icon +import com.android.systemui.R import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.keyguard.qualifiers.KeyguardRootView import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel import com.android.systemui.scene.shared.model.Direction import com.android.systemui.scene.shared.model.SceneKey @@ -42,6 +36,7 @@ import com.android.systemui.scene.shared.model.UserAction import com.android.systemui.scene.ui.composable.ComposableScene import javax.inject.Inject import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.map @@ -54,6 +49,7 @@ class LockscreenScene constructor( @Application private val applicationScope: CoroutineScope, private val viewModel: LockscreenSceneViewModel, + @KeyguardRootView private val viewProvider: () -> @JvmSuppressWildcards View, ) : ComposableScene { override val key = SceneKey.Lockscreen @@ -72,6 +68,7 @@ constructor( ) { LockscreenScene( viewModel = viewModel, + viewProvider = viewProvider, modifier = modifier, ) } @@ -89,25 +86,22 @@ constructor( @Composable private fun LockscreenScene( viewModel: LockscreenSceneViewModel, + viewProvider: () -> View, modifier: Modifier = Modifier, ) { - // TODO(b/280879610): implement the real UI. - - val lockButtonIcon: Icon by viewModel.lockButtonIcon.collectAsState() - - Box(modifier = modifier) { - Column( - horizontalAlignment = Alignment.CenterHorizontally, - modifier = Modifier.align(Alignment.Center) - ) { - Text("Lockscreen", style = MaterialTheme.typography.headlineMedium) - Row( - horizontalArrangement = Arrangement.spacedBy(8.dp), - ) { - Button(onClick = { viewModel.onLockButtonClicked() }) { Icon(lockButtonIcon) } - - Button(onClick = { viewModel.onContentClicked() }) { Text("Open some content") } + AndroidView( + factory = { _ -> + val keyguardRootView = viewProvider() + // Remove the KeyguardRootView from any parent it might already have in legacy code just + // in case (a view can't have two parents). + (keyguardRootView.parent as? ViewGroup)?.removeView(keyguardRootView) + keyguardRootView + }, + update = { keyguardRootView -> + keyguardRootView.requireViewById<View>(R.id.lock_icon_view).setOnClickListener { + viewModel.onLockButtonClicked() } - } - } + }, + modifier = modifier, + ) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/qualifiers/KeyguardRootView.kt b/packages/SystemUI/src/com/android/systemui/keyguard/qualifiers/KeyguardRootView.kt new file mode 100644 index 000000000000..c2d2725f4f06 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/qualifiers/KeyguardRootView.kt @@ -0,0 +1,24 @@ +/* + * Copyright 2023 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.keyguard.qualifiers + +import javax.inject.Qualifier + +@Qualifier +@MustBeDocumented +@Retention(AnnotationRetention.RUNTIME) +annotation class KeyguardRootView diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/LockscreenSceneModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/LockscreenSceneModule.kt new file mode 100644 index 000000000000..c88737e6bd70 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/LockscreenSceneModule.kt @@ -0,0 +1,41 @@ +/* + * Copyright 2023 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. + */ + +@file:OptIn(ExperimentalCoroutinesApi::class) + +package com.android.systemui.keyguard.ui.view + +import android.view.View +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.KeyguardViewConfigurator +import com.android.systemui.keyguard.qualifiers.KeyguardRootView +import dagger.Module +import dagger.Provides +import javax.inject.Provider +import kotlinx.coroutines.ExperimentalCoroutinesApi + +@Module +object LockscreenSceneModule { + + @Provides + @SysUISingleton + @KeyguardRootView + fun viewProvider( + configurator: Provider<KeyguardViewConfigurator>, + ): () -> View { + return { configurator.get().getKeyguardRootView() } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt index 6d3b7f18e974..93c4902332c5 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt @@ -16,41 +16,22 @@ package com.android.systemui.keyguard.ui.viewmodel -import com.android.systemui.R import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor import com.android.systemui.bouncer.domain.interactor.BouncerInteractor -import com.android.systemui.common.shared.model.ContentDescription -import com.android.systemui.common.shared.model.Icon import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.scene.shared.model.SceneKey import javax.inject.Inject -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.stateIn /** Models UI state and handles user input for the lockscreen scene. */ @SysUISingleton class LockscreenSceneViewModel @Inject constructor( - @Application applicationScope: CoroutineScope, authenticationInteractor: AuthenticationInteractor, private val bouncerInteractor: BouncerInteractor, ) { - /** The icon for the "lock" button on the lockscreen. */ - val lockButtonIcon: StateFlow<Icon> = - authenticationInteractor.isUnlocked - .map { isUnlocked -> lockIcon(isUnlocked = isUnlocked) } - .stateIn( - scope = applicationScope, - started = SharingStarted.WhileSubscribed(), - initialValue = lockIcon(isUnlocked = authenticationInteractor.isUnlocked.value), - ) - /** The key of the scene we should switch to when swiping up. */ val upDestinationSceneKey: Flow<SceneKey> = authenticationInteractor.isUnlocked.map { isUnlocked -> @@ -65,31 +46,4 @@ constructor( fun onLockButtonClicked() { bouncerInteractor.showOrUnlockDevice() } - - /** Notifies that some content on the lock screen was clicked. */ - fun onContentClicked() { - bouncerInteractor.showOrUnlockDevice() - } - - private fun upDestinationSceneKey( - canSwipeToDismiss: Boolean, - ): SceneKey { - return if (canSwipeToDismiss) SceneKey.Gone else SceneKey.Bouncer - } - - private fun lockIcon( - isUnlocked: Boolean, - ): Icon { - return if (isUnlocked) { - Icon.Resource( - R.drawable.ic_device_lock_off, - ContentDescription.Resource(R.string.accessibility_unlock_button) - ) - } else { - Icon.Resource( - R.drawable.ic_device_lock_on, - ContentDescription.Resource(R.string.accessibility_lock_icon) - ) - } - } } diff --git a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt index 398e64b1981b..714795109454 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt @@ -16,6 +16,7 @@ package com.android.systemui.scene +import com.android.systemui.keyguard.ui.view.LockscreenSceneModule import com.android.systemui.scene.domain.startable.SceneContainerStartableModule import com.android.systemui.scene.shared.model.SceneContainerConfigModule import com.android.systemui.scene.ui.composable.SceneModule @@ -24,6 +25,7 @@ import dagger.Module @Module( includes = [ + LockscreenSceneModule::class, SceneContainerConfigModule::class, SceneContainerStartableModule::class, SceneModule::class, diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt index 23f243c8ebda..a9f288d3575f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt @@ -17,10 +17,8 @@ package com.android.systemui.keyguard.ui.viewmodel import androidx.test.filters.SmallTest -import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.authentication.data.model.AuthenticationMethodModel -import com.android.systemui.common.shared.model.Icon import com.android.systemui.coroutines.collectLastValue import com.android.systemui.scene.SceneTestUtils import com.android.systemui.scene.shared.model.SceneKey @@ -48,7 +46,6 @@ class LockscreenSceneViewModelTest : SysuiTestCase() { private val underTest = LockscreenSceneViewModel( - applicationScope = testScope.backgroundScope, authenticationInteractor = authenticationInteractor, bouncerInteractor = utils.bouncerInteractor( @@ -58,32 +55,6 @@ class LockscreenSceneViewModelTest : SysuiTestCase() { ) @Test - fun lockButtonIcon_whenLocked() = - testScope.runTest { - val lockButtonIcon by collectLastValue(underTest.lockButtonIcon) - utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Password - ) - utils.authenticationRepository.setUnlocked(false) - - assertThat((lockButtonIcon as? Icon.Resource)?.res) - .isEqualTo(R.drawable.ic_device_lock_on) - } - - @Test - fun lockButtonIcon_whenUnlocked() = - testScope.runTest { - val lockButtonIcon by collectLastValue(underTest.lockButtonIcon) - utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Password - ) - utils.authenticationRepository.setUnlocked(true) - - assertThat((lockButtonIcon as? Icon.Resource)?.res) - .isEqualTo(R.drawable.ic_device_lock_off) - } - - @Test fun upTransitionSceneKey_canSwipeToUnlock_gone() = testScope.runTest { val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey) @@ -120,32 +91,6 @@ class LockscreenSceneViewModelTest : SysuiTestCase() { } @Test - fun onContentClicked_deviceUnlocked_switchesToGone() = - testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.desiredScene) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) - utils.authenticationRepository.setUnlocked(true) - runCurrent() - - underTest.onContentClicked() - - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone)) - } - - @Test - fun onContentClicked_deviceLockedSecurely_switchesToBouncer() = - testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.desiredScene) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) - utils.authenticationRepository.setUnlocked(false) - runCurrent() - - underTest.onContentClicked() - - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) - } - - @Test fun onLockButtonClicked_deviceUnlocked_switchesToGone() = testScope.runTest { val currentScene by collectLastValue(sceneInteractor.desiredScene) diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt index 53c04ccbdb38..46cbfacb0044 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt @@ -116,7 +116,6 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { private val lockscreenSceneViewModel = LockscreenSceneViewModel( - applicationScope = testScope.backgroundScope, authenticationInteractor = authenticationInteractor, bouncerInteractor = bouncerInteractor, ) |