summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/HeadsUpNotificationIconViewStateRepository.kt30
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationIconInteractor.kt44
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImpl.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt94
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModel.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerShelfViewModel.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt32
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerViewModel.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java35
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java38
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt16
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt63
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shared/TestActiveNotificationModel.kt16
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java8
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);