Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 955525fe authored by Jeff DeCew's avatar Jeff DeCew
Browse files

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
parent 6f0493a3
Loading
Loading
Loading
Loading
+6 −1
Original line number Diff line number Diff line
@@ -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) {
+51 −0
Original line number Diff line number Diff line
/*
 * 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>)
}
+137 −0
Original line number Diff line number Diff line
/*
 * 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
+0 −32
Original line number Diff line number Diff line
@@ -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
+59 −0
Original line number Diff line number Diff line
@@ -14,37 +14,46 @@
 * limitations under the License.
 */

package com.android.systemui.statusbar.notification.collection.legacy
package com.android.systemui.statusbar.notification.collection.coordinator

import com.android.internal.statusbar.NotificationVisibility
import com.android.systemui.statusbar.notification.NotificationEntryManager
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.render.NotificationVisibilityProvider
import com.android.systemui.statusbar.notification.logging.NotificationLogger
import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
import com.android.systemui.statusbar.notification.collection.render.requireSummary
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)
/**
 * 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) }
    }

    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)
    fun onAfterRenderList(entries: List<ListEntry>) {
        val flatEntryList = flattenedEntryList(entries)
        notifLiveDataStoreImpl.setActiveNotifList(flatEntryList)
    }

    override fun getLocation(key: String): NotificationVisibility.NotificationLocation =
            NotificationLogger.getNotificationLocation(
                    notifEntryManager.getActiveNotificationUnfiltered(key))
    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
Loading