diff options
| author | 2023-11-01 19:51:06 +0000 | |
|---|---|---|
| committer | 2023-11-09 13:01:25 +0000 | |
| commit | ae54e5e41040b9c28f048b9e90bda55f5af444de (patch) | |
| tree | e7a4ba25b45453814476a1a0205b4f790e634c2f | |
| parent | 2568779789ff5f28648953bff586d56cd1f81e41 (diff) | |
Add split shade blueprint
This begins the separation into a specific large screen
blueprint. Adds logic to KeyguardBlueprintInteractor to determine
which blueprint should be active, based upon configuration.
For the moment, the only thing affected is NSSL placement and will be
followed by media.
Bug: 296370020
Test: atest KeyguardBlueprintRepositoryTest
DefaultKeyguardBlueprintTest KeyguardBlueprintInteractorTest
Test: Use foldables to test layouts
Flag: LEGACY MIGRATE_NSSL DISABLED
Change-Id: I147eb91c89a32c7f1535739f0bcc79d91f29dbf6
15 files changed, 434 insertions, 134 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprint.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprint.kt index d8d1dc0c11ef..702554ab4943 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprint.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprint.kt @@ -33,8 +33,8 @@ constructor( defaultCommunalWidgetSection: DefaultCommunalWidgetSection, ) : KeyguardBlueprint { override val id: String = COMMUNAL - override val sections: Set<KeyguardSection> = - setOf( + override val sections: List<KeyguardSection> = + listOf( defaultCommunalHubSection, defaultCommunalWidgetSection, ) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepository.kt index f5ef27daecdd..fbd62cef7dfb 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepository.kt @@ -17,33 +17,29 @@ package com.android.systemui.keyguard.data.repository +import android.util.Log import com.android.systemui.common.ui.data.repository.ConfigurationRepository import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.keyguard.shared.model.KeyguardBlueprint import com.android.systemui.keyguard.ui.view.layout.blueprints.DefaultKeyguardBlueprint.Companion.DEFAULT import com.android.systemui.keyguard.ui.view.layout.blueprints.KeyguardBlueprintModule import java.io.PrintWriter import java.util.TreeMap import javax.inject.Inject -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.asSharedFlow -import kotlinx.coroutines.launch /** * Manages blueprint changes for the lockscreen. * * To add a blueprint, create a class that implements LockscreenBlueprint and bind it to the map in - * the dagger module: + * the dagger module: [KeyguardBlueprintModule] * * A Blueprint determines how the layout should be constrained on a high level. * * A Section is a modular piece of code that implements the constraints. The blueprint uses the * sections to define the constraints. - * - * @see KeyguardBlueprintModule */ @SysUISingleton class KeyguardBlueprintRepository @@ -51,18 +47,16 @@ class KeyguardBlueprintRepository constructor( configurationRepository: ConfigurationRepository, blueprints: Set<@JvmSuppressWildcards KeyguardBlueprint>, - @Application private val applicationScope: CoroutineScope, ) { private val blueprintIdMap: TreeMap<String, KeyguardBlueprint> = TreeMap() private val _blueprint: MutableSharedFlow<KeyguardBlueprint> = MutableSharedFlow(replay = 1) val blueprint: Flow<KeyguardBlueprint> = _blueprint.asSharedFlow() + val configurationChange: Flow<Unit> = configurationRepository.onAnyConfigurationChange + init { blueprintIdMap.putAll(blueprints.associateBy { it.id }) applyBlueprint(blueprintIdMap[DEFAULT]!!) - applicationScope.launch { - configurationRepository.onAnyConfigurationChange.collect { refreshBlueprint() } - } } /** @@ -86,9 +80,18 @@ constructor( * @return whether the transition has succeeded. */ fun applyBlueprint(blueprintId: String?): Boolean { - val blueprint = blueprintIdMap[blueprintId] ?: return false - applyBlueprint(blueprint) - return true + val blueprint = blueprintIdMap[blueprintId] + return if (blueprint != null) { + applyBlueprint(blueprint) + true + } else { + Log.e( + TAG, + "Could not find blueprint with id: $blueprintId. " + + "Perhaps it was not added to KeyguardBlueprintModule?" + ) + false + } } /** Emits the blueprint value to the collectors. */ @@ -107,4 +110,8 @@ constructor( fun printBlueprints(pw: PrintWriter) { blueprintIdMap.onEachIndexed { index, entry -> pw.println("$index: ${entry.key}") } } + + companion object { + private const val TAG = "KeyguardBlueprintRepository" + } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt index 6ce91854ea56..7dab84dc7da3 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt @@ -17,16 +17,56 @@ package com.android.systemui.keyguard.domain.interactor +import android.content.Context import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.keyguard.data.repository.KeyguardBlueprintRepository +import com.android.systemui.keyguard.ui.view.layout.blueprints.DefaultKeyguardBlueprint +import com.android.systemui.keyguard.ui.view.layout.blueprints.SplitShadeKeyguardBlueprint +import com.android.systemui.statusbar.policy.SplitShadeStateController import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.onStart +import kotlinx.coroutines.launch @SysUISingleton class KeyguardBlueprintInteractor @Inject -constructor(private val keyguardBlueprintRepository: KeyguardBlueprintRepository) { +constructor( + private val keyguardBlueprintRepository: KeyguardBlueprintRepository, + @Application private val applicationScope: CoroutineScope, + private val context: Context, + private val splitShadeStateController: SplitShadeStateController, +) { + val blueprint = keyguardBlueprintRepository.blueprint + init { + applicationScope.launch { + keyguardBlueprintRepository.configurationChange + .onStart { emit(Unit) } + .collect { updateBlueprint() } + } + } + + /** + * Detects when a new blueprint should be applied and calls [transitionToBlueprint]. This may + * end up reapplying the same blueprint, which is fine as configuration may have changed. + */ + private fun updateBlueprint() { + val useSplitShade = + splitShadeStateController.shouldUseSplitNotificationShade(context.resources) + + val blueprintId = + if (useSplitShade) { + SplitShadeKeyguardBlueprint.ID + } else { + DefaultKeyguardBlueprint.DEFAULT + } + + transitionToBlueprint(blueprintId) + } + /** * Transitions to a blueprint. * diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardBlueprint.kt index 7fc1911a3477..344044019c0d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardBlueprint.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardBlueprint.kt @@ -22,7 +22,7 @@ import androidx.constraintlayout.widget.ConstraintSet /** Determines the constraints for the ConstraintSet in the lockscreen root view. */ interface KeyguardBlueprint { val id: String - val sections: Set<KeyguardSection> + val sections: List<KeyguardSection> /** * Removes views of old blueprint and add views of new blueprint. diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt index f1ea4469a2ae..2e64c41bace8 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt @@ -32,7 +32,6 @@ import com.android.systemui.keyguard.ui.view.layout.sections.DefaultShortcutsSec import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusBarSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusViewSection import com.android.systemui.keyguard.ui.view.layout.sections.SmartspaceSection -import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeGuidelines import javax.inject.Inject /** @@ -53,7 +52,6 @@ constructor( defaultStatusViewSection: DefaultStatusViewSection, defaultStatusBarSection: DefaultStatusBarSection, defaultNotificationStackScrollLayoutSection: DefaultNotificationStackScrollLayoutSection, - splitShadeGuidelines: SplitShadeGuidelines, aodNotificationIconsSection: AodNotificationIconsSection, aodBurnInSection: AodBurnInSection, communalTutorialIndicatorSection: CommunalTutorialIndicatorSection, @@ -63,7 +61,7 @@ constructor( override val id: String = DEFAULT override val sections = - setOf( + listOf( defaultIndicationAreaSection, defaultDeviceEntryIconSection, defaultShortcutsSection, @@ -72,7 +70,6 @@ constructor( defaultStatusViewSection, defaultStatusBarSection, defaultNotificationStackScrollLayoutSection, - splitShadeGuidelines, aodNotificationIconsSection, aodBurnInSection, communalTutorialIndicatorSection, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/KeyguardBlueprintModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/KeyguardBlueprintModule.kt index fda4c3d5376a..4f1a754adbd5 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/KeyguardBlueprintModule.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/KeyguardBlueprintModule.kt @@ -33,6 +33,12 @@ abstract class KeyguardBlueprintModule { @Binds @IntoSet + abstract fun bindSplitShadeBlueprint( + splitShadeBlueprint: SplitShadeKeyguardBlueprint + ): KeyguardBlueprint + + @Binds + @IntoSet abstract fun bindShortcutsBesideUdfpsLockscreenBlueprint( shortcutsBesideUdfpsLockscreenBlueprint: ShortcutsBesideUdfpsKeyguardBlueprint ): KeyguardBlueprint diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt index f8dd7c1a58c7..d8b368b4a0d3 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt @@ -52,7 +52,7 @@ constructor( override val id: String = SHORTCUTS_BESIDE_UDFPS override val sections = - setOf( + listOf( defaultIndicationAreaSection, defaultDeviceEntryIconSection, defaultAmbientIndicationAreaSection, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/SplitShadeKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/SplitShadeKeyguardBlueprint.kt new file mode 100644 index 000000000000..35679b84771b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/SplitShadeKeyguardBlueprint.kt @@ -0,0 +1,79 @@ +/* + * Copyright (C) 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.ui.view.layout.blueprints + +import com.android.systemui.communal.ui.view.layout.sections.CommunalTutorialIndicatorSection +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.shared.model.KeyguardBlueprint +import com.android.systemui.keyguard.ui.view.layout.sections.AodBurnInSection +import com.android.systemui.keyguard.ui.view.layout.sections.AodNotificationIconsSection +import com.android.systemui.keyguard.ui.view.layout.sections.DefaultAmbientIndicationAreaSection +import com.android.systemui.keyguard.ui.view.layout.sections.DefaultDeviceEntryIconSection +import com.android.systemui.keyguard.ui.view.layout.sections.DefaultIndicationAreaSection +import com.android.systemui.keyguard.ui.view.layout.sections.DefaultSettingsPopupMenuSection +import com.android.systemui.keyguard.ui.view.layout.sections.DefaultShortcutsSection +import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusBarSection +import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusViewSection +import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeGuidelines +import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeNotificationStackScrollLayoutSection +import javax.inject.Inject + +/** + * Split-shade layout, mostly used for larger devices like foldables and tablets when in landscape + * orientation. + */ +@SysUISingleton +@JvmSuppressWildcards +class SplitShadeKeyguardBlueprint +@Inject +constructor( + defaultIndicationAreaSection: DefaultIndicationAreaSection, + defaultDeviceEntryIconSection: DefaultDeviceEntryIconSection, + defaultShortcutsSection: DefaultShortcutsSection, + defaultAmbientIndicationAreaSection: DefaultAmbientIndicationAreaSection, + defaultSettingsPopupMenuSection: DefaultSettingsPopupMenuSection, + defaultStatusViewSection: DefaultStatusViewSection, + defaultStatusBarSection: DefaultStatusBarSection, + splitShadeNotificationStackScrollLayoutSection: SplitShadeNotificationStackScrollLayoutSection, + splitShadeGuidelines: SplitShadeGuidelines, + aodNotificationIconsSection: AodNotificationIconsSection, + aodBurnInSection: AodBurnInSection, + communalTutorialIndicatorSection: CommunalTutorialIndicatorSection, +) : KeyguardBlueprint { + override val id: String = ID + + override val sections = + listOf( + defaultIndicationAreaSection, + defaultDeviceEntryIconSection, + defaultShortcutsSection, + defaultAmbientIndicationAreaSection, + defaultSettingsPopupMenuSection, + defaultStatusViewSection, + defaultStatusBarSection, + splitShadeNotificationStackScrollLayoutSection, + splitShadeGuidelines, + aodNotificationIconsSection, + aodBurnInSection, + communalTutorialIndicatorSection, + ) + + companion object { + const val ID = "split-shade" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt index 199566129eb3..078fefff394a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt @@ -18,9 +18,6 @@ package com.android.systemui.keyguard.ui.view.layout.sections import android.content.Context -import android.view.View -import android.view.ViewGroup -import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet import androidx.constraintlayout.widget.ConstraintSet.BOTTOM import androidx.constraintlayout.widget.ConstraintSet.END @@ -29,55 +26,34 @@ import androidx.constraintlayout.widget.ConstraintSet.START import androidx.constraintlayout.widget.ConstraintSet.TOP import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags -import com.android.systemui.keyguard.shared.model.KeyguardSection import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel import com.android.systemui.res.R import com.android.systemui.shade.NotificationPanelView import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer -import com.android.systemui.statusbar.notification.stack.ui.viewbinder.SharedNotificationContainerBinder import com.android.systemui.statusbar.notification.stack.ui.viewmodel.SharedNotificationContainerViewModel import javax.inject.Inject +/** Single column format for notifications (default for phones) */ class DefaultNotificationStackScrollLayoutSection @Inject constructor( - private val context: Context, - private val featureFlags: FeatureFlags, - private val notificationPanelView: NotificationPanelView, - private val sharedNotificationContainer: SharedNotificationContainer, - private val sharedNotificationContainerViewModel: SharedNotificationContainerViewModel, - private val controller: NotificationStackScrollLayoutController, - private val smartspaceViewModel: KeyguardSmartspaceViewModel -) : KeyguardSection() { - private val placeHolderId = R.id.nssl_placeholder - - override fun addViews(constraintLayout: ConstraintLayout) { - if (!featureFlags.isEnabled(Flags.MIGRATE_NSSL)) { - return - } - // This moves the existing NSSL view to a different parent, as the controller is a - // singleton and recreating it has other bad side effects - notificationPanelView.findViewById<View?>(R.id.notification_stack_scroller)?.let { - (it.parent as ViewGroup).removeView(it) - sharedNotificationContainer.addNotificationStackScrollLayout(it) - } - - val view = View(context, null).apply { id = placeHolderId } - constraintLayout.addView(view) - } - - override fun bindData(constraintLayout: ConstraintLayout) { - if (!featureFlags.isEnabled(Flags.MIGRATE_NSSL)) { - return - } - SharedNotificationContainerBinder.bind( - sharedNotificationContainer, - sharedNotificationContainerViewModel, - controller, - ) - } - + context: Context, + featureFlags: FeatureFlags, + notificationPanelView: NotificationPanelView, + sharedNotificationContainer: SharedNotificationContainer, + sharedNotificationContainerViewModel: SharedNotificationContainerViewModel, + controller: NotificationStackScrollLayoutController, + private val smartspaceViewModel: KeyguardSmartspaceViewModel, +) : + NotificationStackScrollLayoutSection( + context, + featureFlags, + notificationPanelView, + sharedNotificationContainer, + sharedNotificationContainerViewModel, + controller, + ) { override fun applyConstraints(constraintSet: ConstraintSet) { if (!featureFlags.isEnabled(Flags.MIGRATE_NSSL)) { return @@ -85,41 +61,29 @@ constructor( constraintSet.apply { val bottomMargin = context.resources.getDimensionPixelSize(R.dimen.keyguard_status_view_bottom_margin) - val useSplitShade = - context.resources.getBoolean(R.bool.config_use_split_notification_shade) - val topAlignment = - if (useSplitShade) { - TOP - } else { - BOTTOM - } if (featureFlags.isEnabled(Flags.MIGRATE_CLOCKS_TO_BLUEPRINT)) { connect( R.id.nssl_placeholder, TOP, smartspaceViewModel.smartspaceViewId, - topAlignment, + BOTTOM, bottomMargin ) setGoneMargin(R.id.nssl_placeholder, TOP, bottomMargin) } else { - connect( - R.id.nssl_placeholder, - TOP, - R.id.keyguard_status_view, - topAlignment, - bottomMargin - ) + connect(R.id.nssl_placeholder, TOP, R.id.keyguard_status_view, BOTTOM, bottomMargin) } - connect(R.id.nssl_placeholder, START, PARENT_ID, START) connect(R.id.nssl_placeholder, END, PARENT_ID, END) - connect(R.id.nssl_placeholder, BOTTOM, R.id.lock_icon_view, TOP) - } - } - override fun removeViews(constraintLayout: ConstraintLayout) { - constraintLayout.removeView(placeHolderId) + val lockId = + if (featureFlags.isEnabled(Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS)) { + R.id.device_entry_icon_view + } else { + R.id.lock_icon_view + } + connect(R.id.nssl_placeholder, BOTTOM, lockId, TOP) + } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt new file mode 100644 index 000000000000..00966f235a57 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt @@ -0,0 +1,79 @@ +/* + * Copyright (C) 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.ui.view.layout.sections + +import android.content.Context +import android.view.View +import android.view.ViewGroup +import androidx.constraintlayout.widget.ConstraintLayout +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.shared.model.KeyguardSection +import com.android.systemui.res.R +import com.android.systemui.shade.NotificationPanelView +import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController +import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer +import com.android.systemui.statusbar.notification.stack.ui.viewbinder.SharedNotificationContainerBinder +import com.android.systemui.statusbar.notification.stack.ui.viewmodel.SharedNotificationContainerViewModel +import kotlinx.coroutines.DisposableHandle + +abstract class NotificationStackScrollLayoutSection +constructor( + protected val context: Context, + protected val featureFlags: FeatureFlags, + private val notificationPanelView: NotificationPanelView, + private val sharedNotificationContainer: SharedNotificationContainer, + private val sharedNotificationContainerViewModel: SharedNotificationContainerViewModel, + private val controller: NotificationStackScrollLayoutController, +) : KeyguardSection() { + private val placeHolderId = R.id.nssl_placeholder + private var disposableHandle: DisposableHandle? = null + + override fun addViews(constraintLayout: ConstraintLayout) { + if (!featureFlags.isEnabled(Flags.MIGRATE_NSSL)) { + return + } + // This moves the existing NSSL view to a different parent, as the controller is a + // singleton and recreating it has other bad side effects + notificationPanelView.findViewById<View?>(R.id.notification_stack_scroller)?.let { + (it.parent as ViewGroup).removeView(it) + sharedNotificationContainer.addNotificationStackScrollLayout(it) + } + + val view = View(context, null).apply { id = placeHolderId } + constraintLayout.addView(view) + } + + override fun bindData(constraintLayout: ConstraintLayout) { + if (!featureFlags.isEnabled(Flags.MIGRATE_NSSL)) { + return + } + disposableHandle?.dispose() + disposableHandle = + SharedNotificationContainerBinder.bind( + sharedNotificationContainer, + sharedNotificationContainerViewModel, + controller, + ) + } + + override fun removeViews(constraintLayout: ConstraintLayout) { + disposableHandle?.dispose() + constraintLayout.removeView(placeHolderId) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt new file mode 100644 index 000000000000..bf95c77229e9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt @@ -0,0 +1,89 @@ +/* + * Copyright (C) 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.ui.view.layout.sections + +import android.content.Context +import androidx.constraintlayout.widget.ConstraintSet +import androidx.constraintlayout.widget.ConstraintSet.BOTTOM +import androidx.constraintlayout.widget.ConstraintSet.END +import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID +import androidx.constraintlayout.widget.ConstraintSet.START +import androidx.constraintlayout.widget.ConstraintSet.TOP +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel +import com.android.systemui.res.R +import com.android.systemui.shade.NotificationPanelView +import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController +import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer +import com.android.systemui.statusbar.notification.stack.ui.viewmodel.SharedNotificationContainerViewModel +import javax.inject.Inject + +/** Large-screen format for notifications, shown as two columns on the device */ +class SplitShadeNotificationStackScrollLayoutSection +@Inject +constructor( + context: Context, + featureFlags: FeatureFlags, + notificationPanelView: NotificationPanelView, + sharedNotificationContainer: SharedNotificationContainer, + sharedNotificationContainerViewModel: SharedNotificationContainerViewModel, + controller: NotificationStackScrollLayoutController, + private val smartspaceViewModel: KeyguardSmartspaceViewModel, +) : + NotificationStackScrollLayoutSection( + context, + featureFlags, + notificationPanelView, + sharedNotificationContainer, + sharedNotificationContainerViewModel, + controller, + ) { + override fun applyConstraints(constraintSet: ConstraintSet) { + if (!featureFlags.isEnabled(Flags.MIGRATE_NSSL)) { + return + } + constraintSet.apply { + val bottomMargin = + context.resources.getDimensionPixelSize(R.dimen.keyguard_status_view_bottom_margin) + + if (featureFlags.isEnabled(Flags.MIGRATE_CLOCKS_TO_BLUEPRINT)) { + connect( + R.id.nssl_placeholder, + TOP, + smartspaceViewModel.smartspaceViewId, + TOP, + bottomMargin + ) + setGoneMargin(R.id.nssl_placeholder, TOP, bottomMargin) + } else { + connect(R.id.nssl_placeholder, TOP, R.id.keyguard_status_view, TOP, bottomMargin) + } + connect(R.id.nssl_placeholder, START, PARENT_ID, START) + connect(R.id.nssl_placeholder, END, PARENT_ID, END) + + val lockId = + if (featureFlags.isEnabled(Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS)) { + R.id.device_entry_icon_view + } else { + R.id.lock_icon_view + } + connect(R.id.nssl_placeholder, BOTTOM, lockId, TOP) + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt index 6785da4bf4f1..a1a0ccac3500 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt @@ -22,6 +22,7 @@ import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer import com.android.systemui.statusbar.notification.stack.ui.viewmodel.SharedNotificationContainerViewModel +import kotlinx.coroutines.DisposableHandle import kotlinx.coroutines.launch /** Binds the shared notification container to its view-model. */ @@ -32,39 +33,46 @@ object SharedNotificationContainerBinder { view: SharedNotificationContainer, viewModel: SharedNotificationContainerViewModel, controller: NotificationStackScrollLayoutController, - ) { - view.repeatWhenAttached { - repeatOnLifecycle(Lifecycle.State.CREATED) { - launch { - viewModel.configurationBasedDimensions.collect { - view.updateConstraints( - useSplitShade = it.useSplitShade, - marginStart = it.marginStart, - marginTop = it.marginTop, - marginEnd = it.marginEnd, - marginBottom = it.marginBottom, - ) + ): DisposableHandle { + val disposableHandle = + view.repeatWhenAttached { + repeatOnLifecycle(Lifecycle.State.CREATED) { + launch { + viewModel.configurationBasedDimensions.collect { + view.updateConstraints( + useSplitShade = it.useSplitShade, + marginStart = it.marginStart, + marginTop = it.marginTop, + marginEnd = it.marginEnd, + marginBottom = it.marginBottom, + ) - controller.setOverExpansion(0f) - controller.setOverScrollAmount(0) - controller.updateFooter() + controller.setOverExpansion(0f) + controller.setOverScrollAmount(0) + controller.updateFooter() + } } - } - launch { - viewModel.maxNotifications.collect { - controller.setMaxDisplayedNotifications(it) + launch { + viewModel.maxNotifications.collect { + controller.setMaxDisplayedNotifications(it) + } } - } - launch { - viewModel.position.collect { - val animate = it.animate || controller.isAddOrRemoveAnimationPending() - controller.updateTopPadding(it.top, animate) + launch { + viewModel.position.collect { + val animate = it.animate || controller.isAddOrRemoveAnimationPending() + controller.updateTopPadding(it.top, animate) + } } + + launch { viewModel.translationY.collect { controller.setTranslationY(it) } } } + } - launch { viewModel.translationY.collect { controller.setTranslationY(it) } } + return object : DisposableHandle { + override fun dispose() { + disposableHandle.dispose() } } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryTest.kt index 66ead14d3d4c..5852bdb5c351 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryTest.kt @@ -25,10 +25,8 @@ import com.android.systemui.keyguard.ui.view.layout.blueprints.DefaultKeyguardBl import com.android.systemui.keyguard.ui.view.layout.blueprints.DefaultKeyguardBlueprint.Companion.DEFAULT import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestScope -import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test @@ -43,20 +41,16 @@ class KeyguardBlueprintRepositoryTest : SysuiTestCase() { private lateinit var underTest: KeyguardBlueprintRepository @Mock lateinit var configurationRepository: ConfigurationRepository @Mock lateinit var defaultLockscreenBlueprint: DefaultKeyguardBlueprint - private val testDispatcher = StandardTestDispatcher() - private val testScope = TestScope(testDispatcher) - private val configurationFlow = MutableSharedFlow<Unit>(extraBufferCapacity = 1) + private val testScope = TestScope(StandardTestDispatcher()) @Before fun setup() { MockitoAnnotations.initMocks(this) whenever(defaultLockscreenBlueprint.id).thenReturn(DEFAULT) - whenever(configurationRepository.onAnyConfigurationChange).thenReturn(configurationFlow) underTest = KeyguardBlueprintRepository( configurationRepository, setOf(defaultLockscreenBlueprint), - testScope.backgroundScope, ) } @@ -64,20 +58,7 @@ class KeyguardBlueprintRepositoryTest : SysuiTestCase() { fun testApplyBlueprint_DefaultLayout() { testScope.runTest { val blueprint by collectLastValue(underTest.blueprint) - runCurrent() underTest.applyBlueprint(defaultLockscreenBlueprint) - runCurrent() - assertThat(blueprint).isEqualTo(defaultLockscreenBlueprint) - } - } - - @Test - fun testConfigurationChange() { - testScope.runTest { - val blueprint by collectLastValue(underTest.blueprint) - runCurrent() - configurationFlow.tryEmit(Unit) - runCurrent() assertThat(blueprint).isEqualTo(defaultLockscreenBlueprint) } } @@ -86,9 +67,7 @@ class KeyguardBlueprintRepositoryTest : SysuiTestCase() { fun testRefreshBlueprint() { testScope.runTest { val blueprint by collectLastValue(underTest.blueprint) - runCurrent() underTest.refreshBlueprint() - runCurrent() assertThat(blueprint).isEqualTo(defaultLockscreenBlueprint) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt index b8a2e9d4afc7..9fe40d73fa1d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt @@ -21,24 +21,77 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.keyguard.data.repository.KeyguardBlueprintRepository +import com.android.systemui.keyguard.ui.view.layout.blueprints.DefaultKeyguardBlueprint +import com.android.systemui.keyguard.ui.view.layout.blueprints.SplitShadeKeyguardBlueprint +import com.android.systemui.statusbar.policy.SplitShadeStateController +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.whenever +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock +import org.mockito.Mockito.reset import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @SmallTest @RunWith(AndroidJUnit4::class) class KeyguardBlueprintInteractorTest : SysuiTestCase() { + private val configurationFlow = MutableSharedFlow<Unit>(extraBufferCapacity = 1) private lateinit var underTest: KeyguardBlueprintInteractor + private lateinit var testScope: TestScope + @Mock private lateinit var splitShadeStateController: SplitShadeStateController @Mock private lateinit var keyguardBlueprintRepository: KeyguardBlueprintRepository @Before fun setup() { MockitoAnnotations.initMocks(this) - underTest = KeyguardBlueprintInteractor(keyguardBlueprintRepository) + testScope = TestScope(StandardTestDispatcher()) + whenever(keyguardBlueprintRepository.configurationChange).thenReturn(configurationFlow) + + underTest = + KeyguardBlueprintInteractor( + keyguardBlueprintRepository, + testScope.backgroundScope, + mContext, + splitShadeStateController, + ) + } + + @Test + fun testAppliesDefaultBlueprint() { + testScope.runTest { + whenever(splitShadeStateController.shouldUseSplitNotificationShade(any())) + .thenReturn(false) + + reset(keyguardBlueprintRepository) + configurationFlow.tryEmit(Unit) + runCurrent() + + verify(keyguardBlueprintRepository) + .applyBlueprint(DefaultKeyguardBlueprint.Companion.DEFAULT) + } + } + + @Test + fun testAppliesSplitShadeBlueprint() { + testScope.runTest { + whenever(splitShadeStateController.shouldUseSplitNotificationShade(any())) + .thenReturn(true) + + reset(keyguardBlueprintRepository) + configurationFlow.tryEmit(Unit) + runCurrent() + + verify(keyguardBlueprintRepository) + .applyBlueprint(SplitShadeKeyguardBlueprint.Companion.ID) + } } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt index 2831053184cc..43d70adf26b0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt @@ -86,7 +86,6 @@ class DefaultKeyguardBlueprintTest : SysuiTestCase() { defaultStatusViewSection, defaultStatusBarViewSection, defaultNSSLSection, - splitShadeGuidelines, aodNotificationIconsSection, aodBurnInSection, communalTutorialIndicatorSection, |