Remove access to pipeline outputs from NotifPipeline.

* Add NotifLiveDataStore which provides access to and observation of pipeline outputs.
* Adds NotifLiveData which is written to by each of the two pipelines, only when active.
* Replaces an instance of NotifPipeline.getShadeListCount() from NotificationsControllerImpl.
* Replaces an entry listener in LightsOutNotifController with an observer on this data store.

Fixes: 205696003
Test: atest NotifLiveDataImplTest NotificationListenerTest DataStoreCoordinatorTest LightsOutNotifControllerTest
Change-Id: I3f190b601bf0f71cb03d155d240268d06049bbf7
This commit is contained in:
Jeff DeCew
2021-11-27 20:09:56 +00:00
parent 6f0493a366
commit 955525fe52
27 changed files with 824 additions and 305 deletions

View File

@@ -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) {

View File

@@ -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>)
}

View File

@@ -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)
}
}

View File

@@ -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
}
}

View File

@@ -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")
}
}
}
}

View File

@@ -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)

View File

@@ -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
}
}

View File

@@ -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))
}

View File

@@ -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
}

View File

@@ -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);
}

View File

@@ -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*.

View File

@@ -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();

View File

@@ -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();
}
};
}

View File

@@ -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}.
*/

View File

@@ -38,6 +38,11 @@ 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
@@ -45,3 +50,6 @@ class ListenerSet<E> : Iterable<E> {
*/
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()

View File

@@ -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(

View File

@@ -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;

View File

@@ -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)
}
}

View File

@@ -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()
}
}

View File

@@ -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
}

View File

@@ -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()
}
}

View File

@@ -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)
}
}

View File

@@ -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,

View File

@@ -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,

View File

@@ -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(

View File

@@ -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();

View File

@@ -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),