diff options
14 files changed, 350 insertions, 53 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/HeadsUpNotificationIconViewStateRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/HeadsUpNotificationIconViewStateRepository.kt new file mode 100644 index 000000000000..afed6bef58fc --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/HeadsUpNotificationIconViewStateRepository.kt @@ -0,0 +1,30 @@ +/* + * 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.statusbar.notification.data.repository + +import android.graphics.Rect +import com.android.systemui.dagger.SysUISingleton +import javax.inject.Inject +import kotlinx.coroutines.flow.MutableStateFlow + +/** View-states pertaining to heads-up notification icons. */ +@SysUISingleton +class HeadsUpNotificationIconViewStateRepository @Inject constructor() { + /** Notification key for a notification icon to show isolated, or `null` if none. */ + val isolatedNotification = MutableStateFlow<String?>(null) + /** Area to display the isolated notification, or `null` if none. */ + val isolatedIconLocation = MutableStateFlow<Rect?>(null) +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationIconInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationIconInteractor.kt new file mode 100644 index 000000000000..17b6e9f572c9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationIconInteractor.kt @@ -0,0 +1,44 @@ +/* + * 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.statusbar.notification.domain.interactor + +import android.graphics.Rect +import com.android.systemui.statusbar.notification.data.repository.HeadsUpNotificationIconViewStateRepository +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow + +/** Domain logic pertaining to heads up notification icons. */ +class HeadsUpNotificationIconInteractor +@Inject +constructor( + private val repository: HeadsUpNotificationIconViewStateRepository, +) { + /** Notification key for a notification icon to show isolated, or `null` if none. */ + val isolatedIconLocation: Flow<Rect?> = repository.isolatedIconLocation + + /** Area to display the isolated notification, or `null` if none. */ + val isolatedNotification: Flow<String?> = repository.isolatedNotification + + /** Updates the location where isolated notification icons are shown. */ + fun setIsolatedIconLocation(rect: Rect?) { + repository.isolatedIconLocation.value = rect + } + + /** Updates which notification will have its icon displayed isolated. */ + fun setIsolatedIconNotificationKey(key: String?) { + repository.isolatedNotification.value = key + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImpl.kt index d8a5f01fc6c8..593ed4766ed0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImpl.kt @@ -136,13 +136,10 @@ constructor( override fun updateAodNotificationIcons() = unsupported - override fun showIconIsolated(icon: StatusBarIconView?, animated: Boolean) { - notificationIcons!!.showIconIsolated(icon, animated) - } + override fun showIconIsolated(icon: StatusBarIconView?, animated: Boolean) = unsupported - override fun setIsolatedIconLocation(iconDrawingRect: Rect, requireStateUpdate: Boolean) { - notificationIcons!!.setIsolatedIconLocation(iconDrawingRect, requireStateUpdate) - } + override fun setIsolatedIconLocation(iconDrawingRect: Rect, requireStateUpdate: Boolean) = + unsupported override fun setAnimationsEnabled(enabled: Boolean) = unsupported diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt index a78fd9dfca43..aa559e0601ed 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt @@ -77,26 +77,8 @@ object NotificationIconContainerViewBinder { val contrastColorUtil = ContrastColorUtil.getInstance(view.context) return view.repeatWhenAttached { repeatOnLifecycle(Lifecycle.State.CREATED) { - launch { viewModel.animationsEnabled.collect(view::setAnimationsEnabled) } - launch { - viewModel.isDozing.collect { isDozing -> - if (isDozing.isAnimating) { - val animate = !dozeParameters.displayNeedsBlanking - view.setDozing( - /* dozing = */ isDozing.value, - /* fade = */ animate, - /* delay = */ 0, - /* endRunnable = */ isDozing::stopAnimating, - ) - } else { - view.setDozing( - /* dozing = */ isDozing.value, - /* fade= */ false, - /* delay= */ 0, - ) - } - } - } + launch { bindAnimationsEnabled(viewModel, view) } + launch { bindIsDozing(viewModel, view, dozeParameters) } // TODO(b/278765923): this should live where AOD is bound, not inside of the NIC // view-binder launch { @@ -108,11 +90,7 @@ object NotificationIconContainerViewBinder { screenOffAnimationController, ) } - launch { - viewModel.iconColors - .mapNotNull { lookup -> lookup.iconColors(view.viewBounds) } - .collect { iconLookup -> applyTint(view, iconLookup, contrastColorUtil) } - } + launch { bindIconColors(viewModel, view, contrastColorUtil) } launch { bindIconViewData( viewModel, @@ -122,6 +100,72 @@ object NotificationIconContainerViewBinder { viewStore, ) } + launch { bindIsolatedIcon(viewModel, view, viewStore) } + } + } + } + + private suspend fun bindAnimationsEnabled( + viewModel: NotificationIconContainerViewModel, + view: NotificationIconContainer + ) { + viewModel.animationsEnabled.collect(view::setAnimationsEnabled) + } + + private suspend fun bindIconColors( + viewModel: NotificationIconContainerViewModel, + view: NotificationIconContainer, + contrastColorUtil: ContrastColorUtil, + ) { + viewModel.iconColors + .mapNotNull { lookup -> lookup.iconColors(view.viewBounds) } + .collect { iconLookup -> applyTint(view, iconLookup, contrastColorUtil) } + } + + private suspend fun bindIsDozing( + viewModel: NotificationIconContainerViewModel, + view: NotificationIconContainer, + dozeParameters: DozeParameters, + ) { + viewModel.isDozing.collect { isDozing -> + if (isDozing.isAnimating) { + val animate = !dozeParameters.displayNeedsBlanking + view.setDozing( + /* dozing = */ isDozing.value, + /* fade = */ animate, + /* delay = */ 0, + /* endRunnable = */ isDozing::stopAnimating, + ) + } else { + view.setDozing( + /* dozing = */ isDozing.value, + /* fade= */ false, + /* delay= */ 0, + ) + } + } + } + + private suspend fun bindIsolatedIcon( + viewModel: NotificationIconContainerViewModel, + view: NotificationIconContainer, + viewStore: IconViewStore, + ) { + coroutineScope { + launch { + viewModel.isolatedIconLocation.collect { location -> + view.setIsolatedIconLocation(location, true) + } + } + launch { + viewModel.isolatedIcon.collect { iconInfo -> + val iconView = iconInfo.value?.let { viewStore.iconView(it.notifKey) } + if (iconInfo.isAnimating) { + view.showIconIsolatedAnimated(iconView, iconInfo::stopAnimating) + } else { + view.showIconIsolated(iconView) + } + } } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModel.kt index e6788fb858bf..120d342b18d8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModel.kt @@ -33,6 +33,7 @@ import com.android.systemui.statusbar.notification.domain.interactor.Notificatio import com.android.systemui.statusbar.notification.icon.domain.interactor.AlwaysOnDisplayNotificationIconsInteractor import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerViewModel.ColorLookup import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerViewModel.IconColors +import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerViewModel.IconInfo import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerViewModel.IconsViewData import com.android.systemui.statusbar.phone.DozeParameters import com.android.systemui.statusbar.phone.ScreenOffAnimationController @@ -46,6 +47,8 @@ import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map /** View-model for the row of notification icons displayed on the always-on display. */ @@ -140,6 +143,10 @@ constructor( ) } + override val isolatedIcon: Flow<AnimatedValue<IconInfo?>> = + flowOf(AnimatedValue.NotAnimating(null)) + override val isolatedIconLocation: Flow<Rect> = emptyFlow() + /** Is there an expanded pulse, are we animating in response? */ private fun isPulseExpandingAnimated(): Flow<AnimatedValue<Boolean>> { return notificationsKeyguardInteractor.isPulseExpanding diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerShelfViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerShelfViewModel.kt index b8e0b5828831..c6aabb7527da 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerShelfViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerShelfViewModel.kt @@ -15,8 +15,10 @@ */ package com.android.systemui.statusbar.notification.icon.ui.viewmodel +import android.graphics.Rect import com.android.systemui.statusbar.notification.icon.domain.interactor.NotificationIconsInteractor import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerViewModel.ColorLookup +import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerViewModel.IconInfo import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerViewModel.IconsViewData import com.android.systemui.util.ui.AnimatedValue import javax.inject.Inject @@ -36,6 +38,9 @@ constructor( override val isDozing: Flow<AnimatedValue<Boolean>> = emptyFlow() override val isVisible: Flow<AnimatedValue<Boolean>> = emptyFlow() override val iconColors: Flow<ColorLookup> = emptyFlow() + override val isolatedIcon: Flow<AnimatedValue<IconInfo?>> = + flowOf(AnimatedValue.NotAnimating(null)) + override val isolatedIconLocation: Flow<Rect> = emptyFlow() override val iconsViewData: Flow<IconsViewData> = interactor.filteredNotifSet().map { entries -> diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt index 046d364a407b..4d14024fcd99 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt @@ -20,16 +20,23 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.plugins.DarkIconDispatcher import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor +import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationIconInteractor import com.android.systemui.statusbar.notification.icon.domain.interactor.StatusBarNotificationIconsInteractor import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerViewModel.ColorLookup import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerViewModel.IconColors +import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerViewModel.IconInfo import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerViewModel.IconsViewData import com.android.systemui.statusbar.phone.domain.interactor.DarkIconInteractor +import com.android.systemui.util.kotlin.pairwise +import com.android.systemui.util.kotlin.sample +import com.android.systemui.util.ui.AnimatableEvent import com.android.systemui.util.ui.AnimatedValue +import com.android.systemui.util.ui.toAnimatedValueFlow import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.map /** View-model for the row of notification icons displayed in the status bar, */ @@ -38,6 +45,7 @@ class NotificationIconContainerStatusBarViewModel constructor( darkIconInteractor: DarkIconInteractor, iconsInteractor: StatusBarNotificationIconsInteractor, + headsUpIconInteractor: HeadsUpNotificationIconInteractor, keyguardInteractor: KeyguardInteractor, notificationsInteractor: ActiveNotificationsInteractor, shadeInteractor: ShadeInteractor, @@ -76,6 +84,30 @@ constructor( ) } + override val isolatedIcon: Flow<AnimatedValue<IconInfo?>> = + headsUpIconInteractor.isolatedNotification + .pairwise(initialValue = null) + .sample(combine(iconsViewData, shadeInteractor.shadeExpansion, ::Pair)) { + (prev, isolatedNotif), + (iconsViewData, shadeExpansion), + -> + val iconInfo = + isolatedNotif?.let { + iconsViewData.visibleKeys.firstOrNull { it.notifKey == isolatedNotif } + } + val animate = + when { + isolatedNotif == prev -> false + isolatedNotif == null || prev == null -> shadeExpansion == 0f + else -> false + } + AnimatableEvent(iconInfo, animate) + } + .toAnimatedValueFlow() + + override val isolatedIconLocation: Flow<Rect> = + headsUpIconInteractor.isolatedIconLocation.filterNotNull() + private class IconColorsImpl( override val tint: Int, private val areas: Collection<Rect>, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerViewModel.kt index 236a2371d9dd..a611323201e3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerViewModel.kt @@ -45,6 +45,12 @@ interface NotificationIconContainerViewModel { /** [IconsViewData] indicating which icons to display in the view. */ val iconsViewData: Flow<IconsViewData> + /** An Icon to show "isolated" in the IconContainer. */ + val isolatedIcon: Flow<AnimatedValue<IconInfo?>> + + /** Location to show an isolated icon, if there is one. */ + val isolatedIconLocation: Flow<Rect> + /** * Lookup the colors to use for the notification icons based on the bounds of the icon * container. A result of `null` indicates that no color changes should be applied. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java index c493eeda7077..8fee5c0487f3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java @@ -26,6 +26,8 @@ import androidx.annotation.NonNull; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.widget.ViewClippingUtil; +import com.android.systemui.flags.FeatureFlagsClassic; +import com.android.systemui.flags.Flags; import com.android.systemui.plugins.DarkIconDispatcher; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.res.R; @@ -38,6 +40,7 @@ import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; import com.android.systemui.statusbar.notification.SourceType; import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationIconInteractor; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; @@ -104,6 +107,8 @@ public class HeadsUpAppearanceController extends ViewController<HeadsUpStatusBar }; private boolean mAnimationsEnabled = true; private final KeyguardStateController mKeyguardStateController; + private final FeatureFlagsClassic mFeatureFlags; + private final HeadsUpNotificationIconInteractor mHeadsUpNotificationIconInteractor; @VisibleForTesting @Inject @@ -122,6 +127,8 @@ public class HeadsUpAppearanceController extends ViewController<HeadsUpStatusBar NotificationRoundnessManager notificationRoundnessManager, HeadsUpStatusBarView headsUpStatusBarView, Clock clockView, + FeatureFlagsClassic featureFlags, + HeadsUpNotificationIconInteractor headsUpNotificationIconInteractor, @Named(OPERATOR_NAME_FRAME_VIEW) Optional<View> operatorNameViewOptional) { super(headsUpStatusBarView); mNotificationIconAreaController = notificationIconAreaController; @@ -139,6 +146,8 @@ public class HeadsUpAppearanceController extends ViewController<HeadsUpStatusBar mStackScrollerController = stackScrollerController; mShadeViewController = shadeViewController; + mFeatureFlags = featureFlags; + mHeadsUpNotificationIconInteractor = headsUpNotificationIconInteractor; mStackScrollerController.setHeadsUpAppearanceController(this); mClockView = clockView; mOperatorNameViewOptional = operatorNameViewOptional; @@ -170,6 +179,9 @@ public class HeadsUpAppearanceController extends ViewController<HeadsUpStatusBar mHeadsUpManager.addListener(this); mView.setOnDrawingRectChangedListener( () -> updateIsolatedIconLocation(true /* requireUpdate */)); + if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR)) { + updateIsolatedIconLocation(true); + } mWakeUpCoordinator.addListener(this); getShadeHeadsUpTracker().addTrackingHeadsUpListener(mSetTrackingHeadsUp); getShadeHeadsUpTracker().setHeadsUpAppearanceController(this); @@ -185,6 +197,9 @@ public class HeadsUpAppearanceController extends ViewController<HeadsUpStatusBar protected void onViewDetached() { mHeadsUpManager.removeListener(this); mView.setOnDrawingRectChangedListener(null); + if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR)) { + mHeadsUpNotificationIconInteractor.setIsolatedIconLocation(null); + } mWakeUpCoordinator.removeListener(this); getShadeHeadsUpTracker().removeTrackingHeadsUpListener(mSetTrackingHeadsUp); getShadeHeadsUpTracker().setHeadsUpAppearanceController(null); @@ -193,8 +208,13 @@ public class HeadsUpAppearanceController extends ViewController<HeadsUpStatusBar } private void updateIsolatedIconLocation(boolean requireStateUpdate) { - mNotificationIconAreaController.setIsolatedIconLocation( - mView.getIconDrawingRect(), requireStateUpdate); + if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR)) { + mHeadsUpNotificationIconInteractor + .setIsolatedIconLocation(mView.getIconDrawingRect()); + } else { + mNotificationIconAreaController.setIsolatedIconLocation( + mView.getIconDrawingRect(), requireStateUpdate); + } } @Override @@ -230,9 +250,14 @@ public class HeadsUpAppearanceController extends ViewController<HeadsUpStatusBar setShown(true); animateIsolation = !isExpanded(); } - updateIsolatedIconLocation(false /* requireUpdate */); - mNotificationIconAreaController.showIconIsolated(newEntry == null ? null - : newEntry.getIcons().getStatusBarIcon(), animateIsolation); + if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR)) { + mHeadsUpNotificationIconInteractor.setIsolatedIconNotificationKey( + newEntry == null ? null : newEntry.getKey()); + } else { + updateIsolatedIconLocation(false /* requireUpdate */); + mNotificationIconAreaController.showIconIsolated(newEntry == null ? null + : newEntry.getIcons().getStatusBarIcon(), animateIsolation); + } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java index d70edbf81d38..535f6acab5be 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java @@ -50,9 +50,7 @@ import com.android.systemui.statusbar.notification.stack.ViewState; import java.util.ArrayList; import java.util.HashMap; -import java.util.Map; import java.util.function.Consumer; -import java.util.stream.Collectors; /** * A container for notification icons. It handles overflowing icons properly and positions them @@ -175,6 +173,7 @@ public class NotificationIconContainer extends ViewGroup { private final int[] mAbsolutePosition = new int[2]; private View mIsolatedIconForAnimation; private int mThemedTextColorPrimary; + private Runnable mIsolatedIconAnimationEndRunnable; public NotificationIconContainer(Context context, AttributeSet attrs) { super(context, attrs); @@ -705,10 +704,26 @@ public class NotificationIconContainer extends ViewGroup { mReplacingIcons = replacingIcons; } + @Deprecated public void showIconIsolated(StatusBarIconView icon, boolean animated) { + mIconContainerRefactorFlag.assertInLegacyMode(); if (animated) { - mIsolatedIconForAnimation = icon != null ? icon : mIsolatedIcon; + showIconIsolatedAnimated(icon, null); + } else { + showIconIsolated(icon); } + } + + public void showIconIsolatedAnimated(StatusBarIconView icon, + @Nullable Runnable onAnimationEnd) { + if (mIconContainerRefactorFlag.isUnexpectedlyInLegacyMode()) return; + mIsolatedIconForAnimation = icon != null ? icon : mIsolatedIcon; + mIsolatedIconAnimationEndRunnable = onAnimationEnd; + showIconIsolated(icon); + } + + public void showIconIsolated(StatusBarIconView icon) { + if (mIconContainerRefactorFlag.isUnexpectedlyInLegacyMode()) return; mIsolatedIcon = icon; updateState(); } @@ -833,6 +848,11 @@ public class NotificationIconContainer extends ViewGroup { animationProperties = UNISOLATION_PROPERTY; animationProperties.setDelay( mIsolatedIcon != null ? CONTENT_FADE_DELAY : 0); + Consumer<Property> endAction = getEndAction(); + if (endAction != null) { + animationProperties.setAnimationEndAction(endAction); + animationProperties.setAnimationCancelAction(endAction); + } } else { animationProperties = UNISOLATION_PROPERTY_OTHERS; animationProperties.setDelay( @@ -856,6 +876,18 @@ public class NotificationIconContainer extends ViewGroup { needsCannedAnimation = false; } + @Nullable + private Consumer<Property> getEndAction() { + if (mIsolatedIconAnimationEndRunnable == null) return null; + final Runnable endRunnable = mIsolatedIconAnimationEndRunnable; + return prop -> { + endRunnable.run(); + if (mIsolatedIconAnimationEndRunnable == endRunnable) { + mIsolatedIconAnimationEndRunnable = null; + } + }; + } + @Override public void initFrom(View view) { super.initFrom(view); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt index 9988fc783a07..ec80e5f8821c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt @@ -26,7 +26,7 @@ import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepositor import com.android.systemui.statusbar.data.repository.NotificationListenerSettingsRepository import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository import com.android.systemui.statusbar.notification.data.repository.FakeNotificationsKeyguardViewStateRepository -import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel +import com.android.systemui.statusbar.notification.shared.activeNotificationModel import com.android.systemui.statusbar.notification.shared.byIsAmbient import com.android.systemui.statusbar.notification.shared.byIsLastMessageFromReply import com.android.systemui.statusbar.notification.shared.byIsPulsing @@ -388,30 +388,30 @@ class StatusBarNotificationIconsInteractorTest : SysuiTestCase() { private val testIcons = listOf( - ActiveNotificationModel( + activeNotificationModel( key = "notif1", ), - ActiveNotificationModel( + activeNotificationModel( key = "notif2", isAmbient = true, ), - ActiveNotificationModel( + activeNotificationModel( key = "notif3", isRowDismissed = true, ), - ActiveNotificationModel( + activeNotificationModel( key = "notif4", isSilent = true, ), - ActiveNotificationModel( + activeNotificationModel( key = "notif5", isLastMessageFromReply = true, ), - ActiveNotificationModel( + activeNotificationModel( key = "notif6", isSuppressedFromStatusBar = true, ), - ActiveNotificationModel( + activeNotificationModel( key = "notif7", isPulsing = true, ), diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt index e21ebeb77f96..ba68fbb981e6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt @@ -18,6 +18,7 @@ package com.android.systemui.statusbar.notification.icon.ui.viewmodel import android.graphics.Rect +import android.graphics.drawable.Icon import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.SysUITestModule @@ -39,12 +40,19 @@ import com.android.systemui.plugins.DarkIconDispatcher import com.android.systemui.power.data.repository.FakePowerRepository import com.android.systemui.power.shared.model.WakeSleepReason import com.android.systemui.power.shared.model.WakefulnessState +import com.android.systemui.shade.data.repository.FakeShadeRepository +import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository +import com.android.systemui.statusbar.notification.data.repository.HeadsUpNotificationIconViewStateRepository +import com.android.systemui.statusbar.notification.shared.activeNotificationModel import com.android.systemui.statusbar.phone.DozeParameters import com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher import com.android.systemui.statusbar.phone.data.repository.FakeDarkIconRepository import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository import com.android.systemui.user.domain.UserDomainLayerModule +import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever +import com.android.systemui.util.ui.isAnimating +import com.android.systemui.util.ui.value import com.google.common.truth.Truth.assertThat import dagger.BindsInstance import dagger.Component @@ -327,6 +335,58 @@ class NotificationIconContainerStatusBarViewModelTest : SysuiTestCase() { } } + @Test + fun isolatedIcon_animateOnAppear_shadeCollapsed() = + with(testComponent) { + scope.runTest { + val icon: Icon = mock() + shadeRepository.setLegacyShadeExpansion(0f) + activeNotificationsRepository.activeNotifications.value = + listOf( + activeNotificationModel( + key = "notif1", + groupKey = "group", + statusBarIcon = icon + ) + ) + .associateBy { it.key } + val isolatedIcon by collectLastValue(underTest.isolatedIcon) + runCurrent() + + headsUpViewStateRepository.isolatedNotification.value = "notif1" + runCurrent() + + assertThat(isolatedIcon?.value?.notifKey).isEqualTo("notif1") + assertThat(isolatedIcon?.isAnimating).isTrue() + } + } + + @Test + fun isolatedIcon_dontAnimateOnAppear_shadeExpanded() = + with(testComponent) { + scope.runTest { + val icon: Icon = mock() + shadeRepository.setLegacyShadeExpansion(.5f) + activeNotificationsRepository.activeNotifications.value = + listOf( + activeNotificationModel( + key = "notif1", + groupKey = "group", + statusBarIcon = icon + ) + ) + .associateBy { it.key } + val isolatedIcon by collectLastValue(underTest.isolatedIcon) + runCurrent() + + headsUpViewStateRepository.isolatedNotification.value = "notif1" + runCurrent() + + assertThat(isolatedIcon?.value?.notifKey).isEqualTo("notif1") + assertThat(isolatedIcon?.isAnimating).isFalse() + } + } + @SysUISingleton @Component( modules = @@ -340,11 +400,14 @@ class NotificationIconContainerStatusBarViewModelTest : SysuiTestCase() { val underTest: NotificationIconContainerStatusBarViewModel + val activeNotificationsRepository: ActiveNotificationListRepository val darkIconRepository: FakeDarkIconRepository val deviceProvisioningRepository: FakeDeviceProvisioningRepository + val headsUpViewStateRepository: HeadsUpNotificationIconViewStateRepository val keyguardTransitionRepository: FakeKeyguardTransitionRepository val keyguardRepository: FakeKeyguardRepository val powerRepository: FakePowerRepository + val shadeRepository: FakeShadeRepository val scope: TestScope @Component.Factory diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shared/TestActiveNotificationModel.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shared/TestActiveNotificationModel.kt index 0eac1ae6ab44..ca105f3e52ea 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shared/TestActiveNotificationModel.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shared/TestActiveNotificationModel.kt @@ -15,6 +15,7 @@ package com.android.systemui.statusbar.notification.shared +import android.graphics.drawable.Icon import com.google.common.truth.Correspondence val byKey: Correspondence<ActiveNotificationModel, String> = @@ -38,26 +39,29 @@ val byIsLastMessageFromReply: Correspondence<ActiveNotificationModel, Boolean> = val byIsPulsing: Correspondence<ActiveNotificationModel, Boolean> = Correspondence.transforming({ it?.isPulsing }, "has an isPulsing value of") -@Suppress("TestFunctionName") -fun ActiveNotificationModel( +fun activeNotificationModel( key: String, + groupKey: String? = null, isAmbient: Boolean = false, isRowDismissed: Boolean = false, isSilent: Boolean = false, isLastMessageFromReply: Boolean = false, isSuppressedFromStatusBar: Boolean = false, isPulsing: Boolean = false, + aodIcon: Icon? = null, + shelfIcon: Icon? = null, + statusBarIcon: Icon? = null, ) = ActiveNotificationModel( key = key, - groupKey = null, + groupKey = groupKey, isAmbient = isAmbient, isRowDismissed = isRowDismissed, isSilent = isSilent, isLastMessageFromReply = isLastMessageFromReply, isSuppressedFromStatusBar = isSuppressedFromStatusBar, isPulsing = isPulsing, - aodIcon = null, - shelfIcon = null, - statusBarIcon = null, + aodIcon = aodIcon, + shelfIcon = shelfIcon, + statusBarIcon = statusBarIcon, ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java index d84bb728a856..529e2c9a8e7e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java @@ -34,6 +34,8 @@ import android.widget.TextView; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; +import com.android.systemui.flags.FakeFeatureFlagsClassic; +import com.android.systemui.flags.Flags; import com.android.systemui.plugins.DarkIconDispatcher; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.shade.ShadeHeadsUpTracker; @@ -42,6 +44,7 @@ import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.HeadsUpStatusBarView; import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationIconInteractor; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.NotificationTestHelper; import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager; @@ -82,10 +85,12 @@ public class HeadsUpAppearanceControllerTest extends SysuiTestCase { private KeyguardStateController mKeyguardStateController; private CommandQueue mCommandQueue; private NotificationRoundnessManager mNotificationRoundnessManager; + private final FakeFeatureFlagsClassic mFeatureFlags = new FakeFeatureFlagsClassic(); @Before public void setUp() throws Exception { allowTestableLooperAsMainThread(); + mFeatureFlags.setDefault(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR); mTestHelper = new NotificationTestHelper( mContext, mDependency, @@ -119,6 +124,8 @@ public class HeadsUpAppearanceControllerTest extends SysuiTestCase { mNotificationRoundnessManager, mHeadsUpStatusBarView, new Clock(mContext, null), + mFeatureFlags, + mock(HeadsUpNotificationIconInteractor.class), Optional.of(mOperatorNameView)); mHeadsUpAppearanceController.setAppearFraction(0.0f, 0.0f); } @@ -203,6 +210,7 @@ public class HeadsUpAppearanceControllerTest extends SysuiTestCase { mNotificationRoundnessManager, mHeadsUpStatusBarView, new Clock(mContext, null), + mFeatureFlags, mock(HeadsUpNotificationIconInteractor.class), Optional.empty()); assertEquals(expandedHeight, newController.mExpandedHeight, 0.0f); |