diff options
27 files changed, 824 insertions, 305 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java index 0389a7b01c72..f500d39032ba 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java @@ -45,6 +45,7 @@ import com.android.systemui.statusbar.NotificationPresenter; import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.NotificationRemoveInterceptor; import com.android.systemui.statusbar.NotificationUiAdjustment; +import com.android.systemui.statusbar.notification.collection.NotifLiveDataStoreImpl; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder; import com.android.systemui.statusbar.notification.collection.legacy.LegacyNotificationRanker; @@ -107,6 +108,7 @@ public class NotificationEntryManager implements private final LeakDetector mLeakDetector; private final ForegroundServiceDismissalFeatureController mFgsFeatureController; private final IStatusBarService mStatusBarService; + private final NotifLiveDataStoreImpl mNotifLiveDataStore; private final DumpManager mDumpManager; private final Set<NotificationEntry> mAllNotifications = new ArraySet<>(); @@ -154,6 +156,7 @@ public class NotificationEntryManager implements LeakDetector leakDetector, ForegroundServiceDismissalFeatureController fgsFeatureController, IStatusBarService statusBarService, + NotifLiveDataStoreImpl notifLiveDataStore, DumpManager dumpManager ) { mLogger = logger; @@ -164,6 +167,7 @@ public class NotificationEntryManager implements mLeakDetector = leakDetector; mFgsFeatureController = fgsFeatureController; mStatusBarService = statusBarService; + mNotifLiveDataStore = notifLiveDataStore; mDumpManager = dumpManager; } @@ -725,9 +729,10 @@ public class NotificationEntryManager implements return; } reapplyFilterAndSort(reason); - if (mPresenter != null && !mNotifPipelineFlags.isNewPipelineEnabled()) { + if (mPresenter != null) { mPresenter.updateNotificationViews(reason); } + mNotifLiveDataStore.setActiveNotifList(getVisibleNotifications()); } public void updateNotificationRanking(RankingMap rankingMap) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStore.kt new file mode 100644 index 000000000000..ef006279142c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStore.kt @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2021 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.collection + +import androidx.lifecycle.Observer + +/** + * An object which provides pieces of information about the notification shade. + * + * Note that individual fields of this object are updated together before synchronous observers are + * notified, so synchronous observers of two fields can be assured that they will see consistent + * results: e.g. if [hasActiveNotifs] is false then [activeNotifList] will be empty, and vice versa. + * + * This interface is read-only. + */ +interface NotifLiveDataStore { + val hasActiveNotifs: NotifLiveData<Boolean> + val activeNotifCount: NotifLiveData<Int> + val activeNotifList: NotifLiveData<List<NotificationEntry>> +} + +/** + * An individual value which can be accessed directly, or observed for changes either synchronously + * or asynchronously. + * + * This interface is read-only. + */ +interface NotifLiveData<T> { + /** Access the current value */ + val value: T + /** Add an observer which will be invoked synchronously when the value is changed. */ + fun addSyncObserver(observer: Observer<T>) + /** Add an observer which will be invoked asynchronously after the value has changed */ + fun addAsyncObserver(observer: Observer<T>) + /** Remove an observer previously added with [addSyncObserver] or [addAsyncObserver]. */ + fun removeObserver(observer: Observer<T>) +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreImpl.kt new file mode 100644 index 000000000000..8aa6b81eede0 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreImpl.kt @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2021 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.collection + +import androidx.lifecycle.Observer +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.util.Assert +import com.android.systemui.util.ListenerSet +import com.android.systemui.util.isNotEmpty +import com.android.systemui.util.traceSection +import java.util.Collections.unmodifiableList +import java.util.concurrent.Executor +import java.util.concurrent.atomic.AtomicReference +import javax.inject.Inject + +/** Writeable implementation of [NotifLiveDataStore] */ +@SysUISingleton +class NotifLiveDataStoreImpl @Inject constructor( + @Main private val mainExecutor: Executor +) : NotifLiveDataStore { + private val hasActiveNotifsPrivate = NotifLiveDataImpl( + name = "hasActiveNotifs", + initialValue = false, + mainExecutor + ) + private val activeNotifCountPrivate = NotifLiveDataImpl( + name = "activeNotifCount", + initialValue = 0, + mainExecutor + ) + private val activeNotifListPrivate = NotifLiveDataImpl( + name = "activeNotifList", + initialValue = listOf<NotificationEntry>(), + mainExecutor + ) + + override val hasActiveNotifs: NotifLiveData<Boolean> = hasActiveNotifsPrivate + override val activeNotifCount: NotifLiveData<Int> = activeNotifCountPrivate + override val activeNotifList: NotifLiveData<List<NotificationEntry>> = activeNotifListPrivate + + /** Set the latest flattened list of notification entries. */ + fun setActiveNotifList(flatEntryList: List<NotificationEntry>) { + traceSection("NotifLiveDataStore.setActiveNotifList") { + Assert.isMainThread() + val unmodifiableCopy = unmodifiableList(flatEntryList.toList()) + // This ensures we set all values before dispatching to any observers + listOf( + activeNotifListPrivate.setValueAndProvideDispatcher(unmodifiableCopy), + activeNotifCountPrivate.setValueAndProvideDispatcher(unmodifiableCopy.size), + hasActiveNotifsPrivate.setValueAndProvideDispatcher(unmodifiableCopy.isNotEmpty()) + ).forEach { dispatcher -> dispatcher.invoke() } + } + } +} + +/** Read-write implementation of [NotifLiveData] */ +class NotifLiveDataImpl<T>( + private val name: String, + initialValue: T, + @Main private val mainExecutor: Executor +) : NotifLiveData<T> { + private val syncObservers = ListenerSet<Observer<T>>() + private val asyncObservers = ListenerSet<Observer<T>>() + private val atomicValue = AtomicReference(initialValue) + private var lastAsyncValue: T? = null + + private fun dispatchToAsyncObservers() { + val value = atomicValue.get() + if (lastAsyncValue != value) { + lastAsyncValue = value + traceSection("NotifLiveData($name).dispatchToAsyncObservers") { + asyncObservers.forEach { it.onChanged(value) } + } + } + } + + /** + * Access or set the current value. + * + * When setting, sync observers will be dispatched synchronously, and a task will be posted to + * dispatch the value to async observers. + */ + override var value: T + get() = atomicValue.get() + set(value) = setValueAndProvideDispatcher(value).invoke() + + /** + * Set the value, and return a function that when invoked will dispatch to the observers. + * + * This is intended to allow multiple instances with related data to be updated together and + * have their dispatchers invoked after all data has been updated. + */ + fun setValueAndProvideDispatcher(value: T): () -> Unit { + val oldValue = atomicValue.getAndSet(value) + if (oldValue != value) { + return { + if (syncObservers.isNotEmpty()) { + traceSection("NotifLiveData($name).dispatchToSyncObservers") { + syncObservers.forEach { it.onChanged(value) } + } + } + if (asyncObservers.isNotEmpty()) { + mainExecutor.execute(::dispatchToAsyncObservers) + } + } + } + return {} + } + + override fun addSyncObserver(observer: Observer<T>) { + syncObservers.addIfAbsent(observer) + } + + override fun addAsyncObserver(observer: Observer<T>) { + asyncObservers.addIfAbsent(observer) + } + + override fun removeObserver(observer: Observer<T>) { + syncObservers.remove(observer) + asyncObservers.remove(observer) + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.kt index 6fbed9a8d3b4..5ada7a82f8fe 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.kt @@ -245,36 +245,4 @@ class NotifPipeline @Inject constructor( fun getInternalNotifUpdater(name: String?): InternalNotifUpdater { return mNotifCollection.getInternalNotifUpdater(name) } - - /** - * Returns a read-only view in to the current shade list, i.e. the list of notifications that - * are currently present in the shade. - * @throws IllegalStateException if called during pipeline execution. - */ - val shadeList: List<ListEntry> - get() = mShadeListBuilder.shadeList - - /** - * Constructs a flattened representation of the notification tree, where each group will have - * the summary (if present) followed by the children. - * @throws IllegalStateException if called during pipeline execution. - */ - fun getFlatShadeList(): List<NotificationEntry> = shadeList.flatMap { entry -> - when (entry) { - is NotificationEntry -> sequenceOf(entry) - is GroupEntry -> sequenceOf(entry.summary).filterNotNull() + entry.children - else -> throw RuntimeException("Unexpected entry $entry") - } - } - - /** - * Returns the number of notifications currently shown in the shade. This includes all - * children and summary notifications. - * @throws IllegalStateException if called during pipeline execution. - */ - fun getShadeListCount(): Int = shadeList.sumOf { entry -> - // include the summary in the count - if (entry is GroupEntry) 1 + entry.children.size - else 1 - } }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DataStoreCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DataStoreCoordinator.kt new file mode 100644 index 000000000000..8e307ecd896d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DataStoreCoordinator.kt @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2021 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.collection.coordinator + +import com.android.systemui.statusbar.notification.collection.GroupEntry +import com.android.systemui.statusbar.notification.collection.ListEntry +import com.android.systemui.statusbar.notification.collection.NotifLiveDataStoreImpl +import com.android.systemui.statusbar.notification.collection.NotifPipeline +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope +import com.android.systemui.statusbar.notification.collection.render.requireSummary +import javax.inject.Inject + +/** + * A small coordinator which updates the notif stack (the view layer which holds notifications) + * with high-level data after the stack is populated with the final entries. + */ +@CoordinatorScope +class DataStoreCoordinator @Inject internal constructor( + private val notifLiveDataStoreImpl: NotifLiveDataStoreImpl +) : Coordinator { + + override fun attach(pipeline: NotifPipeline) { + pipeline.addOnAfterRenderListListener { entries, _ -> onAfterRenderList(entries) } + } + + fun onAfterRenderList(entries: List<ListEntry>) { + val flatEntryList = flattenedEntryList(entries) + notifLiveDataStoreImpl.setActiveNotifList(flatEntryList) + } + + private fun flattenedEntryList(entries: List<ListEntry>) = + mutableListOf<NotificationEntry>().also { list -> + entries.forEach { entry -> + when (entry) { + is NotificationEntry -> list.add(entry) + is GroupEntry -> { + list.add(entry.requireSummary) + list.addAll(entry.children) + } + else -> error("Unexpected entry $entry") + } + } + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt index a16b565d488c..02649bad33d3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt @@ -35,6 +35,7 @@ interface NotifCoordinators : Coordinator, Dumpable class NotifCoordinatorsImpl @Inject constructor( dumpManager: DumpManager, notifPipelineFlags: NotifPipelineFlags, + dataStoreCoordinator: DataStoreCoordinator, hideLocallyDismissedNotifsCoordinator: HideLocallyDismissedNotifsCoordinator, hideNotifsForOtherUsersCoordinator: HideNotifsForOtherUsersCoordinator, keyguardCoordinator: KeyguardCoordinator, @@ -44,10 +45,11 @@ class NotifCoordinatorsImpl @Inject constructor( bubbleCoordinator: BubbleCoordinator, headsUpCoordinator: HeadsUpCoordinator, gutsCoordinator: GutsCoordinator, + communalCoordinator: CommunalCoordinator, conversationCoordinator: ConversationCoordinator, - preparationCoordinator: PreparationCoordinator, groupCountCoordinator: GroupCountCoordinator, mediaCoordinator: MediaCoordinator, + preparationCoordinator: PreparationCoordinator, remoteInputCoordinator: RemoteInputCoordinator, rowAppearanceCoordinator: RowAppearanceCoordinator, stackCoordinator: StackCoordinator, @@ -55,7 +57,6 @@ class NotifCoordinatorsImpl @Inject constructor( smartspaceDedupingCoordinator: SmartspaceDedupingCoordinator, viewConfigCoordinator: ViewConfigCoordinator, visualStabilityCoordinator: VisualStabilityCoordinator, - communalCoordinator: CommunalCoordinator, sensitiveContentCoordinator: SensitiveContentCoordinator ) : NotifCoordinators { @@ -67,6 +68,16 @@ class NotifCoordinatorsImpl @Inject constructor( */ init { dumpManager.registerDumpable(TAG, this) + + // TODO(b/208866714): formalize the system by which some coordinators may be required by the + // pipeline, such as this DataStoreCoordinator which cannot be removed, as it's a critical + // glue between the pipeline and parts of SystemUI which depend on pipeline output via the + // NotifLiveDataStore. + if (notifPipelineFlags.isNewPipelineEnabled()) { + mCoordinators.add(dataStoreCoordinator) + } + + // Attach normal coordinators. mCoordinators.add(hideLocallyDismissedNotifsCoordinator) mCoordinators.add(hideNotifsForOtherUsersCoordinator) mCoordinators.add(keyguardCoordinator) @@ -74,6 +85,7 @@ class NotifCoordinatorsImpl @Inject constructor( mCoordinators.add(appOpsCoordinator) mCoordinators.add(deviceProvisionedCoordinator) mCoordinators.add(bubbleCoordinator) + mCoordinators.add(communalCoordinator) mCoordinators.add(conversationCoordinator) mCoordinators.add(groupCountCoordinator) mCoordinators.add(mediaCoordinator) @@ -83,7 +95,6 @@ class NotifCoordinatorsImpl @Inject constructor( mCoordinators.add(shadeEventCoordinator) mCoordinators.add(viewConfigCoordinator) mCoordinators.add(visualStabilityCoordinator) - mCoordinators.add(communalCoordinator) mCoordinators.add(sensitiveContentCoordinator) if (notifPipelineFlags.isSmartspaceDedupingEnabled()) { mCoordinators.add(smartspaceDedupingCoordinator) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt index 38f11fc88b72..c6a8a69cfb0d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt @@ -19,8 +19,8 @@ package com.android.systemui.statusbar.notification.collection.coordinator import com.android.systemui.statusbar.notification.collection.ListEntry import com.android.systemui.statusbar.notification.collection.NotifPipeline import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope -import com.android.systemui.statusbar.notification.collection.render.NotifStats import com.android.systemui.statusbar.notification.collection.render.NotifStackController +import com.android.systemui.statusbar.notification.collection.render.NotifStats import com.android.systemui.statusbar.notification.stack.BUCKET_SILENT import com.android.systemui.statusbar.phone.NotificationIconAreaController import javax.inject.Inject @@ -49,10 +49,12 @@ class StackCoordinator @Inject internal constructor( var hasNonClearableSilentNotifs = false var hasClearableSilentNotifs = false entries.forEach { - val isSilent = it.section!!.bucket == BUCKET_SILENT + val section = checkNotNull(it.section) { "Null section for ${it.key}" } + val entry = checkNotNull(it.representativeEntry) { "Null notif entry for ${it.key}" } + val isSilent = section.bucket == BUCKET_SILENT // NOTE: NotificationEntry.isClearable will internally check group children to ensure // the group itself definitively clearable. - val isClearable = it.representativeEntry!!.isClearable + val isClearable = entry.isClearable when { isSilent && isClearable -> hasClearableSilentNotifs = true isSilent && !isClearable -> hasNonClearableSilentNotifs = true @@ -60,13 +62,12 @@ class StackCoordinator @Inject internal constructor( !isSilent && !isClearable -> hasNonClearableAlertingNotifs = true } } - val stats = NotifStats( + return NotifStats( numActiveNotifs = entries.size, hasNonClearableAlertingNotifs = hasNonClearableAlertingNotifs, hasClearableAlertingNotifs = hasClearableAlertingNotifs, hasNonClearableSilentNotifs = hasNonClearableSilentNotifs, hasClearableSilentNotifs = hasClearableSilentNotifs ) - return stats } }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/LegacyNotificationVisibilityProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/LegacyNotificationVisibilityProvider.kt deleted file mode 100644 index 5c70f32dfd25..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/LegacyNotificationVisibilityProvider.kt +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (C) 2021 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.collection.legacy - -import com.android.internal.statusbar.NotificationVisibility -import com.android.systemui.statusbar.notification.NotificationEntryManager -import com.android.systemui.statusbar.notification.collection.NotificationEntry -import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider -import com.android.systemui.statusbar.notification.logging.NotificationLogger -import javax.inject.Inject - -/** Legacy pipeline implementation for getting [NotificationVisibility]. */ -class LegacyNotificationVisibilityProvider @Inject constructor( - private val notifEntryManager: NotificationEntryManager -) : NotificationVisibilityProvider { - override fun obtain(entry: NotificationEntry, visible: Boolean): NotificationVisibility { - val count: Int = notifEntryManager.activeNotificationsCount - val rank = entry.ranking.rank - val hasRow = entry.row != null - val location = NotificationLogger.getNotificationLocation(entry) - return NotificationVisibility.obtain(entry.key, rank, count, visible && hasRow, location) - } - - override fun obtain(key: String, visible: Boolean): NotificationVisibility { - val entry: NotificationEntry? = notifEntryManager.getActiveNotificationUnfiltered(key) - val count: Int = notifEntryManager.activeNotificationsCount - val rank = entry?.ranking?.rank ?: -1 - val hasRow = entry?.row != null - val location = NotificationLogger.getNotificationLocation(entry) - return NotificationVisibility.obtain(key, rank, count, visible && hasRow, location) - } - - override fun getLocation(key: String): NotificationVisibility.NotificationLocation = - NotificationLogger.getNotificationLocation( - notifEntryManager.getActiveNotificationUnfiltered(key)) -}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotificationVisibilityProviderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/NotificationVisibilityProviderImpl.kt index 51de08d539b6..6a1e36f4f469 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotificationVisibilityProviderImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/NotificationVisibilityProviderImpl.kt @@ -14,20 +14,24 @@ * limitations under the License. */ -package com.android.systemui.statusbar.notification.collection.render +package com.android.systemui.statusbar.notification.collection.provider import com.android.internal.statusbar.NotificationVisibility -import com.android.systemui.statusbar.notification.collection.NotifPipeline +import com.android.systemui.statusbar.notification.collection.NotifLiveDataStore import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection +import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider import com.android.systemui.statusbar.notification.logging.NotificationLogger import javax.inject.Inject -/** New pipeline implementation for getting [NotificationVisibility]. */ +/** pipeline-agnostic implementation for getting [NotificationVisibility]. */ class NotificationVisibilityProviderImpl @Inject constructor( - private val notifPipeline: NotifPipeline + private val notifDataStore: NotifLiveDataStore, + private val notifCollection: CommonNotifCollection ) : NotificationVisibilityProvider { + override fun obtain(entry: NotificationEntry, visible: Boolean): NotificationVisibility { - val count: Int = notifPipeline.getShadeListCount() + val count: Int = getCount() val rank = entry.ranking.rank val hasRow = entry.row != null val location = NotificationLogger.getNotificationLocation(entry) @@ -35,9 +39,11 @@ class NotificationVisibilityProviderImpl @Inject constructor( } override fun obtain(key: String, visible: Boolean): NotificationVisibility = - notifPipeline.getEntry(key)?.let { return obtain(it, visible) } - ?: NotificationVisibility.obtain(key, -1, notifPipeline.getShadeListCount(), false) + notifCollection.getEntry(key)?.let { return obtain(it, visible) } + ?: NotificationVisibility.obtain(key, -1, getCount(), false) override fun getLocation(key: String): NotificationVisibility.NotificationLocation = - NotificationLogger.getNotificationLocation(notifPipeline.getEntry(key)) + NotificationLogger.getNotificationLocation(notifCollection.getEntry(key)) + + private fun getCount() = notifDataStore.activeNotifCount.value }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java index d25a2d30fb4d..f1cba34158d1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java @@ -44,6 +44,8 @@ import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.NotificationEntryManagerLogger; import com.android.systemui.statusbar.notification.collection.NotifCollection; import com.android.systemui.statusbar.notification.collection.NotifInflaterImpl; +import com.android.systemui.statusbar.notification.collection.NotifLiveDataStore; +import com.android.systemui.statusbar.notification.collection.NotifLiveDataStoreImpl; import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.coordinator.ShadeEventCoordinator; import com.android.systemui.statusbar.notification.collection.coordinator.VisualStabilityCoordinator; @@ -52,12 +54,12 @@ import com.android.systemui.statusbar.notification.collection.inflation.NotifInf import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder; import com.android.systemui.statusbar.notification.collection.inflation.OnUserInteractionCallbackImpl; import com.android.systemui.statusbar.notification.collection.legacy.LegacyNotificationPresenterExtensions; -import com.android.systemui.statusbar.notification.collection.legacy.LegacyNotificationVisibilityProvider; import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy; import com.android.systemui.statusbar.notification.collection.legacy.OnUserInteractionCallbackImplLegacy; import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager; import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection; import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider; +import com.android.systemui.statusbar.notification.collection.provider.NotificationVisibilityProviderImpl; import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager; import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManagerImpl; import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager; @@ -65,7 +67,6 @@ import com.android.systemui.statusbar.notification.collection.render.GroupMember import com.android.systemui.statusbar.notification.collection.render.NotifGutsViewManager; import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource; import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider; -import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProviderImpl; import com.android.systemui.statusbar.notification.init.NotificationsController; import com.android.systemui.statusbar.notification.init.NotificationsControllerImpl; import com.android.systemui.statusbar.notification.init.NotificationsControllerStub; @@ -122,6 +123,7 @@ public interface NotificationsModule { LeakDetector leakDetector, ForegroundServiceDismissalFeatureController fgsFeatureController, IStatusBarService statusBarService, + NotifLiveDataStoreImpl notifLiveDataStore, DumpManager dumpManager) { return new NotificationEntryManager( logger, @@ -132,6 +134,7 @@ public interface NotificationsModule { leakDetector, fgsFeatureController, statusBarService, + notifLiveDataStore, dumpManager); } @@ -212,6 +215,7 @@ public interface NotificationsModule { NotificationListener notificationListener, @UiBackground Executor uiBgExecutor, NotifPipelineFlags notifPipelineFlags, + NotifLiveDataStore notifLiveDataStore, NotificationVisibilityProvider visibilityProvider, NotificationEntryManager entryManager, NotifPipeline notifPipeline, @@ -222,6 +226,7 @@ public interface NotificationsModule { notificationListener, uiBgExecutor, notifPipelineFlags, + notifLiveDataStore, visibilityProvider, entryManager, notifPipeline, @@ -290,16 +295,10 @@ public interface NotificationsModule { /** * Provide the object which can be used to obtain NotificationVisibility objects. */ - @Provides + @Binds @SysUISingleton - static NotificationVisibilityProvider provideNotificationVisibilityProvider( - NotifPipelineFlags notifPipelineFlags, - Lazy<NotificationVisibilityProviderImpl> newProvider, - Lazy<LegacyNotificationVisibilityProvider> legacyProvider) { - return notifPipelineFlags.isNewPipelineEnabled() - ? newProvider.get() - : legacyProvider.get(); - } + NotificationVisibilityProvider provideNotificationVisibilityProvider( + NotificationVisibilityProviderImpl newProvider); /** * Provide the active implementation for presenting notifications. @@ -356,4 +355,8 @@ public interface NotificationsModule { /** */ @Binds NotifInflater bindNotifInflater(NotifInflaterImpl notifInflaterImpl); + + /** */ + @Binds + NotifLiveDataStore bindNotifLiveDataStore(NotifLiveDataStoreImpl notifLiveDataStoreImpl); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt index 212c342eb8f1..84f09558e022 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt @@ -28,12 +28,14 @@ import com.android.systemui.statusbar.notification.NotificationActivityStarter import com.android.systemui.statusbar.notification.NotificationClicker import com.android.systemui.statusbar.notification.NotificationEntryManager import com.android.systemui.statusbar.notification.NotificationListController +import com.android.systemui.statusbar.notification.collection.NotifLiveDataStore import com.android.systemui.statusbar.notification.collection.NotifPipeline import com.android.systemui.statusbar.notification.collection.NotificationRankingManager import com.android.systemui.statusbar.notification.collection.TargetSdkResolver import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl import com.android.systemui.statusbar.notification.collection.init.NotifPipelineInitializer import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy +import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection import com.android.systemui.statusbar.notification.collection.render.NotifStackController import com.android.systemui.statusbar.notification.interruption.HeadsUpController import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder @@ -64,9 +66,11 @@ class NotificationsControllerImpl @Inject constructor( private val notificationListener: NotificationListener, private val entryManager: NotificationEntryManager, private val legacyRanker: NotificationRankingManager, + private val commonNotifCollection: Lazy<CommonNotifCollection>, private val notifPipeline: Lazy<NotifPipeline>, + private val notifLiveDataStore: NotifLiveDataStore, private val targetSdkResolver: TargetSdkResolver, - private val newNotifPipeline: Lazy<NotifPipelineInitializer>, + private val newNotifPipelineInitializer: Lazy<NotifPipelineInitializer>, private val notifBindPipelineInitializer: NotifBindPipelineInitializer, private val deviceProvisionedController: DeviceProvisionedController, private val notificationRowBinder: NotificationRowBinderImpl, @@ -111,7 +115,7 @@ class NotificationsControllerImpl @Inject constructor( animatedImageNotificationManager.bind() if (INITIALIZE_NEW_PIPELINE) { - newNotifPipeline.get().initialize( + newNotifPipelineInitializer.get().initialize( notificationListener, notificationRowBinder, listContainer, @@ -155,14 +159,10 @@ class NotificationsControllerImpl @Inject constructor( } override fun resetUserExpandedStates() { - if (notifPipelineFlags.isNewPipelineEnabled()) { - for (entry in notifPipeline.get().allNotifs) { - entry.resetUserExpansion() - } - } else { - for (entry in entryManager.visibleNotifications) { - entry.resetUserExpansion() - } + // TODO: this is a view thing that should be done through the views, but that means doing it + // both when this event is fired and any time a row is attached. + for (entry in commonNotifCollection.get().allNotifs) { + entry.resetUserExpansion() } } @@ -177,11 +177,7 @@ class NotificationsControllerImpl @Inject constructor( } override fun getActiveNotificationsCount(): Int = - if (notifPipelineFlags.isNewPipelineEnabled()) { - notifPipeline.get().getShadeListCount() - } else { - entryManager.activeNotificationsCount - } + notifLiveDataStore.activeNotifCount.value companion object { // NOTE: The new pipeline is always active, even if the old pipeline is *rendering*. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java index 52488da29162..9e8200b063fe 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java @@ -42,6 +42,7 @@ import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.notification.NotifPipelineFlags; import com.android.systemui.statusbar.notification.NotificationEntryListener; import com.android.systemui.statusbar.notification.NotificationEntryManager; +import com.android.systemui.statusbar.notification.collection.NotifLiveDataStore; import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; @@ -76,7 +77,7 @@ public class NotificationLogger implements StateListener { // Dependencies: private final NotificationListenerService mNotificationListener; private final Executor mUiBgExecutor; - private final NotifPipelineFlags mNotifPipelineFlags; + private final NotifLiveDataStore mNotifLiveDataStore; private final NotificationVisibilityProvider mVisibilityProvider; private final NotificationEntryManager mEntryManager; private final NotifPipeline mNotifPipeline; @@ -179,11 +180,7 @@ public class NotificationLogger implements StateListener { }; private List<NotificationEntry> getVisibleNotifications() { - if (mNotifPipelineFlags.isNewPipelineEnabled()) { - return mNotifPipeline.getFlatShadeList(); - } else { - return mEntryManager.getVisibleNotifications(); - } + return mNotifLiveDataStore.getActiveNotifList().getValue(); } /** @@ -223,6 +220,7 @@ public class NotificationLogger implements StateListener { public NotificationLogger(NotificationListener notificationListener, @UiBackground Executor uiBgExecutor, NotifPipelineFlags notifPipelineFlags, + NotifLiveDataStore notifLiveDataStore, NotificationVisibilityProvider visibilityProvider, NotificationEntryManager entryManager, NotifPipeline notifPipeline, @@ -231,7 +229,7 @@ public class NotificationLogger implements StateListener { NotificationPanelLogger notificationPanelLogger) { mNotificationListener = notificationListener; mUiBgExecutor = uiBgExecutor; - mNotifPipelineFlags = notifPipelineFlags; + mNotifLiveDataStore = notifLiveDataStore; mVisibilityProvider = visibilityProvider; mEntryManager = entryManager; mNotifPipeline = notifPipeline; @@ -242,7 +240,7 @@ public class NotificationLogger implements StateListener { // Not expected to be destroyed, don't need to unsubscribe statusBarStateController.addCallback(this); - if (mNotifPipelineFlags.isNewPipelineEnabled()) { + if (notifPipelineFlags.isNewPipelineEnabled()) { registerNewPipelineListener(); } else { registerLegacyListener(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightsOutNotifController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightsOutNotifController.java index c09cca1f0e42..c61510cce10e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightsOutNotifController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightsOutNotifController.java @@ -22,7 +22,6 @@ import static com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFrag import android.animation.Animator; import android.animation.AnimatorListenerAdapter; -import android.annotation.Nullable; import android.view.InsetsVisibilities; import android.view.View; import android.view.WindowInsetsController.Appearance; @@ -30,13 +29,12 @@ import android.view.WindowInsetsController.Behavior; import android.view.WindowManager; import android.view.animation.AccelerateInterpolator; +import androidx.lifecycle.Observer; + import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.statusbar.NotificationVisibility; import com.android.internal.view.AppearanceRegion; import com.android.systemui.statusbar.CommandQueue; -import com.android.systemui.statusbar.notification.NotificationEntryListener; -import com.android.systemui.statusbar.notification.NotificationEntryManager; -import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.collection.NotifLiveDataStore; import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentScope; import com.android.systemui.util.ViewController; @@ -44,20 +42,21 @@ import javax.inject.Inject; import javax.inject.Named; /** - * Apps can request a low profile mode {@link View.SYSTEM_UI_FLAG_LOW_PROFILE} + * Apps can request a low profile mode {@link View#SYSTEM_UI_FLAG_LOW_PROFILE} * where status bar and navigation icons dim. In this mode, a notification dot appears * where the notification icons would appear if they would be shown outside of this mode. * * This controller shows and hides the notification dot in the status bar to indicate - * whether there are notifications when the device is in {@link View.SYSTEM_UI_FLAG_LOW_PROFILE}. + * whether there are notifications when the device is in {@link View#SYSTEM_UI_FLAG_LOW_PROFILE}. */ @StatusBarFragmentScope public class LightsOutNotifController extends ViewController<View> { private final CommandQueue mCommandQueue; - private final NotificationEntryManager mEntryManager; + private final NotifLiveDataStore mNotifDataStore; private final WindowManager mWindowManager; + private final Observer<Boolean> mObserver = hasNotifs -> updateLightsOutView(); - /** @see android.view.WindowInsetsController#setSystemBarsAppearance(int) */ + /** @see android.view.WindowInsetsController#setSystemBarsAppearance(int, int) */ @VisibleForTesting @Appearance int mAppearance; private int mDisplayId; @@ -66,18 +65,18 @@ public class LightsOutNotifController extends ViewController<View> { LightsOutNotifController( @Named(LIGHTS_OUT_NOTIF_VIEW) View lightsOutNotifView, WindowManager windowManager, - NotificationEntryManager entryManager, + NotifLiveDataStore notifDataStore, CommandQueue commandQueue) { super(lightsOutNotifView); mWindowManager = windowManager; - mEntryManager = entryManager; + mNotifDataStore = notifDataStore; mCommandQueue = commandQueue; } @Override protected void onViewDetached() { - mEntryManager.removeNotificationEntryListener(mEntryListener); + mNotifDataStore.getHasActiveNotifs().removeObserver(mObserver); mCommandQueue.removeCallback(mCallback); } @@ -87,14 +86,14 @@ public class LightsOutNotifController extends ViewController<View> { mView.setAlpha(0f); mDisplayId = mWindowManager.getDefaultDisplay().getDisplayId(); - mEntryManager.addNotificationEntryListener(mEntryListener); + mNotifDataStore.getHasActiveNotifs().addSyncObserver(mObserver); mCommandQueue.addCallback(mCallback); updateLightsOutView(); } private boolean hasActiveNotifications() { - return mEntryManager.hasActiveNotifications(); + return mNotifDataStore.getHasActiveNotifs().getValue(); } @VisibleForTesting @@ -153,23 +152,4 @@ public class LightsOutNotifController extends ViewController<View> { updateLightsOutView(); } }; - - private final NotificationEntryListener mEntryListener = new NotificationEntryListener() { - // Cares about notifications post-filtering - @Override - public void onNotificationAdded(NotificationEntry entry) { - updateLightsOutView(); - } - - @Override - public void onPostEntryUpdated(NotificationEntry entry) { - updateLightsOutView(); - } - - @Override - public void onEntryRemoved(@Nullable NotificationEntry entry, - NotificationVisibility visibility, boolean removedByUser, int reason) { - updateLightsOutView(); - } - }; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java index 863ce5758f9e..ff86d74a86eb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java @@ -660,14 +660,6 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit // --------------------- NotificationEntryManager/NotifPipeline methods ------------------------ - private int getVisibleNotificationsCount() { - if (mNotifPipelineFlags.isNewPipelineEnabled()) { - return mNotifPipeline.getShadeListCount(); - } else { - return mEntryManager.getActiveNotificationsCount(); - } - } - /** * Public builder for {@link StatusBarNotificationActivityStarter}. */ diff --git a/packages/SystemUI/src/com/android/systemui/util/ListenerSet.kt b/packages/SystemUI/src/com/android/systemui/util/ListenerSet.kt index 0f4193e94196..4f20067dc0ed 100644 --- a/packages/SystemUI/src/com/android/systemui/util/ListenerSet.kt +++ b/packages/SystemUI/src/com/android/systemui/util/ListenerSet.kt @@ -39,9 +39,17 @@ class ListenerSet<E> : Iterable<E> { fun remove(element: E): Boolean = listeners.remove(element) /** + * Determine if the listener set is empty + */ + fun isEmpty(): Boolean = listeners.isEmpty() + + /** * Returns an iterator over the listeners currently in the set. Note that to ensure * [ConcurrentModificationException] is never thrown, this iterator will not reflect changes * made to the set after the iterator is constructed. */ override fun iterator(): Iterator<E> = listeners.iterator() } + +/** Extension to match Collection which is implemented to only be (easily) accessible in kotlin */ +fun <T> ListenerSet<T>.isNotEmpty(): Boolean = !isEmpty() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java index f62de5159f54..dc83c0d08291 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java @@ -75,6 +75,7 @@ import com.android.systemui.statusbar.NotificationRemoveInterceptor; import com.android.systemui.statusbar.RankingBuilder; import com.android.systemui.statusbar.SmartReplyController; import com.android.systemui.statusbar.notification.NotificationEntryManager.KeyguardEnvironment; +import com.android.systemui.statusbar.notification.collection.NotifLiveDataStoreMocksKt; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; import com.android.systemui.statusbar.notification.collection.NotificationRankingManager; @@ -201,6 +202,7 @@ public class NotificationEntryManagerTest extends SysuiTestCase { mLeakDetector, mock(ForegroundServiceDismissalFeatureController.class), mock(IStatusBarService.class), + NotifLiveDataStoreMocksKt.createNotifLiveDataStoreImplMock(), mock(DumpManager.class) ); mEntryManager.initialize( diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java index 2971c05487c6..b45d78d5502d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java @@ -18,6 +18,8 @@ package com.android.systemui.statusbar.notification.collection; import androidx.annotation.Nullable; +import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection; + import java.util.ArrayList; import java.util.List; @@ -28,6 +30,7 @@ public class GroupEntryBuilder { private String mKey = "test_group_key"; private long mCreationTime = 0; @Nullable private GroupEntry mParent = GroupEntry.ROOT_ENTRY; + private NotifSection mNotifSection; private NotificationEntry mSummary = null; private List<NotificationEntry> mChildren = new ArrayList<>(); @@ -35,6 +38,7 @@ public class GroupEntryBuilder { public GroupEntry build() { GroupEntry ge = new GroupEntry(mKey, mCreationTime); ge.setParent(mParent); + ge.getAttachState().setSection(mNotifSection); ge.setSummary(mSummary); mSummary.setParent(ge); @@ -61,6 +65,11 @@ public class GroupEntryBuilder { return this; } + public GroupEntryBuilder setSection(@Nullable NotifSection section) { + mNotifSection = section; + return this; + } + public GroupEntryBuilder setSummary( NotificationEntry summary) { mSummary = summary; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataImplTest.kt new file mode 100644 index 000000000000..892575ab6c71 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataImplTest.kt @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2021 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.collection + +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper.RunWithLooper +import androidx.lifecycle.Observer +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.util.concurrency.FakeExecutor +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.time.FakeSystemClock +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.eq +import org.mockito.Mockito.inOrder +import org.mockito.Mockito.verify +import org.mockito.Mockito.verifyNoMoreInteractions + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@RunWithLooper +class NotifLiveDataImplTest : SysuiTestCase() { + + private val executor = FakeExecutor(FakeSystemClock()) + private val liveDataImpl: NotifLiveDataImpl<Int> = NotifLiveDataImpl("tst", 9, executor) + private val syncObserver: Observer<Int> = mock() + private val asyncObserver: Observer<Int> = mock() + + @Before + fun setup() { + allowTestableLooperAsMainThread() + liveDataImpl.addSyncObserver(syncObserver) + liveDataImpl.addAsyncObserver(asyncObserver) + } + + @Test + fun testGetInitialValue() { + assertThat(liveDataImpl.value).isEqualTo(9) + } + + @Test + fun testGetModifiedValue() { + liveDataImpl.value = 13 + assertThat(liveDataImpl.value).isEqualTo(13) + } + + @Test + fun testGetsModifiedValueFromWithinSyncObserver() { + liveDataImpl.addSyncObserver { intVal -> + assertThat(intVal).isEqualTo(13) + assertThat(liveDataImpl.value).isEqualTo(13) + } + liveDataImpl.value = 13 + } + + @Test + fun testDoesNotAlertsRemovedObservers() { + liveDataImpl.removeObserver(syncObserver) + liveDataImpl.removeObserver(asyncObserver) + + liveDataImpl.value = 13 + + // There should be no runnables on the executor + assertThat(executor.runAllReady()).isEqualTo(0) + + // And observers should not be called + verifyNoMoreInteractions(syncObserver, asyncObserver) + } + + @Test + fun testDoesNotAsyncObserversRemovedSinceChange() { + liveDataImpl.value = 13 + liveDataImpl.removeObserver(asyncObserver) + + // There should be a runnable that will get executed... + assertThat(executor.runAllReady()).isEqualTo(1) + + // ...but async observers should not be called + verifyNoMoreInteractions(asyncObserver) + } + + @Test + fun testAlertsObservers() { + liveDataImpl.value = 13 + + // Verify that the synchronous observer is called immediately + verify(syncObserver).onChanged(eq(13)) + verifyNoMoreInteractions(syncObserver, asyncObserver) + + // Verify that the asynchronous observer is called when the executor runs + assertThat(executor.runAllReady()).isEqualTo(1) + verify(asyncObserver).onChanged(eq(13)) + verifyNoMoreInteractions(syncObserver, asyncObserver) + } + + @Test + fun testAlertsObserversFromDispatcher() { + // GIVEN that we use setValueAndProvideDispatcher() + val dispatcher = liveDataImpl.setValueAndProvideDispatcher(13) + + // VERIFY that nothing is done before the dispatcher is called + assertThat(executor.numPending()).isEqualTo(0) + verifyNoMoreInteractions(syncObserver, asyncObserver) + + // WHEN the dispatcher is invoked... + dispatcher.invoke() + + // Verify that the synchronous observer is called immediately + verify(syncObserver).onChanged(eq(13)) + verifyNoMoreInteractions(syncObserver, asyncObserver) + + // Verify that the asynchronous observer is called when the executor runs + assertThat(executor.runAllReady()).isEqualTo(1) + verify(asyncObserver).onChanged(eq(13)) + verifyNoMoreInteractions(syncObserver, asyncObserver) + } + + @Test + fun testSkipsAllObserversIfValueDidNotChange() { + liveDataImpl.value = 9 + // Does not add a runnable + assertThat(executor.runAllReady()).isEqualTo(0) + // Setting the current value does not call synchronous observers + verifyNoMoreInteractions(syncObserver, asyncObserver) + } + + @Test + fun testSkipsAsyncObserversWhenValueTogglesBack() { + liveDataImpl.value = 13 + liveDataImpl.value = 11 + liveDataImpl.value = 9 + + // Synchronous observers will receive every change event immediately + inOrder(syncObserver).apply { + verify(syncObserver).onChanged(eq(13)) + verify(syncObserver).onChanged(eq(11)) + verify(syncObserver).onChanged(eq(9)) + } + verifyNoMoreInteractions(syncObserver, asyncObserver) + + // Running the first runnable on the queue will just emit the most recent value + assertThat(executor.runNextReady()).isTrue() + verify(asyncObserver).onChanged(eq(9)) + verifyNoMoreInteractions(syncObserver, asyncObserver) + + // Running the next 2 runnable will have no effect + assertThat(executor.runAllReady()).isEqualTo(2) + verifyNoMoreInteractions(syncObserver, asyncObserver) + } +}
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreImplTest.kt new file mode 100644 index 000000000000..9c8ac5cded9e --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreImplTest.kt @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2021 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.collection + +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper.RunWithLooper +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.util.concurrency.FakeExecutor +import com.android.systemui.util.time.FakeSystemClock +import com.google.common.truth.Truth.assertThat +import org.junit.Assert +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import java.lang.UnsupportedOperationException + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@RunWithLooper +class NotifLiveDataStoreImplTest : SysuiTestCase() { + + private val executor = FakeExecutor(FakeSystemClock()) + private val liveDataStoreImpl = NotifLiveDataStoreImpl(executor) + + @Before + fun setup() { + allowTestableLooperAsMainThread() + } + + @Test + fun testAllObserversSeeConsistentValues() { + val entry1 = NotificationEntryBuilder().setId(1).build() + val entry2 = NotificationEntryBuilder().setId(2).build() + val observer: (Any) -> Unit = { + assertThat(liveDataStoreImpl.hasActiveNotifs.value).isEqualTo(true) + assertThat(liveDataStoreImpl.activeNotifCount.value).isEqualTo(2) + assertThat(liveDataStoreImpl.activeNotifList.value).isEqualTo(listOf(entry1, entry2)) + } + liveDataStoreImpl.hasActiveNotifs.addSyncObserver(observer) + liveDataStoreImpl.hasActiveNotifs.addAsyncObserver(observer) + liveDataStoreImpl.activeNotifCount.addSyncObserver(observer) + liveDataStoreImpl.activeNotifCount.addAsyncObserver(observer) + liveDataStoreImpl.activeNotifList.addSyncObserver(observer) + liveDataStoreImpl.activeNotifList.addAsyncObserver(observer) + liveDataStoreImpl.setActiveNotifList(listOf(entry1, entry2)) + executor.runAllReady() + } + + @Test + fun testOriginalListIsCopied() { + val entry1 = NotificationEntryBuilder().setId(1).build() + val entry2 = NotificationEntryBuilder().setId(2).build() + val mutableInputList = mutableListOf(entry1, entry2) + val observer: (Any) -> Unit = { + mutableInputList.clear() + assertThat(liveDataStoreImpl.hasActiveNotifs.value).isEqualTo(true) + assertThat(liveDataStoreImpl.activeNotifCount.value).isEqualTo(2) + assertThat(liveDataStoreImpl.activeNotifList.value).isEqualTo(listOf(entry1, entry2)) + } + liveDataStoreImpl.hasActiveNotifs.addSyncObserver(observer) + liveDataStoreImpl.hasActiveNotifs.addAsyncObserver(observer) + liveDataStoreImpl.activeNotifCount.addSyncObserver(observer) + liveDataStoreImpl.activeNotifCount.addAsyncObserver(observer) + liveDataStoreImpl.activeNotifList.addSyncObserver(observer) + liveDataStoreImpl.activeNotifList.addAsyncObserver(observer) + liveDataStoreImpl.setActiveNotifList(mutableInputList) + executor.runAllReady() + } + + @Test + fun testProvidedListIsUnmodifiable() { + val entry1 = NotificationEntryBuilder().setId(1).build() + val entry2 = NotificationEntryBuilder().setId(2).build() + val observer: (List<NotificationEntry>) -> Unit = { providedValue -> + val provided = providedValue as MutableList<NotificationEntry> + Assert.assertThrows(UnsupportedOperationException::class.java) { + provided.clear() + } + val current = liveDataStoreImpl.activeNotifList.value as MutableList<NotificationEntry> + Assert.assertThrows(UnsupportedOperationException::class.java) { + current.clear() + } + assertThat(liveDataStoreImpl.activeNotifList.value).isEqualTo(listOf(entry1, entry2)) + } + liveDataStoreImpl.activeNotifList.addSyncObserver(observer) + liveDataStoreImpl.activeNotifList.addAsyncObserver(observer) + liveDataStoreImpl.setActiveNotifList(mutableListOf(entry1, entry2)) + executor.runAllReady() + } +}
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreMocks.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreMocks.kt new file mode 100644 index 000000000000..6e81c69045ce --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreMocks.kt @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2021 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.collection + +import com.android.systemui.util.mockito.mock +import org.mockito.Mockito.`when` as whenever + +/** Creates a mock which returns mocks for the NotifLiveDataImpl fields. */ +fun createNotifLiveDataStoreImplMock(): NotifLiveDataStoreImpl { + val dataStoreImpl: NotifLiveDataStoreImpl = mock() + whenever(dataStoreImpl.hasActiveNotifs).thenReturn(mock()) + whenever(dataStoreImpl.activeNotifCount).thenReturn(mock()) + whenever(dataStoreImpl.activeNotifList).thenReturn(mock()) + return dataStoreImpl +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifPipelineTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifPipelineTest.kt deleted file mode 100644 index 287f50c4202a..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifPipelineTest.kt +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (C) 2019 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.collection - -import android.testing.AndroidTestingRunner -import androidx.test.filters.SmallTest -import com.android.systemui.SysuiTestCase -import com.android.systemui.statusbar.notification.collection.render.RenderStageManager -import com.google.common.truth.Truth.assertThat -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.Mock -import org.mockito.MockitoAnnotations -import org.mockito.Mockito.`when` as whenever - -@SmallTest -@RunWith(AndroidTestingRunner::class) -class NotifPipelineTest : SysuiTestCase() { - - @Mock private lateinit var notifCollection: NotifCollection - @Mock private lateinit var shadeListBuilder: ShadeListBuilder - @Mock private lateinit var renderStageManager: RenderStageManager - private lateinit var notifPipeline: NotifPipeline - - @Before - fun setup() { - MockitoAnnotations.initMocks(this) - notifPipeline = NotifPipeline(notifCollection, shadeListBuilder, renderStageManager) - whenever(shadeListBuilder.shadeList).thenReturn(listOf( - NotificationEntryBuilder().setPkg("foo").setId(1).build(), - NotificationEntryBuilder().setPkg("foo").setId(2).build(), - group( - NotificationEntryBuilder().setPkg("bar").setId(1).build(), - NotificationEntryBuilder().setPkg("bar").setId(2).build(), - NotificationEntryBuilder().setPkg("bar").setId(3).build(), - NotificationEntryBuilder().setPkg("bar").setId(4).build() - ), - NotificationEntryBuilder().setPkg("baz").setId(1).build() - )) - } - - private fun group(summary: NotificationEntry, vararg children: NotificationEntry): GroupEntry { - return GroupEntry(summary.key, summary.creationTime).also { group -> - group.summary = summary - for (it in children) { - group.addChild(it) - } - } - } - - @Test - fun testGetShadeListCount() { - assertThat(notifPipeline.getShadeListCount()).isEqualTo(7) - } - - @Test - fun testGetFlatShadeList() { - assertThat(notifPipeline.getFlatShadeList().map { it.key }).containsExactly( - "0|foo|1|null|0", - "0|foo|2|null|0", - "0|bar|1|null|0", - "0|bar|2|null|0", - "0|bar|3|null|0", - "0|bar|4|null|0", - "0|baz|1|null|0" - ).inOrder() - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DataStoreCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DataStoreCoordinatorTest.kt new file mode 100644 index 000000000000..59fc591e4d06 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DataStoreCoordinatorTest.kt @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2021 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.collection.coordinator + +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper.RunWithLooper +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder +import com.android.systemui.statusbar.notification.collection.NotifLiveDataStoreImpl +import com.android.systemui.statusbar.notification.collection.NotifPipeline +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder +import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection +import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderListListener +import com.android.systemui.statusbar.notification.collection.render.NotifStackController +import com.android.systemui.util.mockito.eq +import com.android.systemui.util.mockito.withArgCaptor +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.Mockito.verifyNoMoreInteractions +import org.mockito.MockitoAnnotations.initMocks + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@RunWithLooper +class DataStoreCoordinatorTest : SysuiTestCase() { + private lateinit var coordinator: DataStoreCoordinator + private lateinit var afterRenderListListener: OnAfterRenderListListener + + private lateinit var entry: NotificationEntry + + @Mock private lateinit var pipeline: NotifPipeline + @Mock private lateinit var notifLiveDataStoreImpl: NotifLiveDataStoreImpl + @Mock private lateinit var stackController: NotifStackController + @Mock private lateinit var section: NotifSection + + @Before + fun setUp() { + initMocks(this) + entry = NotificationEntryBuilder().setSection(section).build() + coordinator = DataStoreCoordinator(notifLiveDataStoreImpl) + coordinator.attach(pipeline) + afterRenderListListener = withArgCaptor { + verify(pipeline).addOnAfterRenderListListener(capture()) + } + } + + @Test + fun testUpdateDataStore_withOneEntry() { + afterRenderListListener.onAfterRenderList(listOf(entry), stackController) + verify(notifLiveDataStoreImpl).setActiveNotifList(eq(listOf(entry))) + verifyNoMoreInteractions(notifLiveDataStoreImpl) + } + + @Test + fun testUpdateDataStore_withGroups() { + afterRenderListListener.onAfterRenderList( + listOf( + notificationEntry("foo", 1), + notificationEntry("foo", 2), + GroupEntryBuilder().setSummary( + notificationEntry("bar", 1) + ).setChildren( + listOf( + notificationEntry("bar", 2), + notificationEntry("bar", 3), + notificationEntry("bar", 4) + ) + ).setSection(section).build(), + notificationEntry("baz", 1) + ), + stackController + ) + val list: List<NotificationEntry> = withArgCaptor { + verify(notifLiveDataStoreImpl).setActiveNotifList(capture()) + } + assertThat(list.map { it.key }).containsExactly( + "0|foo|1|null|0", + "0|foo|2|null|0", + "0|bar|1|null|0", + "0|bar|2|null|0", + "0|bar|3|null|0", + "0|bar|4|null|0", + "0|baz|1|null|0" + ).inOrder() + verifyNoMoreInteractions(notifLiveDataStoreImpl) + } + + private fun notificationEntry(pkg: String, id: Int) = + NotificationEntryBuilder().setPkg(pkg).setId(id).setSection(section).build() + + @Test + fun testUpdateDataStore_withZeroEntries_whenNewPipelineEnabled() { + afterRenderListListener.onAfterRenderList(listOf(), stackController) + verify(notifLiveDataStoreImpl).setActiveNotifList(eq(listOf())) + verifyNoMoreInteractions(notifLiveDataStoreImpl) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerLegacyTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerLegacyTest.java index 395aec392021..2dfb9fc674ba 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerLegacyTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerLegacyTest.java @@ -46,6 +46,8 @@ import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.StatusBarStateControllerImpl; import com.android.systemui.statusbar.notification.NotifPipelineFlags; import com.android.systemui.statusbar.notification.NotificationEntryManager; +import com.android.systemui.statusbar.notification.collection.NotifLiveData; +import com.android.systemui.statusbar.notification.collection.NotifLiveDataStore; import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; @@ -65,6 +67,7 @@ import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; +import java.util.List; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.Executor; @@ -82,6 +85,8 @@ public class NotificationLoggerLegacyTest extends SysuiTestCase { // Dependency mocks: @Mock private NotifPipelineFlags mNotifPipelineFlags; + @Mock private NotifLiveDataStore mNotifLiveDataStore; + @Mock private NotifLiveData<List<NotificationEntry>> mActiveNotifList; @Mock private NotificationVisibilityProvider mVisibilityProvider; @Mock private NotificationEntryManager mEntryManager; @Mock private NotifPipeline mNotifPipeline; @@ -97,6 +102,7 @@ public class NotificationLoggerLegacyTest extends SysuiTestCase { @Before public void setUp() { MockitoAnnotations.initMocks(this); + when(mNotifLiveDataStore.getActiveNotifList()).thenReturn(mActiveNotifList); mEntry = new NotificationEntryBuilder() .setPkg(TEST_PACKAGE_NAME) @@ -112,6 +118,7 @@ public class NotificationLoggerLegacyTest extends SysuiTestCase { mListener, mUiBgExecutor, mNotifPipelineFlags, + mNotifLiveDataStore, mVisibilityProvider, mEntryManager, mNotifPipeline, @@ -145,7 +152,7 @@ public class NotificationLoggerLegacyTest extends SysuiTestCase { any(NotificationVisibility[].class)); when(mListContainer.isInVisibleLocation(any())).thenReturn(true); - when(mEntryManager.getVisibleNotifications()).thenReturn(Lists.newArrayList(mEntry)); + when(mActiveNotifList.getValue()).thenReturn(Lists.newArrayList(mEntry)); mLogger.getChildLocationsChangedListenerForTest().onChildLocationsChanged(); TestableLooper.get(this).processAllMessages(); mUiBgExecutor.runAllReady(); @@ -167,7 +174,7 @@ public class NotificationLoggerLegacyTest extends SysuiTestCase { public void testStoppingNotificationLoggingReportsCurrentNotifications() throws Exception { when(mListContainer.isInVisibleLocation(any())).thenReturn(true); - when(mEntryManager.getVisibleNotifications()).thenReturn(Lists.newArrayList(mEntry)); + when(mActiveNotifList.getValue()).thenReturn(Lists.newArrayList(mEntry)); mLogger.getChildLocationsChangedListenerForTest().onChildLocationsChanged(); TestableLooper.get(this).processAllMessages(); mUiBgExecutor.runAllReady(); @@ -196,7 +203,7 @@ public class NotificationLoggerLegacyTest extends SysuiTestCase { @Test public void testLogPanelShownOnWake() { - when(mEntryManager.getVisibleNotifications()).thenReturn(Lists.newArrayList(mEntry)); + when(mActiveNotifList.getValue()).thenReturn(Lists.newArrayList(mEntry)); setStateAsleep(); mLogger.onDozingChanged(false); // Wake to lockscreen assertEquals(1, mNotificationPanelLoggerFake.getCalls().size()); @@ -212,7 +219,7 @@ public class NotificationLoggerLegacyTest extends SysuiTestCase { @Test public void testLogPanelShownOnShadePull() { - when(mEntryManager.getVisibleNotifications()).thenReturn(Lists.newArrayList(mEntry)); + when(mActiveNotifList.getValue()).thenReturn(Lists.newArrayList(mEntry)); setStateAwake(); // Now expand panel mLogger.onPanelExpandedChanged(true); @@ -240,7 +247,7 @@ public class NotificationLoggerLegacyTest extends SysuiTestCase { .build(); entry.setRow(mRow); - when(mEntryManager.getVisibleNotifications()).thenReturn(Lists.newArrayList(entry)); + when(mActiveNotifList.getValue()).thenReturn(Lists.newArrayList(entry)); setStateAsleep(); mLogger.onDozingChanged(false); // Wake to lockscreen assertEquals(1, mNotificationPanelLoggerFake.getCalls().size()); @@ -254,6 +261,7 @@ public class NotificationLoggerLegacyTest extends SysuiTestCase { TestableNotificationLogger(NotificationListener notificationListener, Executor uiBgExecutor, NotifPipelineFlags notifPipelineFlags, + NotifLiveDataStore notifLiveDataStore, NotificationVisibilityProvider visibilityProvider, NotificationEntryManager entryManager, NotifPipeline notifPipeline, @@ -264,6 +272,7 @@ public class NotificationLoggerLegacyTest extends SysuiTestCase { notificationListener, uiBgExecutor, notifPipelineFlags, + notifLiveDataStore, visibilityProvider, entryManager, notifPipeline, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java index 3a9b29799988..4d861f96a943 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java @@ -46,6 +46,8 @@ import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.StatusBarStateControllerImpl; import com.android.systemui.statusbar.notification.NotifPipelineFlags; import com.android.systemui.statusbar.notification.NotificationEntryManager; +import com.android.systemui.statusbar.notification.collection.NotifLiveData; +import com.android.systemui.statusbar.notification.collection.NotifLiveDataStore; import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; @@ -65,6 +67,7 @@ import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; +import java.util.List; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.Executor; @@ -82,6 +85,8 @@ public class NotificationLoggerTest extends SysuiTestCase { // Dependency mocks: @Mock private NotifPipelineFlags mNotifPipelineFlags; + @Mock private NotifLiveDataStore mNotifLiveDataStore; + @Mock private NotifLiveData<List<NotificationEntry>> mActiveNotifEntries; @Mock private NotificationVisibilityProvider mVisibilityProvider; @Mock private NotificationEntryManager mEntryManager; @Mock private NotifPipeline mNotifPipeline; @@ -98,6 +103,7 @@ public class NotificationLoggerTest extends SysuiTestCase { public void setUp() { MockitoAnnotations.initMocks(this); when(mNotifPipelineFlags.isNewPipelineEnabled()).thenReturn(true); + when(mNotifLiveDataStore.getActiveNotifList()).thenReturn(mActiveNotifEntries); mEntry = new NotificationEntryBuilder() .setPkg(TEST_PACKAGE_NAME) @@ -113,6 +119,7 @@ public class NotificationLoggerTest extends SysuiTestCase { mListener, mUiBgExecutor, mNotifPipelineFlags, + mNotifLiveDataStore, mVisibilityProvider, mEntryManager, mNotifPipeline, @@ -146,7 +153,7 @@ public class NotificationLoggerTest extends SysuiTestCase { any(NotificationVisibility[].class)); when(mListContainer.isInVisibleLocation(any())).thenReturn(true); - when(mNotifPipeline.getFlatShadeList()).thenReturn(Lists.newArrayList(mEntry)); + when(mActiveNotifEntries.getValue()).thenReturn(Lists.newArrayList(mEntry)); mLogger.getChildLocationsChangedListenerForTest().onChildLocationsChanged(); TestableLooper.get(this).processAllMessages(); mUiBgExecutor.runAllReady(); @@ -168,7 +175,7 @@ public class NotificationLoggerTest extends SysuiTestCase { public void testStoppingNotificationLoggingReportsCurrentNotifications() throws Exception { when(mListContainer.isInVisibleLocation(any())).thenReturn(true); - when(mNotifPipeline.getFlatShadeList()).thenReturn(Lists.newArrayList(mEntry)); + when(mActiveNotifEntries.getValue()).thenReturn(Lists.newArrayList(mEntry)); mLogger.getChildLocationsChangedListenerForTest().onChildLocationsChanged(); TestableLooper.get(this).processAllMessages(); mUiBgExecutor.runAllReady(); @@ -197,7 +204,7 @@ public class NotificationLoggerTest extends SysuiTestCase { @Test public void testLogPanelShownOnWake() { - when(mNotifPipeline.getFlatShadeList()).thenReturn(Lists.newArrayList(mEntry)); + when(mActiveNotifEntries.getValue()).thenReturn(Lists.newArrayList(mEntry)); setStateAsleep(); mLogger.onDozingChanged(false); // Wake to lockscreen assertEquals(1, mNotificationPanelLoggerFake.getCalls().size()); @@ -213,7 +220,7 @@ public class NotificationLoggerTest extends SysuiTestCase { @Test public void testLogPanelShownOnShadePull() { - when(mNotifPipeline.getFlatShadeList()).thenReturn(Lists.newArrayList(mEntry)); + when(mActiveNotifEntries.getValue()).thenReturn(Lists.newArrayList(mEntry)); setStateAwake(); // Now expand panel mLogger.onPanelExpandedChanged(true); @@ -241,7 +248,7 @@ public class NotificationLoggerTest extends SysuiTestCase { .build(); entry.setRow(mRow); - when(mNotifPipeline.getFlatShadeList()).thenReturn(Lists.newArrayList(entry)); + when(mActiveNotifEntries.getValue()).thenReturn(Lists.newArrayList(entry)); setStateAsleep(); mLogger.onDozingChanged(false); // Wake to lockscreen assertEquals(1, mNotificationPanelLoggerFake.getCalls().size()); @@ -255,6 +262,7 @@ public class NotificationLoggerTest extends SysuiTestCase { TestableNotificationLogger(NotificationListener notificationListener, Executor uiBgExecutor, NotifPipelineFlags notifPipelineFlags, + NotifLiveDataStore notifLiveDataStore, NotificationVisibilityProvider visibilityProvider, NotificationEntryManager entryManager, NotifPipeline notifPipeline, @@ -265,6 +273,7 @@ public class NotificationLoggerTest extends SysuiTestCase { notificationListener, uiBgExecutor, notifPipelineFlags, + notifLiveDataStore, visibilityProvider, entryManager, notifPipeline, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java index eeda9ddd1466..a890414115dd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java @@ -68,6 +68,7 @@ import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.NotificationEntryManagerLogger; import com.android.systemui.statusbar.notification.NotificationFilter; import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager; +import com.android.systemui.statusbar.notification.collection.NotifLiveDataStoreMocksKt; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationRankingManager; import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl; @@ -191,6 +192,7 @@ public class NotificationEntryManagerInflationTest extends SysuiTestCase { mLeakDetector, mock(ForegroundServiceDismissalFeatureController.class), mock(IStatusBarService.class), + NotifLiveDataStoreMocksKt.createNotifLiveDataStoreImplMock(), mock(DumpManager.class) ); mEntryManager.initialize( diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightsOutNotifControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightsOutNotifControllerTest.java index e386263be5a0..9664035e1e1b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightsOutNotifControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightsOutNotifControllerTest.java @@ -16,14 +16,12 @@ package com.android.systemui.statusbar.phone; -import static android.service.notification.NotificationListenerService.REASON_CANCEL_ALL; import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS; import static android.view.WindowInsetsController.BEHAVIOR_DEFAULT; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -34,13 +32,13 @@ import android.view.View; import android.view.ViewPropertyAnimator; import android.view.WindowManager; +import androidx.lifecycle.Observer; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; import com.android.systemui.statusbar.CommandQueue; -import com.android.systemui.statusbar.notification.NotificationEntryListener; -import com.android.systemui.statusbar.notification.NotificationEntryManager; -import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.collection.NotifLiveData; +import com.android.systemui.statusbar.notification.collection.NotifLiveDataStore; import org.junit.Before; import org.junit.Test; @@ -59,18 +57,19 @@ public class LightsOutNotifControllerTest extends SysuiTestCase { private static final int LIGHTS_ON = 0; private static final int LIGHTS_OUT = APPEARANCE_LOW_PROFILE_BARS; - @Mock private NotificationEntryManager mEntryManager; + @Mock private NotifLiveData<Boolean> mHasActiveNotifs; + @Mock private NotifLiveDataStore mNotifLiveDataStore; @Mock private CommandQueue mCommandQueue; @Mock private WindowManager mWindowManager; @Mock private Display mDisplay; - @Captor private ArgumentCaptor<NotificationEntryListener> mListenerCaptor; + @Captor private ArgumentCaptor<Observer<Boolean>> mObserverCaptor; @Captor private ArgumentCaptor<CommandQueue.Callbacks> mCallbacksCaptor; private View mLightsOutView; private LightsOutNotifController mLightsOutNotifController; private int mDisplayId; - private NotificationEntryListener mEntryListener; + private Observer<Boolean> mHaActiveNotifsObserver; private CommandQueue.Callbacks mCallbacks; @Before @@ -80,15 +79,20 @@ public class LightsOutNotifControllerTest extends SysuiTestCase { mLightsOutView = new View(mContext); when(mWindowManager.getDefaultDisplay()).thenReturn(mDisplay); when(mDisplay.getDisplayId()).thenReturn(mDisplayId); + when(mNotifLiveDataStore.getHasActiveNotifs()).thenReturn(mHasActiveNotifs); + when(mHasActiveNotifs.getValue()).thenReturn(false); mLightsOutNotifController = new LightsOutNotifController( - mLightsOutView, mWindowManager, mEntryManager, mCommandQueue); + mLightsOutView, + mWindowManager, + mNotifLiveDataStore, + mCommandQueue); mLightsOutNotifController.init(); mLightsOutNotifController.onViewAttached(); // Capture the entry listener object so we can simulate events in tests below - verify(mEntryManager).addNotificationEntryListener(mListenerCaptor.capture()); - mEntryListener = Objects.requireNonNull(mListenerCaptor.getValue()); + verify(mHasActiveNotifs).addSyncObserver(mObserverCaptor.capture()); + mHaActiveNotifsObserver = Objects.requireNonNull(mObserverCaptor.getValue()); // Capture the callback object so we can simulate callback events in tests below verify(mCommandQueue).addCallback(mCallbacksCaptor.capture()); @@ -138,7 +142,7 @@ public class LightsOutNotifControllerTest extends SysuiTestCase { @Test public void testLightsOut_withNotifs_onSystemBarAttributesChanged() { // GIVEN active visible notifications - when(mEntryManager.hasActiveNotifications()).thenReturn(true); + when(mHasActiveNotifs.getValue()).thenReturn(true); // WHEN lights out mCallbacks.onSystemBarAttributesChanged( @@ -158,7 +162,7 @@ public class LightsOutNotifControllerTest extends SysuiTestCase { @Test public void testLightsOut_withoutNotifs_onSystemBarAttributesChanged() { // GIVEN no active visible notifications - when(mEntryManager.hasActiveNotifications()).thenReturn(false); + when(mHasActiveNotifs.getValue()).thenReturn(false); // WHEN lights out mCallbacks.onSystemBarAttributesChanged( @@ -178,7 +182,7 @@ public class LightsOutNotifControllerTest extends SysuiTestCase { @Test public void testLightsOn_afterLightsOut_onSystemBarAttributesChanged() { // GIVEN active visible notifications - when(mEntryManager.hasActiveNotifications()).thenReturn(true); + when(mHasActiveNotifs.getValue()).thenReturn(true); // WHEN lights on mCallbacks.onSystemBarAttributesChanged( @@ -198,15 +202,15 @@ public class LightsOutNotifControllerTest extends SysuiTestCase { @Test public void testEntryAdded() { // GIVEN no visible notifications and lights out - when(mEntryManager.hasActiveNotifications()).thenReturn(false); + when(mHasActiveNotifs.getValue()).thenReturn(false); mLightsOutNotifController.mAppearance = LIGHTS_OUT; mLightsOutNotifController.updateLightsOutView(); assertIsShowingDot(false); // WHEN an active notification is added - when(mEntryManager.hasActiveNotifications()).thenReturn(true); + when(mHasActiveNotifs.getValue()).thenReturn(true); assertTrue(mLightsOutNotifController.shouldShowDot()); - mEntryListener.onNotificationAdded(mock(NotificationEntry.class)); + mHaActiveNotifsObserver.onChanged(true); // THEN we should see the dot view assertIsShowingDot(true); @@ -215,38 +219,20 @@ public class LightsOutNotifControllerTest extends SysuiTestCase { @Test public void testEntryRemoved() { // GIVEN a visible notification and lights out - when(mEntryManager.hasActiveNotifications()).thenReturn(true); + when(mHasActiveNotifs.getValue()).thenReturn(true); mLightsOutNotifController.mAppearance = LIGHTS_OUT; mLightsOutNotifController.updateLightsOutView(); assertIsShowingDot(true); // WHEN all active notifications are removed - when(mEntryManager.hasActiveNotifications()).thenReturn(false); + when(mHasActiveNotifs.getValue()).thenReturn(false); assertFalse(mLightsOutNotifController.shouldShowDot()); - mEntryListener.onEntryRemoved( - mock(NotificationEntry.class), null, false, REASON_CANCEL_ALL); + mHaActiveNotifsObserver.onChanged(false); // THEN we shouldn't see the dot view assertIsShowingDot(false); } - @Test - public void testEntryUpdated() { - // GIVEN no visible notifications and lights out - when(mEntryManager.hasActiveNotifications()).thenReturn(false); - mLightsOutNotifController.mAppearance = LIGHTS_OUT; - mLightsOutNotifController.updateLightsOutView(); - assertIsShowingDot(false); - - // WHEN an active notification is added - when(mEntryManager.hasActiveNotifications()).thenReturn(true); - assertTrue(mLightsOutNotifController.shouldShowDot()); - mEntryListener.onPostEntryUpdated(mock(NotificationEntry.class)); - - // THEN we should see the dot view - assertIsShowingDot(true); - } - private void assertIsShowingDot(boolean isShowing) { // cancel the animation so we can check the end state final ViewPropertyAnimator animation = mLightsOutView.animate(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java index 0289b9aff062..9d5b17ea6738 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java @@ -119,6 +119,7 @@ import com.android.systemui.statusbar.notification.NotifPipelineFlags; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.NotificationFilter; import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; +import com.android.systemui.statusbar.notification.collection.NotifLiveDataStore; import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; @@ -274,6 +275,7 @@ public class StatusBarTest extends SysuiTestCase { @Mock private OperatorNameViewController.Factory mOperatorNameViewControllerFactory; @Mock private ActivityLaunchAnimator mActivityLaunchAnimator; @Mock private NotifPipelineFlags mNotifPipelineFlags; + @Mock private NotifLiveDataStore mNotifLiveDataStore; private ShadeController mShadeController; private final FakeSystemClock mFakeSystemClock = new FakeSystemClock(); private FakeExecutor mMainExecutor = new FakeExecutor(mFakeSystemClock); @@ -306,6 +308,7 @@ public class StatusBarTest extends SysuiTestCase { mNotificationListener, mUiBgExecutor, mNotifPipelineFlags, + mNotifLiveDataStore, mVisibilityProvider, mock(NotificationEntryManager.class), mock(NotifPipeline.class), |