diff options
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, |