diff options
| author | 2024-12-02 16:34:17 -0800 | |
|---|---|---|
| committer | 2024-12-03 08:35:45 -0800 | |
| commit | eca402eb44ed229336e25fda32dea31e4746f80c (patch) | |
| tree | 2851ba07f072ff8f4bbe90bc5a9958422624711a | |
| parent | 46759e76ca88e5df698b2da6321f0e8dc9091cfb (diff) | |
[flexiglass] Moves alternate bouncer (back) to its own window.
Fix: 379847843
Test: manually verified that hitting a notification while locked shows
the UDFPS alternate bouncer, that using the right/wrong fingerprint
works as expected to unlock/bring up the bouncer, that canceling out
from the bouncer and repeating, correctly does it again.
Flag: com.android.systemui.scene_container
Change-Id: I17877b823b4e2ae638b701a1fc3d686fda0b8f9a
8 files changed, 182 insertions, 64 deletions
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/AlternateBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/AlternateBouncer.kt index 1475795e2dc6..d02215083679 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/AlternateBouncer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/AlternateBouncer.kt @@ -18,6 +18,7 @@ package com.android.systemui.keyguard.ui.composable import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.Crossfade +import androidx.compose.animation.core.MutableTransitionState import androidx.compose.animation.core.tween import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut @@ -29,7 +30,9 @@ import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -60,13 +63,25 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi @Composable fun AlternateBouncer( alternateBouncerDependencies: AlternateBouncerDependencies, + onHideAnimationFinished: () -> Unit, modifier: Modifier = Modifier, ) { val isVisible by - alternateBouncerDependencies.viewModel.isVisible.collectAsStateWithLifecycle( - initialValue = false - ) + alternateBouncerDependencies.viewModel.isVisible.collectAsStateWithLifecycle(true) + val visibleState = remember { MutableTransitionState(isVisible) } + + // Feeds the isVisible value to the MutableTransitionState used by AnimatedVisibility below. + LaunchedEffect(isVisible) { visibleState.targetState = isVisible } + + // Watches the MutableTransitionState and calls onHideAnimationFinished when the fade out + // animation is finished. This way the window view is removed from the view hierarchy only after + // the fade out animation is complete. + LaunchedEffect(visibleState.currentState, visibleState.isIdle) { + if (!visibleState.currentState && visibleState.isIdle) { + onHideAnimationFinished() + } + } val udfpsIconLocation by alternateBouncerDependencies.udfpsIconViewModel.iconLocation.collectAsStateWithLifecycle( @@ -74,7 +89,7 @@ fun AlternateBouncer( ) AnimatedVisibility( - visible = isVisible, + visibleState = visibleState, enter = fadeIn(), exit = fadeOut(), modifier = modifier, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java index 0101e099084e..096439b1008d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java @@ -62,6 +62,7 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionBootInteractor; import com.android.systemui.keyguard.domain.interactor.StartKeyguardTransitionModule; import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransitionModule; +import com.android.systemui.keyguard.ui.view.AlternateBouncerWindowViewBinder; import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordancesCombinedViewModelModule; import com.android.systemui.log.SessionTracker; import com.android.systemui.navigationbar.NavigationModeController; @@ -248,4 +249,10 @@ public interface KeyguardModule { @IntoMap @ClassKey(KeyguardUpdateMonitor.class) CoreStartable bindsKeyguardUpdateMonitor(KeyguardUpdateMonitor keyguardUpdateMonitor); + + /***/ + @Binds + @IntoMap + @ClassKey(AlternateBouncerWindowViewBinder.class) + CoreStartable bindsAlternateBouncerWindowViewBinder(AlternateBouncerWindowViewBinder binder); } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt index 7a368999ecc9..33783b515763 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt @@ -16,9 +16,7 @@ package com.android.systemui.keyguard.ui.binder -import android.graphics.PixelFormat import android.util.Log -import android.view.Gravity import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -36,6 +34,7 @@ import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.deviceentry.ui.binder.UdfpsAccessibilityOverlayBinder import com.android.systemui.deviceentry.ui.view.UdfpsAccessibilityOverlay import com.android.systemui.deviceentry.ui.viewmodel.AlternateBouncerUdfpsAccessibilityOverlayViewModel +import com.android.systemui.keyguard.ui.view.AlternateBouncerWindowViewLayoutParams import com.android.systemui.keyguard.ui.view.DeviceEntryIconView import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerDependencies import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerUdfpsIconViewModel @@ -68,28 +67,6 @@ constructor( private val windowManager: Lazy<WindowManager>, private val layoutInflater: Lazy<LayoutInflater>, ) : CoreStartable { - private val layoutParams: WindowManager.LayoutParams - get() = - WindowManager.LayoutParams( - WindowManager.LayoutParams.MATCH_PARENT, - WindowManager.LayoutParams.MATCH_PARENT, - WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG, - WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN or - WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, - PixelFormat.TRANSLUCENT, - ) - .apply { - title = "AlternateBouncerView" - fitInsetsTypes = 0 // overrides default, avoiding status bars during layout - gravity = Gravity.TOP or Gravity.LEFT - layoutInDisplayCutoutMode = - WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS - privateFlags = - WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY or - WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION - // Avoid announcing window title. - accessibilityTitle = " " - } private var alternateBouncerView: ConstraintLayout? = null @@ -176,7 +153,9 @@ constructor( as ConstraintLayout Log.d(TAG, "Adding alternate bouncer view") - windowManager.get().addView(alternateBouncerView, layoutParams) + windowManager + .get() + .addView(alternateBouncerView, AlternateBouncerWindowViewLayoutParams.layoutParams) alternateBouncerView!!.addOnAttachStateChangeListener(onAttachAddBackGestureHandler) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/AlternateBouncerWindowViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/AlternateBouncerWindowViewBinder.kt new file mode 100644 index 000000000000..d3bc79eb89e2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/AlternateBouncerWindowViewBinder.kt @@ -0,0 +1,102 @@ +/* + * 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.keyguard.ui.view + +import android.content.Context +import android.view.View +import android.view.WindowManager +import android.widget.FrameLayout +import androidx.compose.ui.platform.ComposeView +import com.android.systemui.CoreStartable +import com.android.systemui.compose.ComposeInitializer +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.keyguard.ui.composable.AlternateBouncer +import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerDependencies +import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerViewModel +import com.android.systemui.lifecycle.repeatWhenAttached +import com.android.systemui.lifecycle.repeatWhenAttachedToWindow +import com.android.systemui.scene.shared.flag.SceneContainerFlag +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.awaitCancellation +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.launch + +/** Drives the showing and hiding of the alternate bouncer window. */ +@SysUISingleton +class AlternateBouncerWindowViewBinder +@Inject +constructor( + @Application private val applicationScope: CoroutineScope, + @Application private val context: Context, + private val viewModel: AlternateBouncerViewModel, + private val dependencies: AlternateBouncerDependencies, + private val windowManager: WindowManager, +) : CoreStartable { + + override fun start() { + if (!SceneContainerFlag.isEnabled) { + return + } + + applicationScope.launch { + viewModel.isVisible + .distinctUntilChanged() + .filter { it } + .collect { + windowManager.addView( + createView(), + AlternateBouncerWindowViewLayoutParams.layoutParams, + ) + } + } + } + + private fun createView(): View { + val root = FrameLayout(context) + val composeView = + ComposeView(context).apply { + setContent { + AlternateBouncer( + alternateBouncerDependencies = dependencies, + onHideAnimationFinished = { + if (root.isAttachedToWindow) { + windowManager.removeView(root) + } + }, + ) + } + } + + root.repeatWhenAttached { + root.repeatWhenAttachedToWindow { + try { + ComposeInitializer.onAttachedToWindow(root) + root.addView(composeView) + awaitCancellation() + } finally { + root.removeView(composeView) + ComposeInitializer.onDetachedFromWindow(root) + } + } + } + + return root + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/AlternateBouncerWindowViewLayoutParams.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/AlternateBouncerWindowViewLayoutParams.kt new file mode 100644 index 000000000000..5585c21aa217 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/AlternateBouncerWindowViewLayoutParams.kt @@ -0,0 +1,46 @@ +/* + * 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.keyguard.ui.view + +import android.graphics.PixelFormat +import android.view.Gravity +import android.view.WindowManager + +object AlternateBouncerWindowViewLayoutParams { + val layoutParams: WindowManager.LayoutParams + get() = + WindowManager.LayoutParams( + WindowManager.LayoutParams.MATCH_PARENT, + WindowManager.LayoutParams.MATCH_PARENT, + WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG, + WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN or + WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, + PixelFormat.TRANSLUCENT, + ) + .apply { + title = "AlternateBouncerView" + fitInsetsTypes = 0 // overrides default, avoiding status bars during layout + gravity = Gravity.TOP or Gravity.LEFT + layoutInDisplayCutoutMode = + WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS + privateFlags = + WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY or + WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION + // Avoid announcing window title. + accessibilityTitle = " " + } +} diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt index 7d7cab41cf96..c45906840385 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt @@ -5,7 +5,6 @@ import android.util.AttributeSet import android.view.MotionEvent import android.view.View import android.view.WindowInsets -import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerDependencies import com.android.systemui.qs.ui.adapter.QSSceneAdapter import com.android.systemui.scene.shared.model.SceneContainerConfig import com.android.systemui.scene.shared.model.SceneDataSourceDelegator @@ -35,7 +34,6 @@ class SceneWindowRootView(context: Context, attrs: AttributeSet?) : WindowRootVi layoutInsetController: LayoutInsetsController, sceneDataSourceDelegator: SceneDataSourceDelegator, qsSceneAdapter: Provider<QSSceneAdapter>, - alternateBouncerDependencies: AlternateBouncerDependencies, ) { setLayoutInsetsController(layoutInsetController) SceneWindowRootViewBinder.bind( @@ -54,7 +52,6 @@ class SceneWindowRootView(context: Context, attrs: AttributeSet?) : WindowRootVi }, dataSourceDelegator = sceneDataSourceDelegator, qsSceneAdapter = qsSceneAdapter, - alternateBouncerDependencies = alternateBouncerDependencies, ) } diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt index 1c15c74d5631..f7061d9af961 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt @@ -36,15 +36,12 @@ import com.android.internal.policy.ScreenDecorationsUtils import com.android.systemui.common.ui.compose.windowinsets.CutoutLocation import com.android.systemui.common.ui.compose.windowinsets.DisplayCutout import com.android.systemui.common.ui.compose.windowinsets.ScreenDecorProvider -import com.android.systemui.keyguard.ui.composable.AlternateBouncer -import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerDependencies import com.android.systemui.lifecycle.WindowLifecycleState import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.lifecycle.setSnapshotBinding import com.android.systemui.lifecycle.viewModel import com.android.systemui.qs.ui.adapter.QSSceneAdapter import com.android.systemui.res.R -import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.scene.shared.model.SceneContainerConfig import com.android.systemui.scene.shared.model.SceneDataSourceDelegator import com.android.systemui.scene.ui.composable.Overlay @@ -77,7 +74,6 @@ object SceneWindowRootViewBinder { onVisibilityChangedInternal: (isVisible: Boolean) -> Unit, dataSourceDelegator: SceneDataSourceDelegator, qsSceneAdapter: Provider<QSSceneAdapter>, - alternateBouncerDependencies: AlternateBouncerDependencies, ) { val unsortedSceneByKey: Map<SceneKey, Scene> = scenes.associateBy { scene -> scene.key } val sortedSceneByKey: Map<SceneKey, Scene> = @@ -148,20 +144,10 @@ object SceneWindowRootViewBinder { // the SceneContainerView. This SharedNotificationContainer should contain NSSL // due to the NotificationStackScrollLayoutSection (legacy) or // NotificationSection (scene container) moving it there. - if (SceneContainerFlag.isEnabled) { - (sharedNotificationContainer.parent as? ViewGroup)?.removeView( - sharedNotificationContainer - ) - view.addView(sharedNotificationContainer) - - // TODO(b/358354906): use an overlay for the alternate bouncer - view.addView( - createAlternateBouncerView( - context = view.context, - alternateBouncerDependencies = alternateBouncerDependencies, - ) - ) - } + (sharedNotificationContainer.parent as? ViewGroup)?.removeView( + sharedNotificationContainer + ) + view.addView(sharedNotificationContainer) view.setSnapshotBinding { onVisibilityChangedInternal(viewModel.isVisible) } awaitCancellation() @@ -206,17 +192,6 @@ object SceneWindowRootViewBinder { } } - private fun createAlternateBouncerView( - context: Context, - alternateBouncerDependencies: AlternateBouncerDependencies, - ): ComposeView { - return ComposeView(context).apply { - setContent { - AlternateBouncer(alternateBouncerDependencies = alternateBouncerDependencies) - } - } - } - // TODO(b/298525212): remove once Compose exposes window inset bounds. private fun displayCutoutFromWindowInsets( scope: CoroutineScope, diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt index 15b22700072f..8937ce33cd38 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt @@ -33,7 +33,6 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.flags.FeatureFlags import com.android.systemui.keyguard.ui.view.KeyguardRootView -import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerDependencies import com.android.systemui.privacy.OngoingPrivacyChip import com.android.systemui.qs.ui.adapter.QSSceneAdapter import com.android.systemui.res.R @@ -91,7 +90,6 @@ abstract class ShadeViewProviderModule { layoutInsetController: NotificationInsetsController, sceneDataSourceDelegator: Provider<SceneDataSourceDelegator>, qsSceneAdapter: Provider<QSSceneAdapter>, - alternateBouncerDependencies: Provider<AlternateBouncerDependencies>, ): WindowRootView { return if (SceneContainerFlag.isEnabled) { checkNoSceneDuplicates(scenesProvider.get()) @@ -107,7 +105,6 @@ abstract class ShadeViewProviderModule { layoutInsetController = layoutInsetController, sceneDataSourceDelegator = sceneDataSourceDelegator.get(), qsSceneAdapter = qsSceneAdapter, - alternateBouncerDependencies = alternateBouncerDependencies.get(), ) sceneWindowRootView } else { |